diff --git a/lib/entity/album.dart b/lib/entity/album.dart index fbdadae9..2faf85f8 100644 --- a/lib/entity/album.dart +++ b/lib/entity/album.dart @@ -24,10 +24,10 @@ import 'package:quiver/iterables.dart'; import 'package:tuple/tuple.dart'; String getAlbumFileRoot(Account account) => - "${api_util.getWebdavRootUrlRelative(account)}/.com.nkming.nc_photos"; + "${api_util.getWebdavRootUrlRelative(account)}/.com.nkming.nc_photos/albums"; bool isAlbumFile(File file) => - path.basename(path.dirname(file.path)) == ".com.nkming.nc_photos"; + path.dirname(file.path).endsWith(".com.nkming.nc_photos/albums"); List makeDistinctAlbumItems(List items) => items.distinctIf( diff --git a/lib/use_case/compat/v15.dart b/lib/use_case/compat/v15.dart new file mode 100644 index 00000000..8364ffa3 --- /dev/null +++ b/lib/use_case/compat/v15.dart @@ -0,0 +1,81 @@ +import 'package:logging/logging.dart'; +import 'package:nc_photos/account.dart'; +import 'package:nc_photos/api/api_util.dart' as api_util; +import 'package:nc_photos/entity/album.dart'; +import 'package:nc_photos/entity/file.dart'; +import 'package:nc_photos/exception.dart'; +import 'package:nc_photos/use_case/create_dir.dart'; +import 'package:nc_photos/use_case/ls.dart'; +import 'package:path/path.dart' as path; + +/// Compatibility helper for v15 +class CompatV15 { + /// Migrate album files from old location pre v15 to the new location in v15+ + /// + /// Return true if album files are migrated successfully, false otherwise. + /// Note that false does not necessarily mean that the migration has failed, + /// it could simply mean that no migration is needed + static Future migrateAlbumFiles(Account account, FileRepo fileRepo) { + return _MigrateAlbumFiles(fileRepo)(account); + } +} + +class _MigrateAlbumFiles { + _MigrateAlbumFiles(this.fileRepo); + + Future call(Account account) async { + try { + // get files from the old location + final ls = await Ls(fileRepo)( + account, + File( + path: _getAlbumFileRootCompat14(account), + )); + final albumFiles = + ls.where((element) => element.isCollection != true).toList(); + if (albumFiles.isEmpty) { + return false; + } + // copy to an intermediate location + final intermediateDir = "${getAlbumFileRoot(account)}.tmp"; + _log.info("[call] Copy album files to '$intermediateDir'"); + if (!ls.any((element) => + element.isCollection == true && element.path == intermediateDir)) { + await CreateDir(fileRepo)(account, intermediateDir); + } + for (final f in albumFiles) { + final fileName = path.basename(f.path); + await fileRepo.copy(account, f, "$intermediateDir/$fileName", + shouldOverwrite: true); + } + // rename intermediate + await fileRepo.move( + account, File(path: intermediateDir), getAlbumFileRoot(account)); + _log.info( + "[call] Album files moved to '${getAlbumFileRoot(account)}' successfully"); + // remove old files + for (final f in albumFiles) { + try { + await fileRepo.remove(account, f); + } catch (_) {} + } + return true; + } catch (e, stacktrace) { + if (e is ApiException && e.response.statusCode == 404) { + // no albums + return false; + } + _log.shout("[call] Failed while migrating album files to new location", e, + stacktrace); + rethrow; + } + } + + // old album root location on v14- + static String _getAlbumFileRootCompat14(Account account) => + "${api_util.getWebdavRootUrlRelative(account)}/.com.nkming.nc_photos"; + + final FileRepo fileRepo; + + static final _log = Logger("use_case.compat.v15._MigrateAlbumFiles"); +} diff --git a/lib/use_case/list_album.dart b/lib/use_case/list_album.dart index 3ce8c9ee..36e5e646 100644 --- a/lib/use_case/list_album.dart +++ b/lib/use_case/list_album.dart @@ -3,6 +3,7 @@ import 'package:nc_photos/account.dart'; import 'package:nc_photos/entity/album.dart'; import 'package:nc_photos/entity/file.dart'; import 'package:nc_photos/exception.dart'; +import 'package:nc_photos/use_case/compat/v15.dart'; import 'package:nc_photos/use_case/ls.dart'; class ListAlbum { @@ -10,6 +11,21 @@ class ListAlbum { /// List all albums associated with [account] Future> call(Account account) async { + final results = await _call(account); + if (results.isEmpty) { + if (await CompatV15.migrateAlbumFiles(account, fileRepo)) { + // migrated + return await _call(account); + } else { + // no need to migrate + return []; + } + } else { + return results; + } + } + + Future> _call(Account account) async { try { final ls = await Ls(fileRepo)( account, @@ -27,7 +43,7 @@ class ListAlbum { albumRepo.cleanUp(account, albumFiles); } catch (e, stacktrace) { // not important, log and ignore - _log.shout("[call] Failed while cleanUp", e, stacktrace); + _log.shout("[_call] Failed while cleanUp", e, stacktrace); } return albums; } catch (e) {