2022-10-07 12:36:56 +00:00
|
|
|
package google_photo
|
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
|
|
|
"fmt"
|
|
|
|
"net/http"
|
|
|
|
"strconv"
|
|
|
|
"strings"
|
|
|
|
|
|
|
|
"github.com/alist-org/alist/v3/drivers/base"
|
|
|
|
"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/pkg/utils"
|
|
|
|
"github.com/go-resty/resty/v2"
|
|
|
|
)
|
|
|
|
|
|
|
|
type GooglePhoto struct {
|
|
|
|
model.Storage
|
|
|
|
Addition
|
|
|
|
AccessToken string
|
|
|
|
}
|
|
|
|
|
|
|
|
func (d *GooglePhoto) Config() driver.Config {
|
|
|
|
return config
|
|
|
|
}
|
|
|
|
|
|
|
|
func (d *GooglePhoto) GetAddition() driver.Additional {
|
2022-12-13 10:03:30 +00:00
|
|
|
return &d.Addition
|
2022-10-07 12:36:56 +00:00
|
|
|
}
|
|
|
|
|
2022-12-13 10:03:30 +00:00
|
|
|
func (d *GooglePhoto) Init(ctx context.Context) error {
|
2022-10-07 12:36:56 +00:00
|
|
|
return d.refreshToken()
|
|
|
|
}
|
|
|
|
|
|
|
|
func (d *GooglePhoto) Drop(ctx context.Context) error {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (d *GooglePhoto) List(ctx context.Context, dir model.Obj, args model.ListArgs) ([]model.Obj, error) {
|
2022-10-18 07:19:05 +00:00
|
|
|
files, err := d.getFiles(dir.GetID())
|
2022-10-07 12:36:56 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
return utils.SliceConvert(files, func(src MediaItem) (model.Obj, error) {
|
|
|
|
return fileToObj(src), nil
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
func (d *GooglePhoto) Link(ctx context.Context, file model.Obj, args model.LinkArgs) (*model.Link, error) {
|
2022-10-18 07:19:05 +00:00
|
|
|
f, err := d.getMedia(file.GetID())
|
2022-10-07 12:36:56 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
if strings.Contains(f.MimeType, "image/") {
|
|
|
|
return &model.Link{
|
|
|
|
URL: f.BaseURL + "=d",
|
|
|
|
}, nil
|
|
|
|
} else if strings.Contains(f.MimeType, "video/") {
|
2024-01-19 05:02:05 +00:00
|
|
|
var width, height int
|
|
|
|
|
|
|
|
fmt.Sscanf(f.MediaMetadata.Width, "%d", &width)
|
|
|
|
fmt.Sscanf(f.MediaMetadata.Height, "%d", &height)
|
|
|
|
|
|
|
|
switch {
|
|
|
|
// 1080P
|
|
|
|
case width == 1920 && height == 1080:
|
|
|
|
return &model.Link{
|
|
|
|
URL: f.BaseURL + "=m37",
|
|
|
|
}, nil
|
|
|
|
// 720P
|
|
|
|
case width == 1280 && height == 720:
|
|
|
|
return &model.Link{
|
|
|
|
URL: f.BaseURL + "=m22",
|
|
|
|
}, nil
|
|
|
|
// 360P
|
|
|
|
case width == 640 && height == 360:
|
|
|
|
return &model.Link{
|
|
|
|
URL: f.BaseURL + "=m18",
|
|
|
|
}, nil
|
|
|
|
default:
|
|
|
|
return &model.Link{
|
|
|
|
URL: f.BaseURL + "=dv",
|
|
|
|
}, nil
|
|
|
|
}
|
|
|
|
|
2022-10-07 12:36:56 +00:00
|
|
|
}
|
|
|
|
return &model.Link{}, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (d *GooglePhoto) MakeDir(ctx context.Context, parentDir model.Obj, dirName string) error {
|
|
|
|
return errs.NotSupport
|
|
|
|
}
|
|
|
|
|
|
|
|
func (d *GooglePhoto) Move(ctx context.Context, srcObj, dstDir model.Obj) error {
|
|
|
|
return errs.NotSupport
|
|
|
|
}
|
|
|
|
|
|
|
|
func (d *GooglePhoto) Rename(ctx context.Context, srcObj model.Obj, newName string) error {
|
|
|
|
return errs.NotSupport
|
|
|
|
}
|
|
|
|
|
|
|
|
func (d *GooglePhoto) Copy(ctx context.Context, srcObj, dstDir model.Obj) error {
|
|
|
|
return errs.NotSupport
|
|
|
|
}
|
|
|
|
|
|
|
|
func (d *GooglePhoto) Remove(ctx context.Context, obj model.Obj) error {
|
|
|
|
return errs.NotSupport
|
|
|
|
}
|
|
|
|
|
|
|
|
func (d *GooglePhoto) Put(ctx context.Context, dstDir model.Obj, stream model.FileStreamer, up driver.UpdateProgress) error {
|
|
|
|
var e Error
|
|
|
|
// Create resumable upload url
|
|
|
|
postHeaders := map[string]string{
|
|
|
|
"Authorization": "Bearer " + d.AccessToken,
|
|
|
|
"Content-type": "application/octet-stream",
|
|
|
|
"X-Goog-Upload-Command": "start",
|
|
|
|
"X-Goog-Upload-Content-Type": stream.GetMimetype(),
|
|
|
|
"X-Goog-Upload-Protocol": "resumable",
|
|
|
|
"X-Goog-Upload-Raw-Size": strconv.FormatInt(stream.GetSize(), 10),
|
|
|
|
}
|
|
|
|
url := "https://photoslibrary.googleapis.com/v1/uploads"
|
|
|
|
res, err := base.NoRedirectClient.R().SetHeaders(postHeaders).
|
|
|
|
SetError(&e).
|
|
|
|
Post(url)
|
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
if e.Error.Code != 0 {
|
|
|
|
if e.Error.Code == 401 {
|
|
|
|
err = d.refreshToken()
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
return d.Put(ctx, dstDir, stream, up)
|
|
|
|
}
|
|
|
|
return fmt.Errorf("%s: %v", e.Error.Message, e.Error.Errors)
|
|
|
|
}
|
|
|
|
|
|
|
|
//Upload to the Google Photo
|
|
|
|
postUrl := res.Header().Get("X-Goog-Upload-URL")
|
|
|
|
//chunkSize := res.Header().Get("X-Goog-Upload-Chunk-Granularity")
|
|
|
|
postHeaders = map[string]string{
|
|
|
|
"X-Goog-Upload-Command": "upload, finalize",
|
|
|
|
"X-Goog-Upload-Offset": "0",
|
|
|
|
}
|
|
|
|
|
|
|
|
resp, err := d.request(postUrl, http.MethodPost, func(req *resty.Request) {
|
2023-08-27 13:14:23 +00:00
|
|
|
req.SetBody(stream).SetContext(ctx)
|
2022-10-07 12:36:56 +00:00
|
|
|
}, nil, postHeaders)
|
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
//Create MediaItem
|
|
|
|
createItemUrl := "https://photoslibrary.googleapis.com/v1/mediaItems:batchCreate"
|
|
|
|
|
|
|
|
postHeaders = map[string]string{
|
|
|
|
"X-Goog-Upload-Command": "upload, finalize",
|
|
|
|
"X-Goog-Upload-Offset": "0",
|
|
|
|
}
|
|
|
|
|
|
|
|
data := base.Json{
|
|
|
|
"newMediaItems": []base.Json{
|
|
|
|
{
|
|
|
|
"description": "item-description",
|
|
|
|
"simpleMediaItem": base.Json{
|
|
|
|
"fileName": stream.GetName(),
|
|
|
|
"uploadToken": string(resp),
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
_, err = d.request(createItemUrl, http.MethodPost, func(req *resty.Request) {
|
|
|
|
req.SetBody(data)
|
|
|
|
}, nil, postHeaders)
|
|
|
|
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
var _ driver.Driver = (*GooglePhoto)(nil)
|