mirror of https://github.com/Xhofe/alist
feat(driver): add Gofile storage driver (#9318)
Add support for Gofile.io cloud storage service with full CRUD operations. Features: - File and folder listing - Upload and download functionality - Create, move, rename, copy, and delete operations - Direct link generation for file access - API token authentication The driver implements all required driver interfaces and follows the existing driver patterns in the codebase.pull/7097/merge
parent
d0026030cb
commit
28a8428559
|
@ -32,6 +32,7 @@ import (
|
||||||
_ "github.com/alist-org/alist/v3/drivers/ftp"
|
_ "github.com/alist-org/alist/v3/drivers/ftp"
|
||||||
_ "github.com/alist-org/alist/v3/drivers/github"
|
_ "github.com/alist-org/alist/v3/drivers/github"
|
||||||
_ "github.com/alist-org/alist/v3/drivers/github_releases"
|
_ "github.com/alist-org/alist/v3/drivers/github_releases"
|
||||||
|
_ "github.com/alist-org/alist/v3/drivers/gofile"
|
||||||
_ "github.com/alist-org/alist/v3/drivers/google_drive"
|
_ "github.com/alist-org/alist/v3/drivers/google_drive"
|
||||||
_ "github.com/alist-org/alist/v3/drivers/google_photo"
|
_ "github.com/alist-org/alist/v3/drivers/google_photo"
|
||||||
_ "github.com/alist-org/alist/v3/drivers/halalcloud"
|
_ "github.com/alist-org/alist/v3/drivers/halalcloud"
|
||||||
|
|
|
@ -0,0 +1,261 @@
|
||||||
|
package gofile
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/alist-org/alist/v3/internal/driver"
|
||||||
|
"github.com/alist-org/alist/v3/internal/errs"
|
||||||
|
"github.com/alist-org/alist/v3/internal/model"
|
||||||
|
"github.com/alist-org/alist/v3/internal/op"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Gofile struct {
|
||||||
|
model.Storage
|
||||||
|
Addition
|
||||||
|
|
||||||
|
accountId string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Gofile) Config() driver.Config {
|
||||||
|
return config
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Gofile) GetAddition() driver.Additional {
|
||||||
|
return &d.Addition
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Gofile) Init(ctx context.Context) error {
|
||||||
|
if d.APIToken == "" {
|
||||||
|
return fmt.Errorf("API token is required")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get account ID
|
||||||
|
accountId, err := d.getAccountId(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to get account ID: %w", err)
|
||||||
|
}
|
||||||
|
d.accountId = accountId
|
||||||
|
|
||||||
|
// Get account info to set root folder if not specified
|
||||||
|
if d.RootFolderID == "" {
|
||||||
|
accountInfo, err := d.getAccountInfo(ctx, accountId)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to get account info: %w", err)
|
||||||
|
}
|
||||||
|
d.RootFolderID = accountInfo.Data.RootFolder
|
||||||
|
}
|
||||||
|
|
||||||
|
// Save driver storage
|
||||||
|
op.MustSaveDriverStorage(d)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Gofile) Drop(ctx context.Context) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Gofile) List(ctx context.Context, dir model.Obj, args model.ListArgs) ([]model.Obj, error) {
|
||||||
|
var folderId string
|
||||||
|
if dir.GetID() == "" {
|
||||||
|
folderId = d.GetRootId()
|
||||||
|
} else {
|
||||||
|
folderId = dir.GetID()
|
||||||
|
}
|
||||||
|
|
||||||
|
endpoint := fmt.Sprintf("/contents/%s", folderId)
|
||||||
|
|
||||||
|
var response ContentsResponse
|
||||||
|
err := d.getJSON(ctx, endpoint, &response)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var objects []model.Obj
|
||||||
|
|
||||||
|
// Process children or contents
|
||||||
|
contents := response.Data.Children
|
||||||
|
if contents == nil {
|
||||||
|
contents = response.Data.Contents
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, content := range contents {
|
||||||
|
objects = append(objects, d.convertContentToObj(content))
|
||||||
|
}
|
||||||
|
|
||||||
|
return objects, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Gofile) Link(ctx context.Context, file model.Obj, args model.LinkArgs) (*model.Link, error) {
|
||||||
|
if file.IsDir() {
|
||||||
|
return nil, errs.NotFile
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a direct link for the file
|
||||||
|
directLink, err := d.createDirectLink(ctx, file.GetID())
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to create direct link: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &model.Link{
|
||||||
|
URL: directLink,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Gofile) MakeDir(ctx context.Context, parentDir model.Obj, dirName string) (model.Obj, error) {
|
||||||
|
var parentId string
|
||||||
|
if parentDir.GetID() == "" {
|
||||||
|
parentId = d.GetRootId()
|
||||||
|
} else {
|
||||||
|
parentId = parentDir.GetID()
|
||||||
|
}
|
||||||
|
|
||||||
|
data := map[string]interface{}{
|
||||||
|
"parentFolderId": parentId,
|
||||||
|
"folderName": dirName,
|
||||||
|
}
|
||||||
|
|
||||||
|
var response CreateFolderResponse
|
||||||
|
err := d.postJSON(ctx, "/contents/createFolder", data, &response)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &model.Object{
|
||||||
|
ID: response.Data.ID,
|
||||||
|
Name: response.Data.Name,
|
||||||
|
IsFolder: true,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Gofile) Move(ctx context.Context, srcObj, dstDir model.Obj) (model.Obj, error) {
|
||||||
|
var dstId string
|
||||||
|
if dstDir.GetID() == "" {
|
||||||
|
dstId = d.GetRootId()
|
||||||
|
} else {
|
||||||
|
dstId = dstDir.GetID()
|
||||||
|
}
|
||||||
|
|
||||||
|
data := map[string]interface{}{
|
||||||
|
"contentsId": srcObj.GetID(),
|
||||||
|
"folderId": dstId,
|
||||||
|
}
|
||||||
|
|
||||||
|
err := d.putJSON(ctx, "/contents/move", data, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return updated object
|
||||||
|
return &model.Object{
|
||||||
|
ID: srcObj.GetID(),
|
||||||
|
Name: srcObj.GetName(),
|
||||||
|
Size: srcObj.GetSize(),
|
||||||
|
Modified: srcObj.ModTime(),
|
||||||
|
IsFolder: srcObj.IsDir(),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Gofile) Rename(ctx context.Context, srcObj model.Obj, newName string) (model.Obj, error) {
|
||||||
|
data := map[string]interface{}{
|
||||||
|
"attribute": "name",
|
||||||
|
"attributeValue": newName,
|
||||||
|
}
|
||||||
|
|
||||||
|
var response UpdateResponse
|
||||||
|
err := d.putJSON(ctx, fmt.Sprintf("/contents/%s/update", srcObj.GetID()), data, &response)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &model.Object{
|
||||||
|
ID: srcObj.GetID(),
|
||||||
|
Name: newName,
|
||||||
|
Size: srcObj.GetSize(),
|
||||||
|
Modified: srcObj.ModTime(),
|
||||||
|
IsFolder: srcObj.IsDir(),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Gofile) Copy(ctx context.Context, srcObj, dstDir model.Obj) (model.Obj, error) {
|
||||||
|
var dstId string
|
||||||
|
if dstDir.GetID() == "" {
|
||||||
|
dstId = d.GetRootId()
|
||||||
|
} else {
|
||||||
|
dstId = dstDir.GetID()
|
||||||
|
}
|
||||||
|
|
||||||
|
data := map[string]interface{}{
|
||||||
|
"contentsId": srcObj.GetID(),
|
||||||
|
"folderId": dstId,
|
||||||
|
}
|
||||||
|
|
||||||
|
var response CopyResponse
|
||||||
|
err := d.postJSON(ctx, "/contents/copy", data, &response)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the new ID from the response
|
||||||
|
newId := srcObj.GetID()
|
||||||
|
if response.Data.CopiedContents != nil {
|
||||||
|
if id, ok := response.Data.CopiedContents[srcObj.GetID()]; ok {
|
||||||
|
newId = id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return &model.Object{
|
||||||
|
ID: newId,
|
||||||
|
Name: srcObj.GetName(),
|
||||||
|
Size: srcObj.GetSize(),
|
||||||
|
Modified: srcObj.ModTime(),
|
||||||
|
IsFolder: srcObj.IsDir(),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Gofile) Remove(ctx context.Context, obj model.Obj) error {
|
||||||
|
data := map[string]interface{}{
|
||||||
|
"contentsId": obj.GetID(),
|
||||||
|
}
|
||||||
|
|
||||||
|
return d.deleteJSON(ctx, "/contents", data)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Gofile) Put(ctx context.Context, dstDir model.Obj, fileStreamer model.FileStreamer, up driver.UpdateProgress) (model.Obj, error) {
|
||||||
|
var folderId string
|
||||||
|
if dstDir.GetID() == "" {
|
||||||
|
folderId = d.GetRootId()
|
||||||
|
} else {
|
||||||
|
folderId = dstDir.GetID()
|
||||||
|
}
|
||||||
|
|
||||||
|
response, err := d.uploadFile(ctx, folderId, fileStreamer, up)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &model.Object{
|
||||||
|
ID: response.Data.FileId,
|
||||||
|
Name: response.Data.FileName,
|
||||||
|
Size: fileStreamer.GetSize(),
|
||||||
|
IsFolder: false,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Gofile) GetArchiveMeta(ctx context.Context, obj model.Obj, args model.ArchiveArgs) (model.ArchiveMeta, error) {
|
||||||
|
return nil, errs.NotImplement
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Gofile) ListArchive(ctx context.Context, obj model.Obj, args model.ArchiveInnerArgs) ([]model.Obj, error) {
|
||||||
|
return nil, errs.NotImplement
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Gofile) Extract(ctx context.Context, obj model.Obj, args model.ArchiveInnerArgs) (*model.Link, error) {
|
||||||
|
return nil, errs.NotImplement
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Gofile) ArchiveDecompress(ctx context.Context, srcObj, dstDir model.Obj, args model.ArchiveDecompressArgs) ([]model.Obj, error) {
|
||||||
|
return nil, errs.NotImplement
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ driver.Driver = (*Gofile)(nil)
|
|
@ -0,0 +1,26 @@
|
||||||
|
package gofile
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/alist-org/alist/v3/internal/driver"
|
||||||
|
"github.com/alist-org/alist/v3/internal/op"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Addition struct {
|
||||||
|
driver.RootID
|
||||||
|
APIToken string `json:"api_token" required:"true" help:"Get your API token from your Gofile profile page"`
|
||||||
|
}
|
||||||
|
|
||||||
|
var config = driver.Config{
|
||||||
|
Name: "Gofile",
|
||||||
|
DefaultRoot: "",
|
||||||
|
LocalSort: false,
|
||||||
|
OnlyProxy: false,
|
||||||
|
NoCache: false,
|
||||||
|
NoUpload: false,
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
op.RegisterDriver(func() driver.Driver {
|
||||||
|
return &Gofile{}
|
||||||
|
})
|
||||||
|
}
|
|
@ -0,0 +1,124 @@
|
||||||
|
package gofile
|
||||||
|
|
||||||
|
import "time"
|
||||||
|
|
||||||
|
type APIResponse struct {
|
||||||
|
Status string `json:"status"`
|
||||||
|
Data interface{} `json:"data"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type AccountResponse struct {
|
||||||
|
Status string `json:"status"`
|
||||||
|
Data struct {
|
||||||
|
ID string `json:"id"`
|
||||||
|
} `json:"data"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type AccountInfoResponse struct {
|
||||||
|
Status string `json:"status"`
|
||||||
|
Data struct {
|
||||||
|
ID string `json:"id"`
|
||||||
|
Type string `json:"type"`
|
||||||
|
Email string `json:"email"`
|
||||||
|
RootFolder string `json:"rootFolder"`
|
||||||
|
} `json:"data"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Content struct {
|
||||||
|
ID string `json:"id"`
|
||||||
|
Type string `json:"type"` // "file" or "folder"
|
||||||
|
Name string `json:"name"`
|
||||||
|
Size int64 `json:"size,omitempty"`
|
||||||
|
CreateTime int64 `json:"createTime"`
|
||||||
|
ModTime int64 `json:"modTime,omitempty"`
|
||||||
|
DirectLink string `json:"directLink,omitempty"`
|
||||||
|
Children map[string]Content `json:"children,omitempty"`
|
||||||
|
ParentFolder string `json:"parentFolder,omitempty"`
|
||||||
|
MD5 string `json:"md5,omitempty"`
|
||||||
|
MimeType string `json:"mimeType,omitempty"`
|
||||||
|
Link string `json:"link,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ContentsResponse struct {
|
||||||
|
Status string `json:"status"`
|
||||||
|
Data struct {
|
||||||
|
IsOwner bool `json:"isOwner"`
|
||||||
|
ID string `json:"id"`
|
||||||
|
Type string `json:"type"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
ParentFolder string `json:"parentFolder"`
|
||||||
|
CreateTime int64 `json:"createTime"`
|
||||||
|
ChildrenList []string `json:"childrenList,omitempty"`
|
||||||
|
Children map[string]Content `json:"children,omitempty"`
|
||||||
|
Contents map[string]Content `json:"contents,omitempty"`
|
||||||
|
Public bool `json:"public,omitempty"`
|
||||||
|
Description string `json:"description,omitempty"`
|
||||||
|
Tags string `json:"tags,omitempty"`
|
||||||
|
Expiry int64 `json:"expiry,omitempty"`
|
||||||
|
} `json:"data"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type UploadResponse struct {
|
||||||
|
Status string `json:"status"`
|
||||||
|
Data struct {
|
||||||
|
DownloadPage string `json:"downloadPage"`
|
||||||
|
Code string `json:"code"`
|
||||||
|
ParentFolder string `json:"parentFolder"`
|
||||||
|
FileId string `json:"fileId"`
|
||||||
|
FileName string `json:"fileName"`
|
||||||
|
GuestToken string `json:"guestToken,omitempty"`
|
||||||
|
} `json:"data"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type DirectLinkResponse struct {
|
||||||
|
Status string `json:"status"`
|
||||||
|
Data struct {
|
||||||
|
DirectLink string `json:"directLink"`
|
||||||
|
ID string `json:"id"`
|
||||||
|
} `json:"data"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type CreateFolderResponse struct {
|
||||||
|
Status string `json:"status"`
|
||||||
|
Data struct {
|
||||||
|
ID string `json:"id"`
|
||||||
|
Type string `json:"type"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
ParentFolder string `json:"parentFolder"`
|
||||||
|
CreateTime int64 `json:"createTime"`
|
||||||
|
} `json:"data"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type CopyResponse struct {
|
||||||
|
Status string `json:"status"`
|
||||||
|
Data struct {
|
||||||
|
CopiedContents map[string]string `json:"copiedContents"` // oldId -> newId mapping
|
||||||
|
} `json:"data"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type UpdateResponse struct {
|
||||||
|
Status string `json:"status"`
|
||||||
|
Data struct {
|
||||||
|
ID string `json:"id"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
} `json:"data"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ErrorResponse struct {
|
||||||
|
Status string `json:"status"`
|
||||||
|
Error struct {
|
||||||
|
Message string `json:"message"`
|
||||||
|
Code string `json:"code"`
|
||||||
|
} `json:"error"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Content) ModifiedTime() time.Time {
|
||||||
|
if c.ModTime > 0 {
|
||||||
|
return time.Unix(c.ModTime, 0)
|
||||||
|
}
|
||||||
|
return time.Unix(c.CreateTime, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Content) IsDir() bool {
|
||||||
|
return c.Type == "folder"
|
||||||
|
}
|
|
@ -0,0 +1,257 @@
|
||||||
|
package gofile
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"mime/multipart"
|
||||||
|
"net/http"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/alist-org/alist/v3/drivers/base"
|
||||||
|
"github.com/alist-org/alist/v3/internal/driver"
|
||||||
|
"github.com/alist-org/alist/v3/internal/model"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
baseAPI = "https://api.gofile.io"
|
||||||
|
uploadAPI = "https://upload.gofile.io"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (d *Gofile) request(ctx context.Context, method, endpoint string, body io.Reader, headers map[string]string) (*http.Response, error) {
|
||||||
|
var url string
|
||||||
|
if strings.HasPrefix(endpoint, "http") {
|
||||||
|
url = endpoint
|
||||||
|
} else {
|
||||||
|
url = baseAPI + endpoint
|
||||||
|
}
|
||||||
|
|
||||||
|
req, err := http.NewRequestWithContext(ctx, method, url, body)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
req.Header.Set("Authorization", "Bearer "+d.APIToken)
|
||||||
|
req.Header.Set("User-Agent", "AList/3.0")
|
||||||
|
|
||||||
|
for k, v := range headers {
|
||||||
|
req.Header.Set(k, v)
|
||||||
|
}
|
||||||
|
|
||||||
|
return base.HttpClient.Do(req)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Gofile) getJSON(ctx context.Context, endpoint string, result interface{}) error {
|
||||||
|
resp, err := d.request(ctx, "GET", endpoint, nil, nil)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
if resp.StatusCode != http.StatusOK {
|
||||||
|
return d.handleError(resp)
|
||||||
|
}
|
||||||
|
|
||||||
|
return json.NewDecoder(resp.Body).Decode(result)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Gofile) postJSON(ctx context.Context, endpoint string, data interface{}, result interface{}) error {
|
||||||
|
jsonData, err := json.Marshal(data)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
headers := map[string]string{
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := d.request(ctx, "POST", endpoint, bytes.NewBuffer(jsonData), headers)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
if resp.StatusCode != http.StatusOK {
|
||||||
|
return d.handleError(resp)
|
||||||
|
}
|
||||||
|
|
||||||
|
if result != nil {
|
||||||
|
return json.NewDecoder(resp.Body).Decode(result)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Gofile) putJSON(ctx context.Context, endpoint string, data interface{}, result interface{}) error {
|
||||||
|
jsonData, err := json.Marshal(data)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
headers := map[string]string{
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := d.request(ctx, "PUT", endpoint, bytes.NewBuffer(jsonData), headers)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
if resp.StatusCode != http.StatusOK {
|
||||||
|
return d.handleError(resp)
|
||||||
|
}
|
||||||
|
|
||||||
|
if result != nil {
|
||||||
|
return json.NewDecoder(resp.Body).Decode(result)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Gofile) deleteJSON(ctx context.Context, endpoint string, data interface{}) error {
|
||||||
|
jsonData, err := json.Marshal(data)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
headers := map[string]string{
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := d.request(ctx, "DELETE", endpoint, bytes.NewBuffer(jsonData), headers)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
if resp.StatusCode != http.StatusOK {
|
||||||
|
return d.handleError(resp)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Gofile) handleError(resp *http.Response) error {
|
||||||
|
body, _ := io.ReadAll(resp.Body)
|
||||||
|
|
||||||
|
var errorResp ErrorResponse
|
||||||
|
if err := json.Unmarshal(body, &errorResp); err == nil {
|
||||||
|
return fmt.Errorf("gofile API error: %s (code: %s)", errorResp.Error.Message, errorResp.Error.Code)
|
||||||
|
}
|
||||||
|
|
||||||
|
return fmt.Errorf("gofile API error: HTTP %d - %s", resp.StatusCode, string(body))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Gofile) uploadFile(ctx context.Context, folderId string, file model.FileStreamer, up driver.UpdateProgress) (*UploadResponse, error) {
|
||||||
|
var body bytes.Buffer
|
||||||
|
writer := multipart.NewWriter(&body)
|
||||||
|
|
||||||
|
if folderId != "" {
|
||||||
|
writer.WriteField("folderId", folderId)
|
||||||
|
}
|
||||||
|
|
||||||
|
part, err := writer.CreateFormFile("file", filepath.Base(file.GetName()))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Copy with progress tracking if available
|
||||||
|
if up != nil {
|
||||||
|
reader := &progressReader{
|
||||||
|
reader: file,
|
||||||
|
total: file.GetSize(),
|
||||||
|
up: up,
|
||||||
|
}
|
||||||
|
_, err = io.Copy(part, reader)
|
||||||
|
} else {
|
||||||
|
_, err = io.Copy(part, file)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
writer.Close()
|
||||||
|
|
||||||
|
headers := map[string]string{
|
||||||
|
"Content-Type": writer.FormDataContentType(),
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := d.request(ctx, "POST", uploadAPI+"/uploadfile", &body, headers)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
if resp.StatusCode != http.StatusOK {
|
||||||
|
return nil, d.handleError(resp)
|
||||||
|
}
|
||||||
|
|
||||||
|
var result UploadResponse
|
||||||
|
err = json.NewDecoder(resp.Body).Decode(&result)
|
||||||
|
return &result, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Gofile) createDirectLink(ctx context.Context, contentId string) (string, error) {
|
||||||
|
data := map[string]interface{}{}
|
||||||
|
|
||||||
|
var result DirectLinkResponse
|
||||||
|
err := d.postJSON(ctx, fmt.Sprintf("/contents/%s/directlinks", contentId), data, &result)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
return result.Data.DirectLink, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Gofile) convertContentToObj(content Content) model.Obj {
|
||||||
|
return &model.ObjThumb{
|
||||||
|
Object: model.Object{
|
||||||
|
ID: content.ID,
|
||||||
|
Name: content.Name,
|
||||||
|
Size: content.Size,
|
||||||
|
Modified: content.ModifiedTime(),
|
||||||
|
IsFolder: content.IsDir(),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Gofile) getAccountId(ctx context.Context) (string, error) {
|
||||||
|
var result AccountResponse
|
||||||
|
err := d.getJSON(ctx, "/accounts/getid", &result)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return result.Data.ID, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Gofile) getAccountInfo(ctx context.Context, accountId string) (*AccountInfoResponse, error) {
|
||||||
|
var result AccountInfoResponse
|
||||||
|
err := d.getJSON(ctx, fmt.Sprintf("/accounts/%s", accountId), &result)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// progressReader wraps an io.Reader to track upload progress
|
||||||
|
type progressReader struct {
|
||||||
|
reader io.Reader
|
||||||
|
total int64
|
||||||
|
read int64
|
||||||
|
up driver.UpdateProgress
|
||||||
|
}
|
||||||
|
|
||||||
|
func (pr *progressReader) Read(p []byte) (n int, err error) {
|
||||||
|
n, err = pr.reader.Read(p)
|
||||||
|
pr.read += int64(n)
|
||||||
|
if pr.up != nil && pr.total > 0 {
|
||||||
|
progress := float64(pr.read) * 100 / float64(pr.total)
|
||||||
|
pr.up(progress)
|
||||||
|
}
|
||||||
|
return n, err
|
||||||
|
}
|
Loading…
Reference in New Issue