diff --git a/backend/googlephotos/googlephotos.go b/backend/googlephotos/googlephotos.go index 0ac08be7b..56020cae5 100644 --- a/backend/googlephotos/googlephotos.go +++ b/backend/googlephotos/googlephotos.go @@ -43,6 +43,7 @@ var ( errAlbumDelete = errors.New("google photos API does not implement deleting albums") errRemove = errors.New("google photos API only implements removing files from albums") errOwnAlbums = errors.New("google photos API only allows uploading to albums rclone created") + errReadOnly = errors.New("can't upload files in read only mode") ) const ( @@ -52,19 +53,31 @@ const ( listChunks = 100 // chunk size to read directory listings albumChunks = 50 // chunk size to read album listings minSleep = 10 * time.Millisecond - scopeReadOnly = "https://www.googleapis.com/auth/photoslibrary.readonly" - scopeReadWrite = "https://www.googleapis.com/auth/photoslibrary" - scopeAccess = 2 // position of access scope in list + scopeAppendOnly = "https://www.googleapis.com/auth/photoslibrary.appendonly" + scopeReadOnly = "https://www.googleapis.com/auth/photoslibrary.readonly.appcreateddata" + scopeReadWrite = "https://www.googleapis.com/auth/photoslibrary.edit.appcreateddata" ) var ( + // scopes needed for read write access + scopesReadWrite = []string{ + "openid", + "profile", + scopeAppendOnly, + scopeReadOnly, + scopeReadWrite, + } + + // scopes needed for read only access + scopesReadOnly = []string{ + "openid", + "profile", + scopeReadOnly, + } + // Description of how to auth for this app oauthConfig = &oauthutil.Config{ - Scopes: []string{ - "openid", - "profile", - scopeReadWrite, // this must be at position scopeAccess - }, + Scopes: scopesReadWrite, AuthURL: google.Endpoint.AuthURL, TokenURL: google.Endpoint.TokenURL, ClientID: rcloneClientID, @@ -100,9 +113,9 @@ func init() { case "": // Fill in the scopes if opt.ReadOnly { - oauthConfig.Scopes[scopeAccess] = scopeReadOnly + oauthConfig.Scopes = scopesReadOnly } else { - oauthConfig.Scopes[scopeAccess] = scopeReadWrite + oauthConfig.Scopes = scopesReadWrite } return oauthutil.ConfigOut("warning", &oauthutil.Options{ OAuth2Config: oauthConfig, @@ -1120,6 +1133,9 @@ func (o *Object) Update(ctx context.Context, in io.Reader, src fs.ObjectInfo, op } if !album.IsWriteable { + if o.fs.opt.ReadOnly { + return errReadOnly + } return errOwnAlbums } diff --git a/docs/content/googlephotos.md b/docs/content/googlephotos.md index c3549d158..65db63340 100644 --- a/docs/content/googlephotos.md +++ b/docs/content/googlephotos.md @@ -14,6 +14,11 @@ Google Photos. limitations, so please read the [limitations section](#limitations) carefully to make sure it is suitable for your use. +**NB** From March 31, 2025 rclone can only download photos it +uploaded. This limitation is due to policy changes at Google. You may +need to run `rclone config reconnect remote:` to make rclone work +again after upgrading to rclone v1.70. + ## Configuration The initial setup for google cloud storage involves getting a token from Google Photos @@ -528,6 +533,11 @@ videos or images or formats that Google Photos doesn't understand, rclone will upload the file, then Google Photos will give an error when it is put turned into a media item. +**NB** From March 31, 2025 rclone can only download photos it +uploaded. This limitation is due to policy changes at Google. You may +need to run `rclone config reconnect remote:` to make rclone work +again after upgrading to rclone v1.70. + Note that all media items uploaded to Google Photos through the API are stored in full resolution at "original quality" and **will** count towards your storage quota in your Google Account. The API does