Skip to content

Commit

Permalink
[cli] Add filters for Export (#2596)
Browse files Browse the repository at this point in the history
## Description

Feature: Add support for selective export with filters for account,
albums, and options to exclude hidden or shared files.

    Flags Added:
        --shared: Include shared albums in export (default: true).
        --hidden: Include hidden albums in export (default: true).
        --albums: Comma-separated list of album names to export.
--emails: Comma-separated list of emails of the accounts that needs to
be exported

Behavior: By default, both hidden and shared albums are exported.

## Tests
Tested locally
  • Loading branch information
ua741 authored Aug 14, 2024
2 parents 49f069c + 395f038 commit d8b338a
Show file tree
Hide file tree
Showing 10 changed files with 152 additions and 9 deletions.
24 changes: 22 additions & 2 deletions cli/cmd/export.go
Original file line number Diff line number Diff line change
@@ -1,19 +1,39 @@
package cmd

import (
"github.com/ente-io/cli/pkg/model"
"github.com/spf13/cobra"
)

// versionCmd represents the version command
// exportCmd represents the export command
var exportCmd = &cobra.Command{
Use: "export",
Short: "Starts the export process",
Long: ``,
Run: func(cmd *cobra.Command, args []string) {
ctrl.Export()
// Retrieve flag values
shared, _ := cmd.Flags().GetBool("shared")
hidden, _ := cmd.Flags().GetBool("hidden")
albums, _ := cmd.Flags().GetStringSlice("albums")
emails, _ := cmd.Flags().GetStringSlice("emails")
// Create Filters struct with flag values
filters := model.Filter{
ExcludeShared: !shared,
ExcludeHidden: !hidden,
Albums: albums,
Emails: emails,
}
// Call the Export function with the filters
ctrl.Export(filters)
},
}

func init() {
rootCmd.AddCommand(exportCmd)

// Add flags for Filters struct fields with default value true
exportCmd.Flags().Bool("shared", true, "to exclude shared albums, pass --shared=false")
exportCmd.Flags().Bool("hidden", true, "to exclude hidden albums, pass --hidden=false")
exportCmd.Flags().StringSlice("albums", []string{}, "Comma-separated list of album names to export")
exportCmd.Flags().StringSlice("emails", []string{}, "Comma-separated list of emails to export files shared with")
}
2 changes: 1 addition & 1 deletion cli/pkg/admin_actions.go
Original file line number Diff line number Diff line change
Expand Up @@ -186,7 +186,7 @@ func (c *ClICtrl) buildAdminContext(ctx context.Context, adminEmail string) (con
if err != nil {
return nil, err
}
accountCtx := c.buildRequestContext(ctx, *acc)
accountCtx := c.buildRequestContext(ctx, *acc, model.Filter{})
c.Client.AddToken(acc.AccountKey(), secretInfo.TokenStr())
return accountCtx, nil
}
Expand Down
6 changes: 6 additions & 0 deletions cli/pkg/model/constants.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,9 @@ const (
CollectionsSyncKey = "lastCollectionSync"
CollectionsFileSyncKeyFmt = "collectionFilesSync-%d"
)

type ContextKey string

const (
FilterKey ContextKey = "export_filter"
)
14 changes: 14 additions & 0 deletions cli/pkg/model/export/filter.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package export

type Filters struct {
// When true, none of the shared albums are exported
ExcludeShared bool
// When true, none of the shared files are exported
ExcludeSharedFiles bool
// When true, hidden albums are not exported
ExcludeHidden bool
// when album name is provided, only files in those albums are exported
Albums []string
// when email is provided, only files shared with that email are exported
Emails []string
}
66 changes: 66 additions & 0 deletions cli/pkg/model/filter.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
package model

import (
"log"
"strings"
)

type Filter struct {
// When true, none of the shared albums are exported
ExcludeShared bool
// When true, none of the shared files are exported
ExcludeSharedFiles bool
// When true, hidden albums are not exported
ExcludeHidden bool
// when album name is provided, only files in those albums are exported
Albums []string
// when email is provided, only files shared with that email are exported
Emails []string
}

func (f Filter) SkipAccount(email string) bool {
if len(f.Emails) == 0 {
return false
}
for _, e := range f.Emails {
if strings.ToLower(e) == strings.ToLower(strings.TrimSpace(email)) {
return false
}
}
return true
}

func (f Filter) SkipAlbum(album RemoteAlbum, shouldLog bool) bool {
if f.excludeByName(album) {
if shouldLog {
log.Printf("Skipping album %s as it's not part of album to export", album.AlbumName)
}
return true
}
if f.ExcludeShared && album.IsShared {
if shouldLog {
log.Printf("Skipping album %s as it's shared", album.AlbumName)
}
return true
}
if f.ExcludeHidden && album.IsHidden() {
if shouldLog {
log.Printf("Skipping album %s as it's hidden", album.AlbumName)
}
return true
}
return false
}

// excludeByName returns true if albums list is not empty and album name is not in the list
func (f Filter) excludeByName(album RemoteAlbum) bool {
if len(f.Albums) > 0 {
for _, a := range f.Albums {
if strings.ToLower(a) == strings.ToLower(strings.TrimSpace(album.AlbumName)) {
return false
}
}
return true
}
return false
}
7 changes: 7 additions & 0 deletions cli/pkg/model/remote.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,13 @@ type RemoteAlbum struct {
LastUpdatedAt int64 `json:"lastUpdatedAt"`
}

func (r *RemoteAlbum) IsHidden() bool {
if value, ok := r.PrivateMeta["visibility"]; ok {
return int64(value.(float64)) == int64(2)
}
return false
}

type AlbumFileEntry struct {
FileID int64 `json:"fileID"`
AlbumID int64 `json:"albumID"`
Expand Down
5 changes: 5 additions & 0 deletions cli/pkg/remote_sync.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,13 +50,18 @@ func (c *ClICtrl) fetchRemoteCollections(ctx context.Context) error {

func (c *ClICtrl) fetchRemoteFiles(ctx context.Context) error {
albums, err := c.getRemoteAlbums(ctx)
filter := ctx.Value(model.FilterKey).(model.Filter)
if err != nil {
return err
}

for _, album := range albums {
if album.IsDeleted {
continue
}
if filter.SkipAlbum(album, true) {
continue
}

lastSyncTime, lastSyncTimeErr := c.GetInt64ConfigValue(ctx, fmt.Sprintf(model.CollectionsFileSyncKeyFmt, album.ID))
if lastSyncTimeErr != nil {
Expand Down
5 changes: 4 additions & 1 deletion cli/pkg/remote_to_disk_album.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,11 @@ func (c *ClICtrl) createLocalFolderForRemoteAlbums(ctx context.Context, account
if err != nil {
return err
}

filter := ctx.Value(model.FilterKey).(model.Filter)
for _, album := range albums {
if filter.SkipAlbum(album, false) {
continue
}
if album.IsDeleted {
if meta, ok := albumIDToMetaMap[album.ID]; ok {
log.Printf("Deleting album %s as it is deleted", meta.AlbumName)
Expand Down
14 changes: 14 additions & 0 deletions cli/pkg/remote_to_disk_file.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,17 @@ func (c *ClICtrl) syncFiles(ctx context.Context, account model.Account) error {
if err != nil {
return err
}
albumsToSkip := make(map[int64]bool)
filter := ctx.Value(model.FilterKey).(model.Filter)
remoteAlbums, readAlbumErr := c.getRemoteAlbums(ctx)
if readAlbumErr != nil {
return readAlbumErr
}
for _, album := range remoteAlbums {
if !album.IsDeleted && filter.SkipAlbum(album, false) {
albumsToSkip[album.ID] = true
}
}
entries, err := c.getRemoteAlbumEntries(ctx)
if err != nil {
return err
Expand All @@ -36,6 +47,9 @@ func (c *ClICtrl) syncFiles(ctx context.Context, account model.Account) error {
if albumFileEntry.SyncedLocally {
continue
}
if _, ok := albumsToSkip[albumFileEntry.AlbumID]; ok {
continue
}
albumInfo, ok := albumIDToMetaMap[albumFileEntry.AlbumID]
if !ok {
log.Printf("Album %d not found in local metadata", albumFileEntry.AlbumID)
Expand Down
18 changes: 13 additions & 5 deletions cli/pkg/sync.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import (
"time"
)

func (c *ClICtrl) Export() error {
func (c *ClICtrl) Export(filter model.Filter) error {
accounts, err := c.GetAccounts(context.Background())
if err != nil {
return err
Expand All @@ -21,8 +21,13 @@ func (c *ClICtrl) Export() error {
fmt.Printf("No accounts to sync\n Add account using `account add` cmd\n")
return nil
}

for _, account := range accounts {
log.SetPrefix(fmt.Sprintf("[%s-%s] ", account.App, account.Email))
if filter.SkipAccount(account.Email) {
log.Printf("Skip account %s: account is excluded by filter", account.Email)
continue
}
if account.ExportDir == "" {
log.Printf("Skip account %s: no export directory configured", account.Email)
continue
Expand All @@ -39,7 +44,7 @@ func (c *ClICtrl) Export() error {
log.Println("start sync")
retryCount := 0
for {
err = c.SyncAccount(account)
err = c.SyncAccount(account, filter)
if err != nil {
if model.ShouldRetrySync(err) && retryCount < 20 {
retryCount = retryCount + 1
Expand All @@ -60,12 +65,12 @@ func (c *ClICtrl) Export() error {
return nil
}

func (c *ClICtrl) SyncAccount(account model.Account) error {
func (c *ClICtrl) SyncAccount(account model.Account, filters model.Filter) error {
secretInfo, err := c.KeyHolder.LoadSecrets(account)
if err != nil {
return err
}
ctx := c.buildRequestContext(context.Background(), account)
ctx := c.buildRequestContext(context.Background(), account, filters)
err = createDataBuckets(c.DB, account)
if err != nil {
return err
Expand Down Expand Up @@ -94,10 +99,13 @@ func (c *ClICtrl) SyncAccount(account model.Account) error {
return nil
}

func (c *ClICtrl) buildRequestContext(ctx context.Context, account model.Account) context.Context {
func (c *ClICtrl) buildRequestContext(ctx context.Context,
account model.Account,
filter model.Filter) context.Context {
ctx = context.WithValue(ctx, "app", string(account.App))
ctx = context.WithValue(ctx, "account_key", account.AccountKey())
ctx = context.WithValue(ctx, "user_id", account.UserID)
ctx = context.WithValue(ctx, model.FilterKey, filter)
return ctx
}

Expand Down

0 comments on commit d8b338a

Please sign in to comment.