3535import java .util .Optional ;
3636import java .util .UUID ;
3737import javax .annotation .Nullable ;
38+
3839import org .datatransferproject .api .launcher .Monitor ;
40+ import org .datatransferproject .datatransfer .google .common .FailedToListAlbumsException ;
3941import org .datatransferproject .datatransfer .google .common .GoogleCredentialFactory ;
4042import org .datatransferproject .datatransfer .google .common .GoogleErrorLogger ;
4143import org .datatransferproject .datatransfer .google .mediaModels .AlbumListResponse ;
4446import org .datatransferproject .datatransfer .google .mediaModels .MediaItemSearchResponse ;
4547import org .datatransferproject .datatransfer .google .photos .GooglePhotosInterface ;
4648import org .datatransferproject .spi .cloud .storage .JobStore ;
47- import org .datatransferproject .spi .cloud .storage .TemporaryPerJobDataStore ;
4849import org .datatransferproject .spi .transfer .idempotentexecutor .IdempotentImportExecutor ;
4950import org .datatransferproject .spi .transfer .provider .ExportResult ;
5051import org .datatransferproject .spi .transfer .provider .ExportResult .ResultType ;
@@ -143,7 +144,7 @@ private static String createCacheKey() {
143144 @ Override
144145 public ExportResult <MediaContainerResource > export (
145146 UUID jobId , TokensAndUrlAuthData authData , Optional <ExportInformation > exportInformation )
146- throws IOException , InvalidTokenException , PermissionDeniedException , UploadErrorException {
147+ throws UploadErrorException , FailedToListAlbumsException , InvalidTokenException , PermissionDeniedException , IOException {
147148 if (!exportInformation .isPresent ()) {
148149 // Make list of photos contained in albums so they are not exported twice later on
149150 populateContainedMediaList (jobId , authData );
@@ -321,7 +322,7 @@ private ExportResult<MediaContainerResource> exportMediaContainer(
321322 @ VisibleForTesting
322323 ExportResult <MediaContainerResource > exportAlbums (
323324 TokensAndUrlAuthData authData , Optional <PaginationData > paginationData , UUID jobId )
324- throws IOException , InvalidTokenException , PermissionDeniedException {
325+ throws FailedToListAlbumsException {
325326 Optional <String > paginationToken = Optional .empty ();
326327 if (paginationData .isPresent ()) {
327328 String token = ((StringPaginationToken ) paginationData .get ()).getToken ();
@@ -330,9 +331,7 @@ ExportResult<MediaContainerResource> exportAlbums(
330331 paginationToken = Optional .of (token .substring (ALBUM_TOKEN_PREFIX .length ()));
331332 }
332333
333- AlbumListResponse albumListResponse ;
334-
335- albumListResponse = getOrCreatePhotosInterface (authData ).listAlbums (paginationToken );
334+ AlbumListResponse albumListResponse = listAlbums (jobId , authData , paginationToken );
336335
337336 PaginationData nextPageData ;
338337 String token = albumListResponse .getNextPageToken ();
@@ -406,7 +405,7 @@ ExportResult<MediaContainerResource> exportMedia(
406405
407406 /** Method for storing a list of all photos that are already contained in albums */
408407 void populateContainedMediaList (UUID jobId , TokensAndUrlAuthData authData )
409- throws IOException , InvalidTokenException , PermissionDeniedException , UploadErrorException {
408+ throws IOException , InvalidTokenException , PermissionDeniedException , UploadErrorException , FailedToListAlbumsException {
410409 // This method is only called once at the beginning of the transfer, so we can start by
411410 // initializing a new TempMediaData to be store in the job store.
412411 TempMediaData tempMediaData = new TempMediaData (jobId );
@@ -415,25 +414,29 @@ void populateContainedMediaList(UUID jobId, TokensAndUrlAuthData authData)
415414 AlbumListResponse albumListResponse ;
416415 MediaItemSearchResponse containedMediaSearchResponse ;
417416 do {
418- albumListResponse =
419- getOrCreatePhotosInterface (authData ).listAlbums (Optional .ofNullable (albumToken ));
420- if (albumListResponse .getAlbums () != null ) {
421- for (GoogleAlbum album : albumListResponse .getAlbums ()) {
422- String albumId = album .getId ();
423- String photoToken = null ;
424- do {
425- containedMediaSearchResponse =
426- getOrCreatePhotosInterface (authData )
427- .listMediaItems (Optional .of (albumId ), Optional .ofNullable (photoToken ));
428- if (containedMediaSearchResponse .getMediaItems () != null ) {
429- for (GoogleMediaItem mediaItem : containedMediaSearchResponse .getMediaItems ()) {
430- tempMediaData .addContainedPhotoId (mediaItem .getId ());
431- }
417+ albumListResponse = listAlbums (jobId , authData , Optional .ofNullable (albumToken ));
418+ albumToken = albumListResponse .getNextPageToken ();
419+ if (albumListResponse .getAlbums () == null ) {
420+ continue ;
421+ }
422+
423+ for (GoogleAlbum album : albumListResponse .getAlbums ()) {
424+ String albumId = album .getId ();
425+ String photoToken = null ;
426+
427+ do {
428+ containedMediaSearchResponse =
429+ getOrCreatePhotosInterface (authData )
430+ .listMediaItems (Optional .of (albumId ), Optional .ofNullable (photoToken ));
431+ if (containedMediaSearchResponse .getMediaItems () != null ) {
432+ for (GoogleMediaItem mediaItem : containedMediaSearchResponse .getMediaItems ()) {
433+ tempMediaData .addContainedPhotoId (mediaItem .getId ());
432434 }
433- photoToken = containedMediaSearchResponse . getNextPageToken ();
434- } while ( photoToken != null );
435- }
435+ }
436+ photoToken = containedMediaSearchResponse . getNextPageToken ( );
437+ } while ( photoToken != null );
436438 }
439+
437440 albumToken = albumListResponse .getNextPageToken ();
438441 } while (albumToken != null );
439442
@@ -558,6 +561,31 @@ GoogleMediaItem getGoogleMediaItem(String photoIdempotentId, String photoDataId,
558561 return null ;
559562 }
560563
564+ /**
565+ * Tries to call PhotosInterface.listAlbums, and retries on failure. If unsuccessful, throws a
566+ * FailedToListAlbumsException.
567+ */
568+ private AlbumListResponse listAlbums (UUID jobId , TokensAndUrlAuthData authData , Optional <String > albumToken )
569+ throws FailedToListAlbumsException {
570+ if (retryingExecutor == null || !enableRetrying ) {
571+ try {
572+ return getOrCreatePhotosInterface (authData ).listAlbums (albumToken );
573+ } catch (IOException | InvalidTokenException | PermissionDeniedException e ) {
574+ throw new FailedToListAlbumsException (e .getMessage (), e );
575+ }
576+ }
577+
578+ try {
579+ return retryingExecutor .executeOrThrowException (
580+ format ("%s: listAlbums(page=%s)" , jobId , albumToken ),
581+ format ("listAlbums(page=%s)" , albumToken ),
582+ () -> getOrCreatePhotosInterface (authData ).listAlbums (albumToken )
583+ );
584+ } catch (Exception e ) {
585+ throw new FailedToListAlbumsException (e .getMessage (), e );
586+ }
587+ }
588+
561589 private synchronized GooglePhotosInterface getOrCreatePhotosInterface (
562590 TokensAndUrlAuthData authData ) {
563591 return photosInterface == null ? makePhotosInterface (authData ) : photosInterface ;
0 commit comments