github.com/Files-com/files-sdk-go/v2@v2.1.2/file/client.go (about)

     1  package file
     2  
     3  import (
     4  	"io"
     5  	"io/fs"
     6  	"net/http"
     7  	"net/url"
     8  	"time"
     9  
    10  	"github.com/Files-com/files-sdk-go/v2/downloadurl"
    11  	"github.com/Files-com/files-sdk-go/v2/file/manager"
    12  	"github.com/Files-com/files-sdk-go/v2/folder"
    13  
    14  	files_sdk "github.com/Files-com/files-sdk-go/v2"
    15  	lib "github.com/Files-com/files-sdk-go/v2/lib"
    16  )
    17  
    18  type Client struct {
    19  	files_sdk.Config
    20  }
    21  
    22  func (c *Client) Get(Path string, opts ...files_sdk.RequestResponseOption) (files_sdk.File, error) {
    23  	file := files_sdk.File{}
    24  	path, err := lib.BuildPath("/files/{path}", map[string]string{"path": Path})
    25  	if err != nil {
    26  		return file, err
    27  	}
    28  	data, _, err := files_sdk.Call("GET", c.Config, path, lib.Params{Params: lib.Interface()}, opts...)
    29  	if err != nil {
    30  		return file, err
    31  	}
    32  	if err := file.UnmarshalJSON(*data); err != nil {
    33  		return file, err
    34  	}
    35  
    36  	return file, nil
    37  }
    38  
    39  func Get(Path string, opts ...files_sdk.RequestResponseOption) (files_sdk.File, error) {
    40  	client := Client{}
    41  	return client.Get(Path, opts...)
    42  }
    43  
    44  // File{}.Size and File{}.Mtime are not always up to date. This calls HEAD on File{}.DownloadUri to get the latest info.
    45  // Some Download URLs won't support HEAD. In this case the size is reported as UntrustedSizeValue. The size can be known post download
    46  // using Client{}.DownloadRequestStatus. This applies to the remote mount types FTP, SFTP, and WebDAV.
    47  func (c *Client) FileStats(file files_sdk.File, opts ...files_sdk.RequestResponseOption) (files_sdk.File, error) {
    48  	var err error
    49  	var size int64
    50  	file, err = c.Download(
    51  		files_sdk.FileDownloadParams{File: file},
    52  		append(opts,
    53  			files_sdk.RequestOption(func(req *http.Request) error {
    54  				if req.URL.Host != "s3.amazonaws.com" {
    55  					req.Method = "HEAD"
    56  				}
    57  				return nil
    58  			}),
    59  			files_sdk.ResponseOption(func(response *http.Response) error {
    60  				if response.StatusCode == 422 {
    61  					size = int64(UntrustedSizeValue)
    62  					return nil
    63  				}
    64  				if err := lib.ResponseErrors(response, lib.NonOkError); err != nil {
    65  					return err
    66  				}
    67  				size = response.ContentLength
    68  				if response.Header.Get("Last-Modified") != "" {
    69  					mtime, err := time.Parse(time.RFC1123, response.Header.Get("Last-Modified"))
    70  					if err == nil {
    71  						file.Mtime = &mtime
    72  					}
    73  				}
    74  				return response.Body.Close()
    75  			}),
    76  		)...,
    77  	)
    78  	if err == nil {
    79  		file.Size = size
    80  	}
    81  	return file, err
    82  }
    83  
    84  func (c *Client) DownloadRequestStatus(fileDownloadUrl string, downloadRequestId string, opts ...files_sdk.RequestResponseOption) (files_sdk.ResponseError, error) {
    85  	re := files_sdk.ResponseError{}
    86  	uri, err := url.Parse(fileDownloadUrl)
    87  	if err != nil {
    88  		return re, err
    89  	}
    90  
    91  	uri = uri.JoinPath(downloadRequestId)
    92  
    93  	request, err := http.NewRequest("GET", uri.String(), nil)
    94  	if err != nil {
    95  		return re, err
    96  	}
    97  
    98  	c.SetHeaders(&request.Header)
    99  
   100  	resp, err := files_sdk.WrapRequestOptions(c.Config.GetHttpClient(), request, opts...)
   101  	if err != nil {
   102  		return re, err
   103  	}
   104  	data, err := io.ReadAll(resp.Body)
   105  	if err != nil {
   106  		return re, err
   107  	}
   108  	if lib.IsJSON(resp) {
   109  		err = re.UnmarshalJSON(data)
   110  		if err != nil {
   111  			return re, err
   112  		}
   113  		if re.Type == "" && !re.IsNil() {
   114  			re.Type = "download request status"
   115  		}
   116  	}
   117  	return re, err
   118  }
   119  
   120  func (c *Client) DownloadUri(params files_sdk.FileDownloadParams, opts ...files_sdk.RequestResponseOption) (files_sdk.File, error) {
   121  	var err error
   122  	if params.Path == "" {
   123  		params.Path = params.File.Path
   124  	}
   125  
   126  	if params.File.DownloadUri == "" {
   127  		err = files_sdk.Resource(c.Config, lib.Resource{Method: "GET", Path: "/files/{path}", Params: params, Entity: &params.File}, opts...)
   128  		return params.File, err
   129  	} else {
   130  		url, parseErr := downloadurl.New(params.File.DownloadUri)
   131  		remaining, valid := url.Valid(time.Millisecond * 100)
   132  		if parseErr == nil {
   133  			if !valid {
   134  				err = files_sdk.Resource(c.Config, lib.Resource{Method: "GET", Path: "/files/{path}", Params: params, Entity: &params.File})
   135  				if params.File.DownloadUri == url.URL.String() {
   136  					c.LogPath(params.Path, map[string]interface{}{"message": "URL was expired. Fetched a new URL but it didn't change", "Remaining": remaining, "Time": url.Time})
   137  				} else {
   138  					c.LogPath(params.Path, map[string]interface{}{"message": "URL was expired. Fetched a new URL", "Remaining": remaining, "Time": url.Time})
   139  				}
   140  			}
   141  		}
   142  	}
   143  
   144  	return params.File, err
   145  }
   146  
   147  func (c *Client) Download(params files_sdk.FileDownloadParams, opts ...files_sdk.RequestResponseOption) (files_sdk.File, error) {
   148  	if params.Path == "" {
   149  		params.Path = params.File.Path
   150  	}
   151  	var err error
   152  
   153  	params.File, err = c.DownloadUri(params, files_sdk.WithContext(files_sdk.ContextOption(opts)))
   154  	if err != nil {
   155  		return params.File, err
   156  	}
   157  	request, err := http.NewRequest("GET", params.File.DownloadUri, nil)
   158  	if err != nil {
   159  		return params.File, err
   160  	}
   161  
   162  	c.SetHeaders(&request.Header)
   163  
   164  	_, err = files_sdk.WrapRequestOptions(c.Config.GetHttpClient(), request, opts...)
   165  
   166  	return params.File, err
   167  }
   168  
   169  func Download(params files_sdk.FileDownloadParams, opts ...files_sdk.RequestResponseOption) (files_sdk.File, error) {
   170  	client := Client{}
   171  	return client.Download(params, opts...)
   172  }
   173  
   174  func (c *Client) Create(params files_sdk.FileCreateParams, opts ...files_sdk.RequestResponseOption) (file files_sdk.File, err error) {
   175  	err = files_sdk.Resource(c.Config, lib.Resource{Method: "POST", Path: "/files/{path}", Params: params, Entity: &file}, opts...)
   176  	return
   177  }
   178  
   179  func Create(params files_sdk.FileCreateParams, opts ...files_sdk.RequestResponseOption) (file files_sdk.File, err error) {
   180  	return (&Client{}).Create(params, opts...)
   181  }
   182  
   183  func (c *Client) Update(params files_sdk.FileUpdateParams, opts ...files_sdk.RequestResponseOption) (file files_sdk.File, err error) {
   184  	err = files_sdk.Resource(c.Config, lib.Resource{Method: "PATCH", Path: "/files/{path}", Params: params, Entity: &file}, opts...)
   185  	return
   186  }
   187  
   188  func Update(params files_sdk.FileUpdateParams, opts ...files_sdk.RequestResponseOption) (file files_sdk.File, err error) {
   189  	return (&Client{}).Update(params, opts...)
   190  }
   191  
   192  func (c *Client) UpdateWithMap(params map[string]interface{}, opts ...files_sdk.RequestResponseOption) (file files_sdk.File, err error) {
   193  	err = files_sdk.Resource(c.Config, lib.Resource{Method: "PATCH", Path: "/files/{path}", Params: params, Entity: &file}, opts...)
   194  	return
   195  }
   196  
   197  func UpdateWithMap(params map[string]interface{}, opts ...files_sdk.RequestResponseOption) (file files_sdk.File, err error) {
   198  	return (&Client{}).UpdateWithMap(params, opts...)
   199  }
   200  
   201  func (c *Client) Delete(params files_sdk.FileDeleteParams, opts ...files_sdk.RequestResponseOption) (err error) {
   202  	err = files_sdk.Resource(c.Config, lib.Resource{Method: "DELETE", Path: "/files/{path}", Params: params, Entity: nil}, opts...)
   203  	return
   204  }
   205  
   206  func Delete(params files_sdk.FileDeleteParams, opts ...files_sdk.RequestResponseOption) (err error) {
   207  	return (&Client{}).Delete(params, opts...)
   208  }
   209  
   210  func (c *Client) Find(params files_sdk.FileFindParams, opts ...files_sdk.RequestResponseOption) (file files_sdk.File, err error) {
   211  	err = files_sdk.Resource(c.Config, lib.Resource{Method: "GET", Path: "/file_actions/metadata/{path}", Params: params, Entity: &file}, opts...)
   212  	return
   213  }
   214  
   215  func Find(params files_sdk.FileFindParams, opts ...files_sdk.RequestResponseOption) (file files_sdk.File, err error) {
   216  	return (&Client{}).Find(params, opts...)
   217  }
   218  
   219  func (c *Client) Copy(params files_sdk.FileCopyParams, opts ...files_sdk.RequestResponseOption) (fileAction files_sdk.FileAction, err error) {
   220  	err = files_sdk.Resource(c.Config, lib.Resource{Method: "POST", Path: "/file_actions/copy/{path}", Params: params, Entity: &fileAction}, opts...)
   221  	return
   222  }
   223  
   224  func Copy(params files_sdk.FileCopyParams, opts ...files_sdk.RequestResponseOption) (fileAction files_sdk.FileAction, err error) {
   225  	return (&Client{}).Copy(params, opts...)
   226  }
   227  
   228  func (c *Client) Move(params files_sdk.FileMoveParams, opts ...files_sdk.RequestResponseOption) (fileAction files_sdk.FileAction, err error) {
   229  	err = files_sdk.Resource(c.Config, lib.Resource{Method: "POST", Path: "/file_actions/move/{path}", Params: params, Entity: &fileAction}, opts...)
   230  	return
   231  }
   232  
   233  func Move(params files_sdk.FileMoveParams, opts ...files_sdk.RequestResponseOption) (fileAction files_sdk.FileAction, err error) {
   234  	return (&Client{}).Move(params, opts...)
   235  }
   236  
   237  func (c *Client) BeginUpload(params files_sdk.FileBeginUploadParams, opts ...files_sdk.RequestResponseOption) (fileUploadPartCollection files_sdk.FileUploadPartCollection, err error) {
   238  	err = files_sdk.Resource(c.Config, lib.Resource{Method: "POST", Path: "/file_actions/begin_upload/{path}", Params: params, Entity: &fileUploadPartCollection}, opts...)
   239  	return
   240  }
   241  
   242  func BeginUpload(params files_sdk.FileBeginUploadParams, opts ...files_sdk.RequestResponseOption) (fileUploadPartCollection files_sdk.FileUploadPartCollection, err error) {
   243  	return (&Client{}).BeginUpload(params, opts...)
   244  }
   245  
   246  type Iter struct {
   247  	*folder.Iter
   248  }
   249  
   250  var _ files_sdk.ResourceIterator = Iter{}
   251  var _ files_sdk.ResourceLoader = Iter{}
   252  
   253  func (i Iter) LoadResource(identifier interface{}, opts ...files_sdk.RequestResponseOption) (interface{}, error) {
   254  	params := files_sdk.FileFindParams{}
   255  	if path, ok := identifier.(string); ok {
   256  		params.Path = path
   257  	}
   258  
   259  	return (&Client{Config: i.Config}).Find(params, opts...)
   260  }
   261  
   262  func (i Iter) Iterate(identifier interface{}, opts ...files_sdk.RequestResponseOption) (files_sdk.IterI, error) {
   263  	it, err := i.Iter.Iterate(identifier, opts...)
   264  	return Iter{Iter: it.(*folder.Iter)}, err
   265  }
   266  
   267  func (c *Client) ListFor(params files_sdk.FolderListForParams, opts ...files_sdk.RequestResponseOption) (Iter, error) {
   268  	it, err := (&folder.Client{Config: c.Config}).ListFor(params, opts...)
   269  	return Iter{Iter: it}, err
   270  }
   271  
   272  type RecursiveItem struct {
   273  	files_sdk.File
   274  	error `json:"error"`
   275  }
   276  
   277  func (r RecursiveItem) Err() error {
   278  	return r.error
   279  }
   280  
   281  func (c *Client) ListForRecursive(params files_sdk.FolderListForParams, opts ...files_sdk.RequestResponseOption) (files_sdk.TypedIterI[RecursiveItem], error) {
   282  	if params.ConcurrencyManager == nil {
   283  		params.ConcurrencyManager = manager.Default().FilePartsManager
   284  	}
   285  	// Find the path first for recursive operations
   286  	fsi := (&FS{}).Init(c.Config, true).WithContext(files_sdk.ContextOption(opts)).(*FS)
   287  	fStat, err := fs.Stat(fsi, params.Path)
   288  	if err != nil {
   289  		return nil, err
   290  	}
   291  	// The path is a directory and the literal strings do not match use our returned path
   292  	if fStat.Sys().(files_sdk.File).Type == "directory" && fStat.Sys().(files_sdk.File).Path != params.Path {
   293  		params.Path = fStat.Sys().(files_sdk.File).Path
   294  	}
   295  
   296  	return (&lib.Walk[RecursiveItem]{
   297  		FS:                 fsi,
   298  		Root:               lib.UrlJoinNoEscape(params.Path),
   299  		ConcurrencyManager: params.ConcurrencyManager,
   300  		ListDirectories:    true,
   301  		WalkFile: func(d fs.DirEntry, path string, err error) (RecursiveItem, error) {
   302  			info, infoErr := d.Info()
   303  			if infoErr == nil {
   304  				return RecursiveItem{info.Sys().(files_sdk.File), err}, nil
   305  			} else if err != nil {
   306  				return RecursiveItem{}, err
   307  			} else {
   308  				return RecursiveItem{}, infoErr
   309  			}
   310  		},
   311  	}).Walk(files_sdk.ContextOption(opts)), nil
   312  }