diff --git a/backend/internxt/internxt.go b/backend/internxt/internxt.go index e3ea92ba74d56..16038b6dde0d7 100644 --- a/backend/internxt/internxt.go +++ b/backend/internxt/internxt.go @@ -4,6 +4,7 @@ package internxt import ( "bytes" "context" + "errors" "fmt" "io" "path" @@ -14,11 +15,11 @@ import ( "github.com/StarHack/go-internxt-drive/auth" "github.com/StarHack/go-internxt-drive/buckets" config "github.com/StarHack/go-internxt-drive/config" - rclone_config "github.com/rclone/rclone/fs/config" - "github.com/StarHack/go-internxt-drive/files" "github.com/StarHack/go-internxt-drive/folders" + "github.com/StarHack/go-internxt-drive/users" "github.com/rclone/rclone/fs" + rclone_config "github.com/rclone/rclone/fs/config" "github.com/rclone/rclone/fs/config/configmap" "github.com/rclone/rclone/fs/config/configstruct" "github.com/rclone/rclone/fs/config/obscure" @@ -51,7 +52,7 @@ func init() { Default: false, Help: "Simulates empty files by uploading a small placeholder file instead. Alters the filename when uploading to keep track of empty files, but this is not visible through rclone.", }, - { + { Name: "use_2fa", Help: "Do you use 2FA to login?", Default: false, @@ -86,7 +87,7 @@ type Options struct { Password string `flag:"password" help:"Internxt account password"` Encoding encoder.MultiEncoder `config:"encoding"` SimulateEmptyFiles bool `config:"simulateEmptyFiles"` - Use2FA bool `config:"use_2fa" help:"Do you use 2FA to login?"` + Use2FA bool `config:"use_2fa" help:"Do you use 2FA to login?"` } // Fs represents an Internxt remote @@ -191,6 +192,8 @@ func NewFs(ctx context.Context, name, root string, m configmap.Mapper) (fs.Fs, e DirModTimeUpdatesOnWrite: false, }).Fill(ctx, f) + // Handle leading and trailing slashes + root = strings.Trim(root, "/") f.dirCache = dircache.New(root, cfg.RootFolderID, f) err = f.dirCache.FindRoot(ctx, false) @@ -241,24 +244,48 @@ func (f *Fs) Mkdir(ctx context.Context, dir string) error { } // Rmdir removes a directory +// Returns an error if it isn't empty func (f *Fs) Rmdir(ctx context.Context, dir string) error { + return f.purgeCheck(ctx, dir, true) +} + +// Purge deletes the directory and all its contents +func (f *Fs) Purge(ctx context.Context, dir string) error { + return f.purgeCheck(ctx, dir, false) +} + +func (f *Fs) purgeCheck(ctx context.Context, dir string, check bool) (err error) { + root := path.Join(f.root, dir) + if root == "" { + return errors.New("can't purge root directory") + } + + // check that the directory exists id, err := f.dirCache.FindDir(ctx, dir, false) if err != nil { return fs.ErrorDirNotFound } - // Replace these calls with GetFolderContent? (fmt.Sprintf("/storage/v2/folder/%d%s", folderID, query)) - childFolders, err := folders.ListAllFolders(f.cfg, id) - if err != nil { - return err - } - childFiles, err := folders.ListAllFiles(f.cfg, id) - if err != nil { - return err - } + if check { + // Replace these calls with GetFolderContent? (fmt.Sprintf("/storage/v2/folder/%d%s", folderID, query)) + // Check folders and files separately in case we only need to call the API once. + childFolders, err := folders.ListAllFolders(f.cfg, id) + if err != nil { + return err + } + + if len(childFolders) > 0 { + return fs.ErrorDirectoryNotEmpty + } - if len(childFiles) > 0 || len(childFolders) > 0 { - return fs.ErrorDirectoryNotEmpty + childFiles, err := folders.ListAllFiles(f.cfg, id) + if err != nil { + return err + } + + if len(childFiles) > 0 { + return fs.ErrorDirectoryNotEmpty + } } err = folders.DeleteFolder(f.cfg, id) @@ -272,6 +299,7 @@ func (f *Fs) Rmdir(ctx context.Context, dir string) error { f.dirCache.FlushDir(dir) time.Sleep(500 * time.Millisecond) // REMOVE THIS, use pacer to check for consistency? return nil + } // FindLeaf looks for a sub‑folder named `leaf` under the Internxt folder `pathID`. @@ -587,6 +615,28 @@ func (o *Object) SetModTime(ctx context.Context, t time.Time) error { return fs.ErrorCantSetModTime } +// About gets quota information +func (f *Fs) About(ctx context.Context) (*fs.Usage, error) { + internxtLimit, err := users.GetLimit(f.cfg) + if err != nil { + return nil, err + } + + internxtUsage, err := users.GetUsage(f.cfg) + if err != nil { + return nil, err + } + + usage := &fs.Usage{ + Used: fs.NewUsageValue(internxtUsage.Drive), + } + + usage.Total = fs.NewUsageValue(internxtLimit.MaxSpaceBytes) + usage.Free = fs.NewUsageValue(*usage.Total - *usage.Used) + + return usage, nil +} + // Open opens a file for streaming func (o *Object) Open(ctx context.Context, options ...fs.OpenOption) (io.ReadCloser, error) { fs.FixRangeOption(options, o.size) @@ -623,6 +673,11 @@ func (o *Object) Update(ctx context.Context, in io.Reader, src fs.ObjectInfo, op } o.remote = o.remote + EMPTY_FILE_EXT } + } else { + if o.f.opt.SimulateEmptyFiles { + // Remove the suffix if we're updating an empty file with actual data + o.remote = strings.TrimSuffix(o.remote, EMPTY_FILE_EXT) + } } // Check if object exists on the server