github.com/artpar/rclone@v1.67.3/backend/pikpak/helper.go (about) 1 package pikpak 2 3 import ( 4 "bytes" 5 "context" 6 "crypto/sha1" 7 "encoding/hex" 8 "errors" 9 "fmt" 10 "io" 11 "net/http" 12 "os" 13 14 "github.com/artpar/rclone/backend/pikpak/api" 15 "github.com/artpar/rclone/lib/rest" 16 ) 17 18 // Globals 19 const ( 20 cachePrefix = "rclone-pikpak-sha1sum-" 21 ) 22 23 // requestDecompress requests decompress of compressed files 24 func (f *Fs) requestDecompress(ctx context.Context, file *api.File, password string) (info *api.DecompressResult, err error) { 25 req := &api.RequestDecompress{ 26 Gcid: file.Hash, 27 Password: password, 28 FileID: file.ID, 29 Files: []*api.FileInArchive{}, 30 DefaultParent: true, 31 } 32 opts := rest.Opts{ 33 Method: "POST", 34 Path: "/decompress/v1/decompress", 35 } 36 var resp *http.Response 37 err = f.pacer.Call(func() (bool, error) { 38 resp, err = f.rst.CallJSON(ctx, &opts, &req, &info) 39 return f.shouldRetry(ctx, resp, err) 40 }) 41 return 42 } 43 44 // getUserInfo gets UserInfo from API 45 func (f *Fs) getUserInfo(ctx context.Context) (info *api.User, err error) { 46 opts := rest.Opts{ 47 Method: "GET", 48 RootURL: "https://user.mypikpak.com/v1/user/me", 49 } 50 var resp *http.Response 51 err = f.pacer.Call(func() (bool, error) { 52 resp, err = f.rst.CallJSON(ctx, &opts, nil, &info) 53 return f.shouldRetry(ctx, resp, err) 54 }) 55 if err != nil { 56 return nil, fmt.Errorf("failed to get userinfo: %w", err) 57 } 58 return 59 } 60 61 // getVIPInfo gets VIPInfo from API 62 func (f *Fs) getVIPInfo(ctx context.Context) (info *api.VIP, err error) { 63 opts := rest.Opts{ 64 Method: "GET", 65 RootURL: "https://api-drive.mypikpak.com/drive/v1/privilege/vip", 66 } 67 var resp *http.Response 68 err = f.pacer.Call(func() (bool, error) { 69 resp, err = f.rst.CallJSON(ctx, &opts, nil, &info) 70 return f.shouldRetry(ctx, resp, err) 71 }) 72 if err != nil { 73 return nil, fmt.Errorf("failed to get vip info: %w", err) 74 } 75 return 76 } 77 78 // requestBatchAction requests batch actions to API 79 // 80 // action can be one of batch{Copy,Delete,Trash,Untrash} 81 func (f *Fs) requestBatchAction(ctx context.Context, action string, req *api.RequestBatch) (err error) { 82 opts := rest.Opts{ 83 Method: "POST", 84 Path: "/drive/v1/files:" + action, 85 NoResponse: true, // Only returns `{"task_id":""} 86 } 87 var resp *http.Response 88 err = f.pacer.Call(func() (bool, error) { 89 resp, err = f.rst.CallJSON(ctx, &opts, &req, nil) 90 return f.shouldRetry(ctx, resp, err) 91 }) 92 if err != nil { 93 return fmt.Errorf("batch action %q failed: %w", action, err) 94 } 95 return nil 96 } 97 98 // requestNewTask requests a new api.NewTask and returns api.Task 99 func (f *Fs) requestNewTask(ctx context.Context, req *api.RequestNewTask) (info *api.Task, err error) { 100 opts := rest.Opts{ 101 Method: "POST", 102 Path: "/drive/v1/files", 103 } 104 var newTask api.NewTask 105 var resp *http.Response 106 err = f.pacer.Call(func() (bool, error) { 107 resp, err = f.rst.CallJSON(ctx, &opts, &req, &newTask) 108 return f.shouldRetry(ctx, resp, err) 109 }) 110 if err != nil { 111 return nil, err 112 } 113 return newTask.Task, nil 114 } 115 116 // requestNewFile requests a new api.NewFile and returns api.File 117 func (f *Fs) requestNewFile(ctx context.Context, req *api.RequestNewFile) (info *api.NewFile, err error) { 118 opts := rest.Opts{ 119 Method: "POST", 120 Path: "/drive/v1/files", 121 } 122 var resp *http.Response 123 err = f.pacer.Call(func() (bool, error) { 124 resp, err = f.rst.CallJSON(ctx, &opts, &req, &info) 125 return f.shouldRetry(ctx, resp, err) 126 }) 127 return 128 } 129 130 // getFile gets api.File from API for the ID passed 131 // and returns rich information containing additional fields below 132 // * web_content_link 133 // * thumbnail_link 134 // * links 135 // * medias 136 func (f *Fs) getFile(ctx context.Context, ID string) (info *api.File, err error) { 137 opts := rest.Opts{ 138 Method: "GET", 139 Path: "/drive/v1/files/" + ID, 140 } 141 var resp *http.Response 142 err = f.pacer.Call(func() (bool, error) { 143 resp, err = f.rst.CallJSON(ctx, &opts, nil, &info) 144 if err == nil && info.Phase != api.PhaseTypeComplete { 145 // could be pending right after file is created/uploaded. 146 return true, errors.New("not PHASE_TYPE_COMPLETE") 147 } 148 return f.shouldRetry(ctx, resp, err) 149 }) 150 return 151 } 152 153 // patchFile updates attributes of the file by ID 154 // 155 // currently known patchable fields are 156 // * name 157 func (f *Fs) patchFile(ctx context.Context, ID string, req *api.File) (info *api.File, err error) { 158 opts := rest.Opts{ 159 Method: "PATCH", 160 Path: "/drive/v1/files/" + ID, 161 } 162 var resp *http.Response 163 err = f.pacer.Call(func() (bool, error) { 164 resp, err = f.rst.CallJSON(ctx, &opts, &req, &info) 165 return f.shouldRetry(ctx, resp, err) 166 }) 167 return 168 } 169 170 // getAbout gets drive#quota information from server 171 func (f *Fs) getAbout(ctx context.Context) (info *api.About, err error) { 172 opts := rest.Opts{ 173 Method: "GET", 174 Path: "/drive/v1/about", 175 } 176 var resp *http.Response 177 err = f.pacer.Call(func() (bool, error) { 178 resp, err = f.rst.CallJSON(ctx, &opts, nil, &info) 179 return f.shouldRetry(ctx, resp, err) 180 }) 181 return 182 } 183 184 // requestShare returns information about sharable links 185 func (f *Fs) requestShare(ctx context.Context, req *api.RequestShare) (info *api.Share, err error) { 186 opts := rest.Opts{ 187 Method: "POST", 188 Path: "/drive/v1/share", 189 } 190 var resp *http.Response 191 err = f.pacer.Call(func() (bool, error) { 192 resp, err = f.rst.CallJSON(ctx, &opts, &req, &info) 193 return f.shouldRetry(ctx, resp, err) 194 }) 195 return 196 } 197 198 // Read the sha1 of in returning a reader which will read the same contents 199 // 200 // The cleanup function should be called when out is finished with 201 // regardless of whether this function returned an error or not. 202 func readSHA1(in io.Reader, size, threshold int64) (sha1sum string, out io.Reader, cleanup func(), err error) { 203 // we need an SHA1 204 hash := sha1.New() 205 // use the teeReader to write to the local file AND calculate the SHA1 while doing so 206 teeReader := io.TeeReader(in, hash) 207 208 // nothing to clean up by default 209 cleanup = func() {} 210 211 // don't cache small files on disk to reduce wear of the disk 212 if size > threshold { 213 var tempFile *os.File 214 215 // create the cache file 216 tempFile, err = os.CreateTemp("", cachePrefix) 217 if err != nil { 218 return 219 } 220 221 _ = os.Remove(tempFile.Name()) // Delete the file - may not work on Windows 222 223 // clean up the file after we are done downloading 224 cleanup = func() { 225 // the file should normally already be close, but just to make sure 226 _ = tempFile.Close() 227 _ = os.Remove(tempFile.Name()) // delete the cache file after we are done - may be deleted already 228 } 229 230 // copy the ENTIRE file to disc and calculate the SHA1 in the process 231 if _, err = io.Copy(tempFile, teeReader); err != nil { 232 return 233 } 234 // jump to the start of the local file so we can pass it along 235 if _, err = tempFile.Seek(0, 0); err != nil { 236 return 237 } 238 239 // replace the already read source with a reader of our cached file 240 out = tempFile 241 } else { 242 // that's a small file, just read it into memory 243 var inData []byte 244 inData, err = io.ReadAll(teeReader) 245 if err != nil { 246 return 247 } 248 249 // set the reader to our read memory block 250 out = bytes.NewReader(inData) 251 } 252 return hex.EncodeToString(hash.Sum(nil)), out, cleanup, nil 253 }