package teldrive import ( "context" "fmt" "math" "net/http" "net/url" "strings" "github.com/OpenListTeam/OpenList/v4/drivers/base" "github.com/OpenListTeam/OpenList/v4/internal/driver" "github.com/OpenListTeam/OpenList/v4/internal/errs" "github.com/OpenListTeam/OpenList/v4/internal/model" "github.com/OpenListTeam/OpenList/v4/internal/op" "github.com/OpenListTeam/OpenList/v4/pkg/utils" "github.com/go-resty/resty/v2" "github.com/google/uuid" ) type Teldrive struct { model.Storage Addition } func (d *Teldrive) Config() driver.Config { return config } func (d *Teldrive) GetAddition() driver.Additional { return &d.Addition } func (d *Teldrive) Init(ctx context.Context) error { d.Address = strings.TrimSuffix(d.Address, "/") if d.Cookie == "" || !strings.HasPrefix(d.Cookie, "access_token=") { return fmt.Errorf("cookie must start with 'access_token='") } if d.UploadConcurrency == 0 { d.UploadConcurrency = 4 } if d.ChunkSize == 0 { d.ChunkSize = 10 } op.MustSaveDriverStorage(d) return nil } func (d *Teldrive) Drop(ctx context.Context) error { return nil } func (d *Teldrive) List(ctx context.Context, dir model.Obj, args model.ListArgs) ([]model.Obj, error) { var listResp ListResp err := d.request(http.MethodGet, "/api/files", func(req *resty.Request) { req.SetQueryParams(map[string]string{ "path": dir.GetPath(), "limit": "1000", // overide default 500, TODO pagination }) }, &listResp) if err != nil { return nil, err } return utils.SliceConvert(listResp.Items, func(src Object) (model.Obj, error) { return &model.Object{ ID: src.ID, Name: src.Name, Size: func() int64 { if src.Type == "folder" { return 0 } return src.Size }(), IsFolder: src.Type == "folder", Modified: src.UpdatedAt, }, nil }) } func (d *Teldrive) Link(ctx context.Context, file model.Obj, args model.LinkArgs) (*model.Link, error) { if d.UseShareLink { shareObj, err := d.getShareFileById(file.GetID()) if err != nil || shareObj == nil { if err := d.createShareFile(file.GetID()); err != nil { return nil, err } shareObj, err = d.getShareFileById(file.GetID()) if err != nil { return nil, err } } return &model.Link{ URL: d.Address + "/api/shares/" + url.PathEscape(shareObj.Id) + "/files/" + url.PathEscape(file.GetID()) + "/" + url.PathEscape(file.GetName()), }, nil } return &model.Link{ URL: d.Address + "/api/files/" + url.PathEscape(file.GetID()) + "/" + url.PathEscape(file.GetName()), Header: http.Header{ "Cookie": {d.Cookie}, }, }, nil } func (d *Teldrive) MakeDir(ctx context.Context, parentDir model.Obj, dirName string) error { return d.request(http.MethodPost, "/api/files/mkdir", func(req *resty.Request) { req.SetBody(map[string]interface{}{ "path": parentDir.GetPath() + "/" + dirName, }) }, nil) } func (d *Teldrive) Move(ctx context.Context, srcObj, dstDir model.Obj) error { body := base.Json{ "ids": []string{srcObj.GetID()}, "destinationParent": dstDir.GetID(), } return d.request(http.MethodPost, "/api/files/move", func(req *resty.Request) { req.SetBody(body) }, nil) } func (d *Teldrive) Rename(ctx context.Context, srcObj model.Obj, newName string) error { body := base.Json{ "name": newName, } return d.request(http.MethodPatch, "/api/files/{id}", func(req *resty.Request) { req.SetPathParam("id", srcObj.GetID()) req.SetBody(body) }, nil) } func (d *Teldrive) Copy(ctx context.Context, srcObj, dstDir model.Obj) error { copyConcurrentLimit := 4 copyManager := NewCopyManager(ctx, copyConcurrentLimit, d) copyManager.startWorkers() copyManager.G.Go(func() error { defer close(copyManager.TaskChan) return copyManager.generateTasks(ctx, srcObj, dstDir) }) return copyManager.G.Wait() } func (d *Teldrive) Remove(ctx context.Context, obj model.Obj) error { body := base.Json{ "ids": []string{obj.GetID()}, } return d.request(http.MethodPost, "/api/files/delete", func(req *resty.Request) { req.SetBody(body) }, nil) } func (d *Teldrive) Put(ctx context.Context, dstDir model.Obj, file model.FileStreamer, up driver.UpdateProgress) error { fileId := uuid.New().String() chunkSizeInMB := d.ChunkSize chunkSize := chunkSizeInMB * 1024 * 1024 // Convert MB to bytes totalSize := file.GetSize() totalParts := int(math.Ceil(float64(totalSize) / float64(chunkSize))) maxRetried := 3 // delete the upload task when finished or failed defer func() { _ = d.request(http.MethodDelete, "/api/uploads/{id}", func(req *resty.Request) { req.SetPathParam("id", fileId) }, nil) }() if obj, err := d.getFile(dstDir.GetPath(), file.GetName(), file.IsDir()); err == nil { if err = d.Remove(ctx, obj); err != nil { return err } } // start the upload process if err := d.request(http.MethodGet, "/api/uploads/fileId", func(req *resty.Request) { req.SetPathParam("id", fileId) }, nil); err != nil { return err } if totalSize == 0 { return d.touch(file.GetName(), dstDir.GetPath()) } if totalParts <= 1 { return d.doSingleUpload(ctx, dstDir, file, up, totalParts, chunkSize, fileId) } return d.doMultiUpload(ctx, dstDir, file, up, maxRetried, totalParts, chunkSize, fileId) } func (d *Teldrive) GetArchiveMeta(ctx context.Context, obj model.Obj, args model.ArchiveArgs) (model.ArchiveMeta, error) { // TODO get archive file meta-info, return errs.NotImplement to use an internal archive tool, optional return nil, errs.NotImplement } func (d *Teldrive) ListArchive(ctx context.Context, obj model.Obj, args model.ArchiveInnerArgs) ([]model.Obj, error) { // TODO list args.InnerPath in the archive obj, return errs.NotImplement to use an internal archive tool, optional return nil, errs.NotImplement } func (d *Teldrive) Extract(ctx context.Context, obj model.Obj, args model.ArchiveInnerArgs) (*model.Link, error) { // TODO return link of file args.InnerPath in the archive obj, return errs.NotImplement to use an internal archive tool, optional return nil, errs.NotImplement } func (d *Teldrive) ArchiveDecompress(ctx context.Context, srcObj, dstDir model.Obj, args model.ArchiveDecompressArgs) ([]model.Obj, error) { // TODO extract args.InnerPath path in the archive srcObj to the dstDir location, optional // a folder with the same name as the archive file needs to be created to store the extracted results if args.PutIntoNewDir // return errs.NotImplement to use an internal archive tool return nil, errs.NotImplement } //func (d *Teldrive) Other(ctx context.Context, args model.OtherArgs) (interface{}, error) { // return nil, errs.NotSupport //} var _ driver.Driver = (*Teldrive)(nil)