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  }