From 7a17f2b274033eabb722cccb8d71be7942204e7c Mon Sep 17 00:00:00 2001 From: Ming Ming Date: Thu, 23 Feb 2023 22:49:17 +0800 Subject: [PATCH] Refactor: move nextcloud api to dedicated pacakge --- app/lib/account.dart | 6 +- app/lib/api/api_util.dart | 2 +- app/lib/api/entity_converter.dart | 173 ++++++ app/lib/api/entity_converter.g.dart | 14 + app/lib/bloc/app_password_exchange.dart | 2 +- app/lib/bloc/home_search_suggestion.dart | 2 +- app/lib/bloc/list_album_share_outlier.dart | 2 +- app/lib/bloc/search_suggestion.dart | 2 +- app/lib/debug_util.dart | 2 +- app/lib/double_extension.dart | 2 +- app/lib/entity/album.dart | 4 +- app/lib/entity/album/cover_provider.dart | 2 +- app/lib/entity/album/item.dart | 4 +- app/lib/entity/album/provider.dart | 2 +- app/lib/entity/album/sort_provider.dart | 2 +- app/lib/entity/album/upgrader.dart | 4 +- app/lib/entity/exif.dart | 2 +- app/lib/entity/face/data_source.dart | 42 +- app/lib/entity/face/data_source.g.dart | 7 - app/lib/entity/favorite/data_source.dart | 18 +- app/lib/entity/file.dart | 6 +- app/lib/entity/file/data_source.dart | 39 +- app/lib/entity/file_descriptor.dart | 2 +- app/lib/entity/file_util.dart | 4 +- app/lib/entity/person/data_source.dart | 23 +- app/lib/entity/search/data_source.dart | 2 +- app/lib/entity/share.dart | 4 +- app/lib/entity/share/data_source.dart | 105 ++-- app/lib/entity/sharee.dart | 2 +- app/lib/entity/sharee/data_source.dart | 22 +- .../entity/sqlite/files_query_builder.dart | 2 +- app/lib/entity/sqlite/type_converter.dart | 2 +- app/lib/entity/tag.dart | 2 +- app/lib/entity/tag/data_source.dart | 32 +- app/lib/entity/tagged_file/data_source.dart | 12 +- app/lib/entity/webdav_response_parser.dart | 550 ------------------ app/lib/exception.dart | 2 +- .../legacy/app_password_exchange_bloc.dart | 4 +- app/lib/legacy/connect.dart | 4 +- app/lib/legacy/sign_in.dart | 4 +- app/lib/location_util.dart | 2 +- app/lib/mobile/self_signed_cert_manager.dart | 2 +- app/lib/mobile/universal_storage.dart | 2 +- app/lib/np_api_util.dart | 14 + app/lib/use_case/compat/v32.dart | 2 +- app/lib/use_case/compat/v34.dart | 2 +- app/lib/use_case/download_file.dart | 4 +- app/lib/use_case/download_preview.dart | 4 +- app/lib/use_case/ls.dart | 2 +- app/lib/use_case/remove.dart | 2 +- app/lib/use_case/request_public_link.dart | 11 +- app/lib/use_case/share_album_with_user.dart | 2 +- app/lib/use_case/startup_sync.dart | 2 +- app/lib/use_case/unshare_album_with_user.dart | 2 +- app/lib/use_case/unshare_file_from_album.dart | 2 +- app/lib/widget/album_browser_app_bar.dart | 4 +- .../widget/album_share_outlier_browser.dart | 4 +- .../builder/album_grid_item_builder.dart | 4 +- app/lib/widget/connect.dart | 4 +- app/lib/widget/home_search_suggestion.dart | 2 +- app/lib/widget/image_editor.dart | 4 +- app/lib/widget/image_enhancer.dart | 20 +- app/lib/widget/image_viewer.dart | 8 +- app/lib/widget/network_thumbnail.dart | 4 +- app/lib/widget/share_album_dialog.dart | 2 +- app/lib/widget/shared_file_viewer.dart | 4 +- app/lib/widget/sign_in.dart | 2 +- app/lib/widget/tag_picker_dialog.dart | 2 +- app/lib/widget/video_viewer.dart | 4 +- app/pubspec.lock | 18 +- app/pubspec.yaml | 5 +- app/test/account_test.dart | 2 +- app/test/api/entity_converter_test.dart | 244 ++++++++ .../bloc/list_album_share_outlier_test.dart | 2 +- app/test/entity/album/data_source_test.dart | 2 +- app/test/entity/album/sort_provider_test.dart | 2 +- app/test/entity/album_test.dart | 4 +- app/test/entity/file_test.dart | 2 +- app/test/mock_type.dart | 2 +- app/test/test_util.dart | 2 +- app/test/use_case/add_to_album_test.dart | 2 +- .../use_case/share_album_with_user_test.dart | 2 +- .../unshare_album_with_user_test.dart | 2 +- np_api/.gitignore | 30 + np_api/.metadata | 10 + np_api/analysis_options.yaml | 7 + np_api/lib/np_api.dart | 11 + {app/lib/api => np_api/lib/src}/api.dart | 76 +-- {app/lib/api => np_api/lib/src}/api.g.dart | 31 +- .../api => np_api/lib/src}/direct_api.dart | 0 np_api/lib/src/entity/entity.dart | 257 ++++++++ np_api/lib/src/entity/entity.g.dart | 63 ++ np_api/lib/src/entity/face_parser.dart | 42 ++ np_api/lib/src/entity/face_parser.g.dart | 14 + np_api/lib/src/entity/favorite_parser.dart | 74 +++ np_api/lib/src/entity/file_parser.dart | 202 +++++++ np_api/lib/src/entity/file_parser.g.dart | 14 + np_api/lib/src/entity/parser.dart | 85 +++ .../lib/src/entity/parser.g.dart | 7 +- np_api/lib/src/entity/person_parser.dart | 43 ++ np_api/lib/src/entity/person_parser.g.dart | 14 + np_api/lib/src/entity/share_parser.dart | 53 ++ np_api/lib/src/entity/share_parser.g.dart | 14 + np_api/lib/src/entity/sharee_parser.dart | 59 ++ np_api/lib/src/entity/sharee_parser.g.dart | 14 + np_api/lib/src/entity/tag_parser.dart | 102 ++++ np_api/lib/src/entity/tagged_file_parser.dart | 75 +++ .../lib/src}/face_recognition_api.dart | 0 .../lib/api => np_api/lib/src}/files_api.dart | 0 .../lib/src}/files_sharing_api.dart | 0 .../api => np_api/lib/src}/systemtag_api.dart | 0 np_api/lib/src/type.dart | 44 ++ np_api/lib/src/type.g.dart | 21 + np_api/lib/src/util.dart | 1 + np_api/pubspec.yaml | 75 +++ np_api/test/entity/face_parser_test.dart | 51 ++ np_api/test/entity/favorite_parser_test.dart | 47 ++ .../test/entity/file_parser_test.dart | 103 ++-- np_api/test/entity/person_parser_test.dart | 47 ++ np_api/test/entity/share_parser_test.dart | 81 +++ np_api/test/entity/sharee_parser_test.dart | 73 +++ np_api/test/entity/tag_parser_test.dart | 52 ++ .../test/entity/tagged_file_parser_test.dart | 33 ++ np_common/.gitignore | 30 + np_common/.metadata | 10 + np_common/analysis_options.yaml | 7 + {app => np_common}/lib/ci_string.dart | 2 +- np_common/lib/log.dart | 56 ++ {app => np_common}/lib/string_extension.dart | 0 {app => np_common}/lib/type.dart | 0 np_common/pubspec.yaml | 55 ++ {app => np_common}/test/ci_string_test.dart | 2 +- .../test/string_extension_test.dart | 2 +- 133 files changed, 2668 insertions(+), 952 deletions(-) create mode 100644 app/lib/api/entity_converter.dart create mode 100644 app/lib/api/entity_converter.g.dart delete mode 100644 app/lib/entity/webdav_response_parser.dart create mode 100644 app/lib/np_api_util.dart create mode 100644 app/test/api/entity_converter_test.dart create mode 100644 np_api/.gitignore create mode 100644 np_api/.metadata create mode 100644 np_api/analysis_options.yaml create mode 100644 np_api/lib/np_api.dart rename {app/lib/api => np_api/lib/src}/api.dart (57%) rename {app/lib/api => np_api/lib/src}/api.g.dart (58%) rename {app/lib/api => np_api/lib/src}/direct_api.dart (100%) create mode 100644 np_api/lib/src/entity/entity.dart create mode 100644 np_api/lib/src/entity/entity.g.dart create mode 100644 np_api/lib/src/entity/face_parser.dart create mode 100644 np_api/lib/src/entity/face_parser.g.dart create mode 100644 np_api/lib/src/entity/favorite_parser.dart create mode 100644 np_api/lib/src/entity/file_parser.dart create mode 100644 np_api/lib/src/entity/file_parser.g.dart create mode 100644 np_api/lib/src/entity/parser.dart rename app/lib/entity/webdav_response_parser.g.dart => np_api/lib/src/entity/parser.g.dart (59%) create mode 100644 np_api/lib/src/entity/person_parser.dart create mode 100644 np_api/lib/src/entity/person_parser.g.dart create mode 100644 np_api/lib/src/entity/share_parser.dart create mode 100644 np_api/lib/src/entity/share_parser.g.dart create mode 100644 np_api/lib/src/entity/sharee_parser.dart create mode 100644 np_api/lib/src/entity/sharee_parser.g.dart create mode 100644 np_api/lib/src/entity/tag_parser.dart create mode 100644 np_api/lib/src/entity/tagged_file_parser.dart rename {app/lib/api => np_api/lib/src}/face_recognition_api.dart (100%) rename {app/lib/api => np_api/lib/src}/files_api.dart (100%) rename {app/lib/api => np_api/lib/src}/files_sharing_api.dart (100%) rename {app/lib/api => np_api/lib/src}/systemtag_api.dart (100%) create mode 100644 np_api/lib/src/type.dart create mode 100644 np_api/lib/src/type.g.dart create mode 100644 np_api/lib/src/util.dart create mode 100644 np_api/pubspec.yaml create mode 100644 np_api/test/entity/face_parser_test.dart create mode 100644 np_api/test/entity/favorite_parser_test.dart rename app/test/entity/webdav_response_parser_test.dart => np_api/test/entity/file_parser_test.dart (83%) create mode 100644 np_api/test/entity/person_parser_test.dart create mode 100644 np_api/test/entity/share_parser_test.dart create mode 100644 np_api/test/entity/sharee_parser_test.dart create mode 100644 np_api/test/entity/tag_parser_test.dart create mode 100644 np_api/test/entity/tagged_file_parser_test.dart create mode 100644 np_common/.gitignore create mode 100644 np_common/.metadata create mode 100644 np_common/analysis_options.yaml rename {app => np_common}/lib/ci_string.dart (97%) create mode 100644 np_common/lib/log.dart rename {app => np_common}/lib/string_extension.dart (100%) rename {app => np_common}/lib/type.dart (100%) create mode 100644 np_common/pubspec.yaml rename {app => np_common}/test/ci_string_test.dart (98%) rename {app => np_common}/test/string_extension_test.dart (96%) diff --git a/app/lib/account.dart b/app/lib/account.dart index 06128dd2..c8c31298 100644 --- a/app/lib/account.dart +++ b/app/lib/account.dart @@ -3,11 +3,11 @@ import 'dart:math'; import 'package:equatable/equatable.dart'; import 'package:flutter/foundation.dart'; import 'package:logging/logging.dart'; -import 'package:nc_photos/ci_string.dart'; import 'package:nc_photos/iterable_extension.dart'; -import 'package:nc_photos/string_extension.dart'; -import 'package:nc_photos/type.dart'; import 'package:np_codegen/np_codegen.dart'; +import 'package:np_common/ci_string.dart'; +import 'package:np_common/string_extension.dart'; +import 'package:np_common/type.dart'; import 'package:to_string/to_string.dart'; part 'account.g.dart'; diff --git a/app/lib/api/api_util.dart b/app/lib/api/api_util.dart index 18fbffc5..f025d17c 100644 --- a/app/lib/api/api_util.dart +++ b/app/lib/api/api_util.dart @@ -4,10 +4,10 @@ import 'dart:convert'; import 'package:flutter/foundation.dart'; import 'package:logging/logging.dart'; import 'package:nc_photos/account.dart'; -import 'package:nc_photos/api/api.dart'; import 'package:nc_photos/entity/file_descriptor.dart'; import 'package:nc_photos/entity/file_util.dart' as file_util; import 'package:nc_photos/exception.dart'; +import 'package:np_api/np_api.dart'; import 'package:to_string/to_string.dart'; part 'api_util.g.dart'; diff --git a/app/lib/api/entity_converter.dart b/app/lib/api/entity_converter.dart new file mode 100644 index 00000000..dcadfa1d --- /dev/null +++ b/app/lib/api/entity_converter.dart @@ -0,0 +1,173 @@ +import 'dart:convert'; + +import 'package:logging/logging.dart'; +import 'package:nc_photos/entity/face.dart'; +import 'package:nc_photos/entity/favorite.dart'; +import 'package:nc_photos/entity/file.dart'; +import 'package:nc_photos/entity/person.dart'; +import 'package:nc_photos/entity/share.dart'; +import 'package:nc_photos/entity/sharee.dart'; +import 'package:nc_photos/entity/tag.dart'; +import 'package:nc_photos/entity/tagged_file.dart'; +import 'package:nc_photos/object_extension.dart'; +import 'package:np_api/np_api.dart' as api; +import 'package:np_codegen/np_codegen.dart'; +import 'package:np_common/ci_string.dart'; +import 'package:np_common/string_extension.dart'; + +part 'entity_converter.g.dart'; + +class ApiFaceConverter { + static Face fromApi(api.Face face) { + return Face( + id: face.id, + fileId: face.fileId, + ); + } +} + +class ApiFavoriteConverter { + static Favorite fromApi(api.Favorite favorite) { + return Favorite( + fileId: favorite.fileId, + ); + } +} + +@npLog +class ApiFileConverter { + static File fromApi(api.File file) { + final metadata = file.customProperties?["com.nkming.nc_photos:metadata"] + ?.run((obj) => Metadata.fromJson( + jsonDecode(obj), + upgraderV1: MetadataUpgraderV1( + fileContentType: file.contentType, + logFilePath: file.href, + ), + upgraderV2: MetadataUpgraderV2( + fileContentType: file.contentType, + logFilePath: file.href, + ), + upgraderV3: MetadataUpgraderV3( + fileContentType: file.contentType, + logFilePath: file.href, + ), + )); + return File( + path: _hrefToPath(file.href), + contentLength: file.contentLength, + contentType: file.contentType, + etag: file.etag, + lastModified: file.lastModified, + isCollection: file.isCollection, + hasPreview: file.hasPreview, + fileId: file.fileId, + isFavorite: file.favorite, + ownerId: file.ownerId?.toCi(), + ownerDisplayName: file.ownerDisplayName, + trashbinFilename: file.trashbinFilename, + trashbinOriginalLocation: file.trashbinOriginalLocation, + trashbinDeletionTime: file.trashbinDeletionTime, + metadata: metadata, + isArchived: file.customProperties?["com.nkming.nc_photos:is-archived"] + ?.run((obj) => obj == "true"), + overrideDateTime: file + .customProperties?["com.nkming.nc_photos:override-date-time"] + ?.run((obj) => DateTime.parse(obj)), + location: file.customProperties?["com.nkming.nc_photos:location"] + ?.run((obj) => ImageLocation.fromJson(jsonDecode(obj))), + ); + } + + static String _hrefToPath(String href) { + final rawPath = href.trimLeftAny("/"); + final pos = rawPath.indexOf("remote.php"); + if (pos == -1) { + // what? + _log.warning("[_hrefToPath] Unknown href value: $rawPath"); + return rawPath; + } else { + return rawPath.substring(pos); + } + } + + static final _log = _$ApiFileConverterNpLog.log; +} + +class ApiPersonConverter { + static Person fromApi(api.Person person) { + return Person( + name: person.name, + thumbFaceId: person.thumbFaceId, + count: person.count, + ); + } +} + +class ApiShareConverter { + static Share fromApi(api.Share share) { + final shareType = ShareTypeExtension.fromValue(share.shareType); + final itemType = ShareItemTypeExtension.fromValue(share.itemType); + return Share( + id: share.id, + shareType: shareType, + stime: DateTime.fromMillisecondsSinceEpoch(share.stime * 1000), + uidOwner: share.uidOwner.toCi(), + displaynameOwner: share.displaynameOwner, + uidFileOwner: share.uidFileOwner.toCi(), + path: share.path, + itemType: itemType, + mimeType: share.mimeType, + itemSource: share.itemSource, + // when shared with a password protected link, shareWith somehow contains + // the password, which doesn't make sense. We set it to null instead + shareWith: + shareType == ShareType.publicLink ? null : share.shareWith?.toCi(), + shareWithDisplayName: share.shareWithDisplayName, + url: share.url, + ); + } +} + +class ApiShareeConverter { + static Sharee fromApi(api.Sharee sharee) { + return Sharee( + type: _keyTypes[sharee.type]!, + label: sharee.label, + shareType: sharee.shareType, + shareWith: sharee.shareWith.toCi(), + shareWithDisplayNameUnique: sharee.shareWithDisplayNameUnique, + ); + } + + static const _keyTypes = { + "users": ShareeType.user, + "groups": ShareeType.group, + "remotes": ShareeType.remote, + "remote_groups": ShareeType.remoteGroup, + "emails": ShareeType.email, + "circles": ShareeType.circle, + "rooms": ShareeType.room, + "deck": ShareeType.deck, + "lookup": ShareeType.lookup, + }; +} + +class ApiTagConverter { + static Tag fromApi(api.Tag tag) { + return Tag( + id: tag.id, + displayName: tag.displayName, + userVisible: tag.userVisible, + userAssignable: tag.userAssignable, + ); + } +} + +class ApiTaggedFileConverter { + static TaggedFile fromApi(api.TaggedFile taggedFile) { + return TaggedFile( + fileId: taggedFile.fileId, + ); + } +} diff --git a/app/lib/api/entity_converter.g.dart b/app/lib/api/entity_converter.g.dart new file mode 100644 index 00000000..52f114c6 --- /dev/null +++ b/app/lib/api/entity_converter.g.dart @@ -0,0 +1,14 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'entity_converter.dart'; + +// ************************************************************************** +// NpLogGenerator +// ************************************************************************** + +extension _$ApiFileConverterNpLog on ApiFileConverter { + // ignore: unused_element + Logger get _log => log; + + static final log = Logger("api.entity_converter.ApiFileConverter"); +} diff --git a/app/lib/bloc/app_password_exchange.dart b/app/lib/bloc/app_password_exchange.dart index 804bb50d..caec5fb7 100644 --- a/app/lib/bloc/app_password_exchange.dart +++ b/app/lib/bloc/app_password_exchange.dart @@ -5,9 +5,9 @@ import 'package:bloc/bloc.dart'; 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/ci_string.dart'; import 'package:nc_photos/exception.dart'; import 'package:np_codegen/np_codegen.dart'; +import 'package:np_common/ci_string.dart'; import 'package:to_string/to_string.dart'; part 'app_password_exchange.g.dart'; diff --git a/app/lib/bloc/home_search_suggestion.dart b/app/lib/bloc/home_search_suggestion.dart index a1021ddf..20919fbc 100644 --- a/app/lib/bloc/home_search_suggestion.dart +++ b/app/lib/bloc/home_search_suggestion.dart @@ -4,7 +4,6 @@ import 'package:flutter/foundation.dart'; import 'package:kiwi/kiwi.dart'; import 'package:logging/logging.dart'; import 'package:nc_photos/account.dart'; -import 'package:nc_photos/ci_string.dart'; import 'package:nc_photos/di_container.dart'; import 'package:nc_photos/entity/album.dart'; import 'package:nc_photos/entity/person.dart'; @@ -15,6 +14,7 @@ import 'package:nc_photos/use_case/list_location_group.dart'; import 'package:nc_photos/use_case/list_person.dart'; import 'package:nc_photos/use_case/list_tag.dart'; import 'package:np_codegen/np_codegen.dart'; +import 'package:np_common/ci_string.dart'; import 'package:to_string/to_string.dart'; import 'package:tuple/tuple.dart'; import 'package:woozy_search/woozy_search.dart'; diff --git a/app/lib/bloc/list_album_share_outlier.dart b/app/lib/bloc/list_album_share_outlier.dart index 5b5260b5..21fb096d 100644 --- a/app/lib/bloc/list_album_share_outlier.dart +++ b/app/lib/bloc/list_album_share_outlier.dart @@ -3,7 +3,6 @@ import 'package:equatable/equatable.dart'; import 'package:flutter/foundation.dart'; import 'package:logging/logging.dart'; import 'package:nc_photos/account.dart'; -import 'package:nc_photos/ci_string.dart'; import 'package:nc_photos/debug_util.dart'; import 'package:nc_photos/di_container.dart'; import 'package:nc_photos/entity/album.dart'; @@ -16,6 +15,7 @@ import 'package:nc_photos/object_extension.dart'; import 'package:nc_photos/use_case/list_share.dart'; import 'package:nc_photos/use_case/list_sharee.dart'; import 'package:np_codegen/np_codegen.dart'; +import 'package:np_common/ci_string.dart'; import 'package:to_string/to_string.dart'; part 'list_album_share_outlier.g.dart'; diff --git a/app/lib/bloc/search_suggestion.dart b/app/lib/bloc/search_suggestion.dart index 0b34fab9..77ae0ebb 100644 --- a/app/lib/bloc/search_suggestion.dart +++ b/app/lib/bloc/search_suggestion.dart @@ -2,9 +2,9 @@ import 'package:bloc/bloc.dart'; import 'package:collection/collection.dart'; import 'package:flutter/foundation.dart'; import 'package:logging/logging.dart'; -import 'package:nc_photos/ci_string.dart'; import 'package:nc_photos/iterable_extension.dart'; import 'package:np_codegen/np_codegen.dart'; +import 'package:np_common/ci_string.dart'; import 'package:to_string/to_string.dart'; import 'package:tuple/tuple.dart'; import 'package:woozy_search/woozy_search.dart'; diff --git a/app/lib/debug_util.dart b/app/lib/debug_util.dart index 9809079a..ec5740ac 100644 --- a/app/lib/debug_util.dart +++ b/app/lib/debug_util.dart @@ -3,7 +3,7 @@ import 'dart:convert'; import 'package:flutter/foundation.dart'; import 'package:nc_photos/mobile/platform.dart' if (dart.library.html) 'package:nc_photos/web/platform.dart' as platform; -import 'package:nc_photos/string_extension.dart'; +import 'package:np_common/string_extension.dart'; import 'package:path/path.dart' as path_lib; class LogCapturer { diff --git a/app/lib/double_extension.dart b/app/lib/double_extension.dart index 902dae22..2c9b9a46 100644 --- a/app/lib/double_extension.dart +++ b/app/lib/double_extension.dart @@ -1,4 +1,4 @@ -import 'package:nc_photos/string_extension.dart'; +import 'package:np_common/string_extension.dart'; extension DoubleExtension on double { /// Same as toStringAsFixed but with trailing zeros truncated diff --git a/app/lib/entity/album.dart b/app/lib/entity/album.dart index aa1761a7..2c69ba31 100644 --- a/app/lib/entity/album.dart +++ b/app/lib/entity/album.dart @@ -1,7 +1,6 @@ import 'package:equatable/equatable.dart'; import 'package:logging/logging.dart'; import 'package:nc_photos/account.dart'; -import 'package:nc_photos/ci_string.dart'; import 'package:nc_photos/entity/album/cover_provider.dart'; import 'package:nc_photos/entity/album/provider.dart'; import 'package:nc_photos/entity/album/sort_provider.dart'; @@ -10,8 +9,9 @@ import 'package:nc_photos/entity/file.dart'; import 'package:nc_photos/iterable_extension.dart'; import 'package:nc_photos/object_extension.dart'; import 'package:nc_photos/or_null.dart'; -import 'package:nc_photos/type.dart'; import 'package:np_codegen/np_codegen.dart'; +import 'package:np_common/ci_string.dart'; +import 'package:np_common/type.dart'; import 'package:to_string/to_string.dart'; part 'album.g.dart'; diff --git a/app/lib/entity/album/cover_provider.dart b/app/lib/entity/album/cover_provider.dart index 22c2850b..55e05d73 100644 --- a/app/lib/entity/album/cover_provider.dart +++ b/app/lib/entity/album/cover_provider.dart @@ -7,8 +7,8 @@ import 'package:nc_photos/entity/album/provider.dart'; import 'package:nc_photos/entity/file.dart'; import 'package:nc_photos/entity/file_descriptor.dart'; import 'package:nc_photos/entity/file_util.dart' as file_util; -import 'package:nc_photos/type.dart'; import 'package:np_codegen/np_codegen.dart'; +import 'package:np_common/type.dart'; import 'package:to_string/to_string.dart'; part 'cover_provider.g.dart'; diff --git a/app/lib/entity/album/item.dart b/app/lib/entity/album/item.dart index 600546d9..44d25117 100644 --- a/app/lib/entity/album/item.dart +++ b/app/lib/entity/album/item.dart @@ -1,11 +1,11 @@ import 'package:equatable/equatable.dart'; import 'package:flutter/foundation.dart'; import 'package:logging/logging.dart'; -import 'package:nc_photos/ci_string.dart'; import 'package:nc_photos/entity/file.dart'; import 'package:nc_photos/or_null.dart'; -import 'package:nc_photos/type.dart'; import 'package:np_codegen/np_codegen.dart'; +import 'package:np_common/ci_string.dart'; +import 'package:np_common/type.dart'; import 'package:to_string/to_string.dart'; part 'item.g.dart'; diff --git a/app/lib/entity/album/provider.dart b/app/lib/entity/album/provider.dart index b3266543..18ab269b 100644 --- a/app/lib/entity/album/provider.dart +++ b/app/lib/entity/album/provider.dart @@ -9,8 +9,8 @@ import 'package:nc_photos/entity/file.dart'; import 'package:nc_photos/entity/tag.dart'; import 'package:nc_photos/iterable_extension.dart'; import 'package:nc_photos/or_null.dart'; -import 'package:nc_photos/type.dart'; import 'package:np_codegen/np_codegen.dart'; +import 'package:np_common/type.dart'; import 'package:to_string/to_string.dart'; part 'provider.g.dart'; diff --git a/app/lib/entity/album/sort_provider.dart b/app/lib/entity/album/sort_provider.dart index a31e5818..8287c0df 100644 --- a/app/lib/entity/album/sort_provider.dart +++ b/app/lib/entity/album/sort_provider.dart @@ -5,8 +5,8 @@ import 'package:nc_photos/entity/album/item.dart'; import 'package:nc_photos/entity/file.dart'; import 'package:nc_photos/entity/file_descriptor.dart'; import 'package:nc_photos/iterable_extension.dart'; -import 'package:nc_photos/type.dart'; import 'package:np_codegen/np_codegen.dart'; +import 'package:np_common/type.dart'; import 'package:to_string/to_string.dart'; import 'package:tuple/tuple.dart'; diff --git a/app/lib/entity/album/upgrader.dart b/app/lib/entity/album/upgrader.dart index 882be2c0..92ebd5ff 100644 --- a/app/lib/entity/album/upgrader.dart +++ b/app/lib/entity/album/upgrader.dart @@ -1,11 +1,11 @@ import 'package:collection/collection.dart'; import 'package:logging/logging.dart'; import 'package:nc_photos/account.dart'; -import 'package:nc_photos/ci_string.dart'; import 'package:nc_photos/entity/exif.dart'; import 'package:nc_photos/entity/file.dart'; -import 'package:nc_photos/type.dart'; import 'package:np_codegen/np_codegen.dart'; +import 'package:np_common/ci_string.dart'; +import 'package:np_common/type.dart'; import 'package:tuple/tuple.dart'; part 'upgrader.g.dart'; diff --git a/app/lib/entity/exif.dart b/app/lib/entity/exif.dart index d213afee..1efaeb2f 100644 --- a/app/lib/entity/exif.dart +++ b/app/lib/entity/exif.dart @@ -2,8 +2,8 @@ import 'package:equatable/equatable.dart'; import 'package:exifdart/exifdart.dart'; import 'package:intl/intl.dart'; import 'package:logging/logging.dart'; -import 'package:nc_photos/type.dart'; import 'package:np_codegen/np_codegen.dart'; +import 'package:np_common/type.dart'; part 'exif.g.dart'; diff --git a/app/lib/entity/face/data_source.dart b/app/lib/entity/face/data_source.dart index ac4cdc39..9f6eb5be 100644 --- a/app/lib/entity/face/data_source.dart +++ b/app/lib/entity/face/data_source.dart @@ -1,12 +1,11 @@ -import 'dart:convert'; - import 'package:logging/logging.dart'; import 'package:nc_photos/account.dart'; -import 'package:nc_photos/api/api.dart'; +import 'package:nc_photos/api/entity_converter.dart'; import 'package:nc_photos/entity/face.dart'; import 'package:nc_photos/entity/person.dart'; import 'package:nc_photos/exception.dart'; -import 'package:nc_photos/type.dart'; +import 'package:nc_photos/np_api_util.dart'; +import 'package:np_api/np_api.dart' as api; import 'package:np_codegen/np_codegen.dart'; part 'data_source.g.dart'; @@ -18,7 +17,7 @@ class FaceRemoteDataSource implements FaceDataSource { @override list(Account account, Person person) async { _log.info("[list] $person"); - final response = await Api(account) + final response = await ApiUtil.fromAccount(account) .ocs() .facerecognition() .person(person.name) @@ -27,35 +26,12 @@ class FaceRemoteDataSource implements FaceDataSource { if (!response.isGood) { _log.severe("[list] Failed requesting server: $response"); throw ApiException( - response: response, - message: - "Server responed with an error: HTTP ${response.statusCode}"); + response: response, + message: "Server responed with an error: HTTP ${response.statusCode}", + ); } - final json = jsonDecode(response.body); - final List dataJson = json["ocs"]["data"].cast(); - return _FaceParser().parseList(dataJson); - } -} - -@npLog -class _FaceParser { - List parseList(List jsons) { - final product = []; - for (final j in jsons) { - try { - product.add(parseSingle(j)); - } catch (e) { - _log.severe("[parseList] Failed parsing json: ${jsonEncode(j)}", e); - } - } - return product; - } - - Face parseSingle(JsonObj json) { - return Face( - id: json["id"], - fileId: json["fileId"], - ); + final apiFaces = await api.FaceParser().parse(response.body); + return apiFaces.map(ApiFaceConverter.fromApi).toList(); } } diff --git a/app/lib/entity/face/data_source.g.dart b/app/lib/entity/face/data_source.g.dart index f7cefcfe..89a77b57 100644 --- a/app/lib/entity/face/data_source.g.dart +++ b/app/lib/entity/face/data_source.g.dart @@ -12,10 +12,3 @@ extension _$FaceRemoteDataSourceNpLog on FaceRemoteDataSource { static final log = Logger("entity.face.data_source.FaceRemoteDataSource"); } - -extension _$_FaceParserNpLog on _FaceParser { - // ignore: unused_element - Logger get _log => log; - - static final log = Logger("entity.face.data_source._FaceParser"); -} diff --git a/app/lib/entity/favorite/data_source.dart b/app/lib/entity/favorite/data_source.dart index 0ea7a68c..ae5b5ac5 100644 --- a/app/lib/entity/favorite/data_source.dart +++ b/app/lib/entity/favorite/data_source.dart @@ -1,12 +1,12 @@ import 'package:logging/logging.dart'; import 'package:nc_photos/account.dart'; -import 'package:nc_photos/api/api.dart'; +import 'package:nc_photos/api/entity_converter.dart'; import 'package:nc_photos/entity/favorite.dart'; import 'package:nc_photos/entity/file.dart'; -import 'package:nc_photos/entity/webdav_response_parser.dart'; import 'package:nc_photos/exception.dart'; +import 'package:nc_photos/np_api_util.dart'; +import 'package:np_api/np_api.dart' as api; import 'package:np_codegen/np_codegen.dart'; -import 'package:xml/xml.dart'; part 'data_source.g.dart'; @@ -17,19 +17,19 @@ class FavoriteRemoteDataSource implements FavoriteDataSource { @override list(Account account, File dir) async { _log.info("[list] ${dir.path}"); - final response = await Api(account).files().report( + final response = await ApiUtil.fromAccount(account).files().report( path: dir.path, favorite: true, ); if (!response.isGood) { _log.severe("[list] Failed requesting server: $response"); throw ApiException( - response: response, - message: - "Server responed with an error: HTTP ${response.statusCode}"); + response: response, + message: "Server responed with an error: HTTP ${response.statusCode}", + ); } - final xml = XmlDocument.parse(response.body); - return WebdavResponseParser().parseFavorites(xml); + final apiFavorites = await api.FavoriteParser().parse(response.body); + return apiFavorites.map(ApiFavoriteConverter.fromApi).toList(); } } diff --git a/app/lib/entity/file.dart b/app/lib/entity/file.dart index fef6b35c..f5603df9 100644 --- a/app/lib/entity/file.dart +++ b/app/lib/entity/file.dart @@ -3,15 +3,15 @@ import 'dart:typed_data'; import 'package:equatable/equatable.dart'; import 'package:logging/logging.dart'; import 'package:nc_photos/account.dart'; -import 'package:nc_photos/ci_string.dart'; import 'package:nc_photos/entity/exif.dart'; import 'package:nc_photos/entity/file_descriptor.dart'; import 'package:nc_photos/entity/file_util.dart' as file_util; import 'package:nc_photos/json_util.dart' as json_util; import 'package:nc_photos/or_null.dart'; -import 'package:nc_photos/string_extension.dart'; -import 'package:nc_photos/type.dart'; import 'package:np_codegen/np_codegen.dart'; +import 'package:np_common/ci_string.dart'; +import 'package:np_common/string_extension.dart'; +import 'package:np_common/type.dart'; import 'package:to_string/to_string.dart'; part 'file.g.dart'; diff --git a/app/lib/entity/file/data_source.dart b/app/lib/entity/file/data_source.dart index ca79d79e..16f0e5c9 100644 --- a/app/lib/entity/file/data_source.dart +++ b/app/lib/entity/file/data_source.dart @@ -4,7 +4,7 @@ import 'dart:typed_data'; import 'package:drift/drift.dart' as sql; import 'package:logging/logging.dart'; import 'package:nc_photos/account.dart'; -import 'package:nc_photos/api/api.dart'; +import 'package:nc_photos/api/entity_converter.dart'; import 'package:nc_photos/debug_util.dart'; import 'package:nc_photos/di_container.dart'; import 'package:nc_photos/entity/file.dart'; @@ -13,15 +13,15 @@ import 'package:nc_photos/entity/file_descriptor.dart'; import 'package:nc_photos/entity/file_util.dart' as file_util; import 'package:nc_photos/entity/sqlite/database.dart' as sql; import 'package:nc_photos/entity/sqlite/files_query_builder.dart' as sql; -import 'package:nc_photos/entity/webdav_response_parser.dart'; import 'package:nc_photos/exception.dart'; import 'package:nc_photos/iterable_extension.dart'; +import 'package:nc_photos/np_api_util.dart'; import 'package:nc_photos/object_extension.dart'; import 'package:nc_photos/or_null.dart'; import 'package:nc_photos/use_case/compat/v32.dart'; +import 'package:np_api/np_api.dart' as api; import 'package:np_codegen/np_codegen.dart'; import 'package:path/path.dart' as path_lib; -import 'package:xml/xml.dart'; part 'data_source.g.dart'; @@ -92,20 +92,22 @@ class FileWebdavDataSource implements FileDataSource { @override remove(Account account, File f) async { _log.info("[remove] ${f.path}"); - final response = await Api(account).files().delete(path: f.path); + final response = + await ApiUtil.fromAccount(account).files().delete(path: f.path); if (!response.isGood) { _log.severe("[remove] Failed requesting server: $response"); throw ApiException( - response: response, - message: - "Server responed with an error: HTTP ${response.statusCode}"); + response: response, + message: "Server responed with an error: HTTP ${response.statusCode}", + ); } } @override getBinary(Account account, File f) async { _log.info("[getBinary] ${f.path}"); - final response = await Api(account).files().get(path: f.path); + final response = + await ApiUtil.fromAccount(account).files().get(path: f.path); if (!response.isGood) { _log.severe("[getBinary] Failed requesting server: $response"); throw ApiException( @@ -119,8 +121,9 @@ class FileWebdavDataSource implements FileDataSource { @override putBinary(Account account, String path, Uint8List content) async { _log.info("[putBinary] $path"); - final response = - await Api(account).files().put(path: path, content: content); + final response = await ApiUtil.fromAccount(account) + .files() + .put(path: path, content: content); if (!response.isGood) { _log.severe("[putBinary] Failed requesting server: $response"); throw ApiException( @@ -162,7 +165,7 @@ class FileWebdavDataSource implements FileDataSource { if (OrNull.isSetNull(overrideDateTime)) "app:override-date-time", if (OrNull.isSetNull(location)) "app:location", ]; - final response = await Api(account).files().proppatch( + final response = await ApiUtil.fromAccount(account).files().proppatch( path: f.path, namespaces: { "com.nkming.nc_photos": "app", @@ -188,7 +191,7 @@ class FileWebdavDataSource implements FileDataSource { bool? shouldOverwrite, }) async { _log.info("[copy] ${f.path} to $destination"); - final response = await Api(account).files().copy( + final response = await ApiUtil.fromAccount(account).files().copy( path: f.path, destinationUrl: "${account.url}/$destination", overwrite: shouldOverwrite, @@ -216,7 +219,7 @@ class FileWebdavDataSource implements FileDataSource { bool? shouldOverwrite, }) async { _log.info("[move] ${f.path} to $destination"); - final response = await Api(account).files().move( + final response = await ApiUtil.fromAccount(account).files().move( path: f.path, destinationUrl: "${account.url}/$destination", overwrite: shouldOverwrite, @@ -233,7 +236,7 @@ class FileWebdavDataSource implements FileDataSource { @override createDir(Account account, String path) async { _log.info("[createDir] $path"); - final response = await Api(account).files().mkcol( + final response = await ApiUtil.fromAccount(account).files().mkcol( path: path, ); if (!response.isGood) { @@ -273,7 +276,7 @@ class FileWebdavDataSource implements FileDataSource { Map? customNamespaces, List? customProperties, }) async { - final response = await Api(account).files().propfind( + final response = await ApiUtil.fromAccount(account).files().propfind( path: dir.path, depth: depth, getlastmodified: getlastmodified, @@ -308,11 +311,11 @@ class FileWebdavDataSource implements FileDataSource { "Server responed with an error: HTTP ${response.statusCode}"); } - final xml = XmlDocument.parse(response.body); - var files = await WebdavResponseParser().parseFiles(xml); + final apiFiles = await api.FileParser().parse(response.body); // _log.fine("[list] Parsed files: [$files]"); bool hasNoMediaMarker = false; - files = files + final files = apiFiles + .map(ApiFileConverter.fromApi) .forEachLazy((f) { if (file_util.isNoMediaMarker(f)) { hasNoMediaMarker = true; diff --git a/app/lib/entity/file_descriptor.dart b/app/lib/entity/file_descriptor.dart index 467eaa8a..c2b2c047 100644 --- a/app/lib/entity/file_descriptor.dart +++ b/app/lib/entity/file_descriptor.dart @@ -1,5 +1,5 @@ import 'package:equatable/equatable.dart'; -import 'package:nc_photos/type.dart'; +import 'package:np_common/type.dart'; import 'package:path/path.dart' as path_lib; int compareFileDescriptorDateTimeDescending( diff --git a/app/lib/entity/file_util.dart b/app/lib/entity/file_util.dart index 582999c8..acf23c90 100644 --- a/app/lib/entity/file_util.dart +++ b/app/lib/entity/file_util.dart @@ -1,11 +1,11 @@ import 'package:nc_photos/account.dart'; import 'package:nc_photos/api/api_util.dart' as api_util; -import 'package:nc_photos/ci_string.dart'; import 'package:nc_photos/entity/file.dart'; import 'package:nc_photos/entity/file_descriptor.dart'; import 'package:nc_photos/platform/k.dart' as platform_k; import 'package:nc_photos/remote_storage_util.dart' as remote_storage_util; -import 'package:nc_photos/string_extension.dart'; +import 'package:np_common/ci_string.dart'; +import 'package:np_common/string_extension.dart'; import 'package:path/path.dart' as path_lib; bool isSupportedMime(String mime) => supportedFormatMimes.contains(mime); diff --git a/app/lib/entity/person/data_source.dart b/app/lib/entity/person/data_source.dart index 68a521d3..cd2f4acf 100644 --- a/app/lib/entity/person/data_source.dart +++ b/app/lib/entity/person/data_source.dart @@ -2,13 +2,15 @@ import 'dart:convert'; import 'package:logging/logging.dart'; import 'package:nc_photos/account.dart'; -import 'package:nc_photos/api/api.dart'; +import 'package:nc_photos/api/entity_converter.dart'; import 'package:nc_photos/entity/person.dart'; import 'package:nc_photos/entity/sqlite/database.dart' as sql; import 'package:nc_photos/entity/sqlite/type_converter.dart'; import 'package:nc_photos/exception.dart'; -import 'package:nc_photos/type.dart'; +import 'package:nc_photos/np_api_util.dart'; +import 'package:np_api/np_api.dart' as api; import 'package:np_codegen/np_codegen.dart'; +import 'package:np_common/type.dart'; part 'data_source.g.dart'; @@ -19,18 +21,21 @@ class PersonRemoteDataSource implements PersonDataSource { @override list(Account account) async { _log.info("[list] $account"); - final response = await Api(account).ocs().facerecognition().persons().get(); + final response = await ApiUtil.fromAccount(account) + .ocs() + .facerecognition() + .persons() + .get(); if (!response.isGood) { _log.severe("[list] Failed requesting server: $response"); throw ApiException( - response: response, - message: - "Server responed with an error: HTTP ${response.statusCode}"); + response: response, + message: "Server responed with an error: HTTP ${response.statusCode}", + ); } - final json = jsonDecode(response.body); - final List dataJson = json["ocs"]["data"].cast(); - return _PersonParser().parseList(dataJson); + final apiPersons = await api.PersonParser().parse(response.body); + return apiPersons.map(ApiPersonConverter.fromApi).toList(); } } diff --git a/app/lib/entity/search/data_source.dart b/app/lib/entity/search/data_source.dart index f5c8e481..5f59a760 100644 --- a/app/lib/entity/search/data_source.dart +++ b/app/lib/entity/search/data_source.dart @@ -1,7 +1,6 @@ import 'package:drift/drift.dart' as sql; import 'package:logging/logging.dart'; import 'package:nc_photos/account.dart'; -import 'package:nc_photos/ci_string.dart'; import 'package:nc_photos/di_container.dart'; import 'package:nc_photos/entity/file.dart'; import 'package:nc_photos/entity/file_descriptor.dart'; @@ -15,6 +14,7 @@ import 'package:nc_photos/object_extension.dart'; import 'package:nc_photos/use_case/list_tagged_file.dart'; import 'package:nc_photos/use_case/populate_person.dart'; import 'package:np_codegen/np_codegen.dart'; +import 'package:np_common/ci_string.dart'; part 'data_source.g.dart'; diff --git a/app/lib/entity/share.dart b/app/lib/entity/share.dart index a2d680d5..98de4dfd 100644 --- a/app/lib/entity/share.dart +++ b/app/lib/entity/share.dart @@ -1,8 +1,8 @@ import 'package:equatable/equatable.dart'; import 'package:nc_photos/account.dart'; -import 'package:nc_photos/ci_string.dart'; import 'package:nc_photos/entity/file.dart'; -import 'package:nc_photos/string_extension.dart'; +import 'package:np_common/ci_string.dart'; +import 'package:np_common/string_extension.dart'; import 'package:path/path.dart' as path_lib; import 'package:to_string/to_string.dart'; diff --git a/app/lib/entity/share/data_source.dart b/app/lib/entity/share/data_source.dart index 2738dfbd..490989db 100644 --- a/app/lib/entity/share/data_source.dart +++ b/app/lib/entity/share/data_source.dart @@ -2,14 +2,16 @@ import 'dart:convert'; import 'package:logging/logging.dart'; import 'package:nc_photos/account.dart'; -import 'package:nc_photos/api/api.dart'; -import 'package:nc_photos/ci_string.dart'; +import 'package:nc_photos/api/entity_converter.dart'; import 'package:nc_photos/entity/file.dart'; import 'package:nc_photos/entity/file_descriptor.dart'; import 'package:nc_photos/entity/share.dart'; import 'package:nc_photos/exception.dart'; -import 'package:nc_photos/type.dart'; +import 'package:nc_photos/np_api_util.dart'; +import 'package:np_api/np_api.dart' as api; import 'package:np_codegen/np_codegen.dart'; +import 'package:np_common/ci_string.dart'; +import 'package:np_common/type.dart'; part 'data_source.g.dart'; @@ -22,63 +24,69 @@ class ShareRemoteDataSource implements ShareDataSource { bool? isIncludeReshare, }) async { _log.info("[list] ${file.path}"); - final response = await Api(account).ocs().filesSharing().shares().get( - path: file.strippedPath, - reshares: isIncludeReshare, - ); + final response = + await ApiUtil.fromAccount(account).ocs().filesSharing().shares().get( + path: file.strippedPath, + reshares: isIncludeReshare, + ); return _onListResult(response); } @override listDir(Account account, File dir) async { _log.info("[listDir] ${dir.path}"); - final response = await Api(account).ocs().filesSharing().shares().get( - path: dir.strippedPath, - subfiles: true, - ); + final response = + await ApiUtil.fromAccount(account).ocs().filesSharing().shares().get( + path: dir.strippedPath, + subfiles: true, + ); return _onListResult(response); } @override listAll(Account account) async { _log.info("[listAll] $account"); - final response = await Api(account).ocs().filesSharing().shares().get(); + final response = + await ApiUtil.fromAccount(account).ocs().filesSharing().shares().get(); return _onListResult(response); } @override reverseList(Account account, File file) async { _log.info("[reverseList] ${file.path}"); - final response = await Api(account).ocs().filesSharing().shares().get( - path: file.strippedPath, - sharedWithMe: true, - ); + final response = + await ApiUtil.fromAccount(account).ocs().filesSharing().shares().get( + path: file.strippedPath, + sharedWithMe: true, + ); return _onListResult(response); } @override reverseListAll(Account account) async { _log.info("[reverseListAll] $account"); - final response = await Api(account).ocs().filesSharing().shares().get( - sharedWithMe: true, - ); + final response = + await ApiUtil.fromAccount(account).ocs().filesSharing().shares().get( + sharedWithMe: true, + ); return _onListResult(response); } @override create(Account account, File file, String shareWith) async { _log.info("[create] Share '${file.path}' with '$shareWith'"); - final response = await Api(account).ocs().filesSharing().shares().post( - path: file.strippedPath, - shareType: ShareType.user.toValue(), - shareWith: shareWith, - ); + final response = + await ApiUtil.fromAccount(account).ocs().filesSharing().shares().post( + path: file.strippedPath, + shareType: ShareType.user.toValue(), + shareWith: shareWith, + ); if (!response.isGood) { _log.severe("[create] Failed requesting server: $response"); throw ApiException( - response: response, - message: - "Server responed with an error: HTTP ${response.statusCode}"); + response: response, + message: "Server responed with an error: HTTP ${response.statusCode}", + ); } final json = jsonDecode(response.body); @@ -93,17 +101,18 @@ class ShareRemoteDataSource implements ShareDataSource { String? password, }) async { _log.info("[createLink] Share '${file.path}' with a share link"); - final response = await Api(account).ocs().filesSharing().shares().post( - path: file.strippedPath, - shareType: ShareType.publicLink.toValue(), - password: password, - ); + final response = + await ApiUtil.fromAccount(account).ocs().filesSharing().shares().post( + path: file.strippedPath, + shareType: ShareType.publicLink.toValue(), + password: password, + ); if (!response.isGood) { _log.severe("[create] Failed requesting server: $response"); throw ApiException( - response: response, - message: - "Server responed with an error: HTTP ${response.statusCode}"); + response: response, + message: "Server responed with an error: HTTP ${response.statusCode}", + ); } final json = jsonDecode(response.body); @@ -114,29 +123,31 @@ class ShareRemoteDataSource implements ShareDataSource { @override delete(Account account, Share share) async { _log.info("[delete] $share"); - final response = - await Api(account).ocs().filesSharing().share(share.id).delete(); + final response = await ApiUtil.fromAccount(account) + .ocs() + .filesSharing() + .share(share.id) + .delete(); if (!response.isGood) { _log.severe("[delete] Failed requesting server: $response"); throw ApiException( - response: response, - message: - "Server responed with an error: HTTP ${response.statusCode}"); + response: response, + message: "Server responed with an error: HTTP ${response.statusCode}", + ); } } - List _onListResult(Response response) { + Future> _onListResult(api.Response response) async { if (!response.isGood) { _log.severe("[_onListResult] Failed requesting server: $response"); throw ApiException( - response: response, - message: - "Server responed with an error: HTTP ${response.statusCode}"); + response: response, + message: "Server responed with an error: HTTP ${response.statusCode}", + ); } - final json = jsonDecode(response.body); - final List dataJson = json["ocs"]["data"].cast(); - return _ShareParser().parseList(dataJson); + final apiShares = await api.ShareParser().parse(response.body); + return apiShares.map(ApiShareConverter.fromApi).toList(); } } diff --git a/app/lib/entity/sharee.dart b/app/lib/entity/sharee.dart index 78ccd9d0..e31393b3 100644 --- a/app/lib/entity/sharee.dart +++ b/app/lib/entity/sharee.dart @@ -1,6 +1,6 @@ import 'package:equatable/equatable.dart'; import 'package:nc_photos/account.dart'; -import 'package:nc_photos/ci_string.dart'; +import 'package:np_common/ci_string.dart'; import 'package:to_string/to_string.dart'; part 'sharee.g.dart'; diff --git a/app/lib/entity/sharee/data_source.dart b/app/lib/entity/sharee/data_source.dart index 761985e6..d949439b 100644 --- a/app/lib/entity/sharee/data_source.dart +++ b/app/lib/entity/sharee/data_source.dart @@ -2,12 +2,14 @@ import 'dart:convert'; import 'package:logging/logging.dart'; import 'package:nc_photos/account.dart'; -import 'package:nc_photos/api/api.dart'; -import 'package:nc_photos/ci_string.dart'; +import 'package:nc_photos/api/entity_converter.dart'; import 'package:nc_photos/entity/sharee.dart'; import 'package:nc_photos/exception.dart'; -import 'package:nc_photos/type.dart'; +import 'package:nc_photos/np_api_util.dart'; +import 'package:np_api/np_api.dart' as api; import 'package:np_codegen/np_codegen.dart'; +import 'package:np_common/ci_string.dart'; +import 'package:np_common/type.dart'; part 'data_source.g.dart'; @@ -16,10 +18,11 @@ class ShareeRemoteDataSource implements ShareeDataSource { @override list(Account account) async { _log.info("[list]"); - final response = await Api(account).ocs().filesSharing().sharees().get( - itemType: "file", - lookup: false, - ); + final response = + await ApiUtil.fromAccount(account).ocs().filesSharing().sharees().get( + itemType: "file", + lookup: false, + ); if (!response.isGood) { _log.severe("[list] Failed requesting server: $response"); throw ApiException( @@ -28,9 +31,8 @@ class ShareeRemoteDataSource implements ShareeDataSource { "Server responed with an error: HTTP ${response.statusCode}"); } - final json = jsonDecode(response.body); - final sharees = _ShareeParser()(json); - return sharees; + final apiShares = await api.ShareeParser().parse(response.body); + return apiShares.map(ApiShareeConverter.fromApi).toList(); } } diff --git a/app/lib/entity/sqlite/files_query_builder.dart b/app/lib/entity/sqlite/files_query_builder.dart index 85bba8dd..79c58996 100644 --- a/app/lib/entity/sqlite/files_query_builder.dart +++ b/app/lib/entity/sqlite/files_query_builder.dart @@ -1,8 +1,8 @@ import 'package:drift/drift.dart'; import 'package:nc_photos/account.dart' as app; -import 'package:nc_photos/ci_string.dart'; import 'package:nc_photos/entity/sqlite/database.dart'; import 'package:nc_photos/location_util.dart' as location_util; +import 'package:np_common/ci_string.dart'; enum FilesQueryMode { file, diff --git a/app/lib/entity/sqlite/type_converter.dart b/app/lib/entity/sqlite/type_converter.dart index 708e6247..3f42552d 100644 --- a/app/lib/entity/sqlite/type_converter.dart +++ b/app/lib/entity/sqlite/type_converter.dart @@ -1,7 +1,6 @@ import 'dart:convert'; import 'package:drift/drift.dart'; -import 'package:nc_photos/ci_string.dart'; import 'package:nc_photos/entity/album.dart'; import 'package:nc_photos/entity/album/cover_provider.dart'; import 'package:nc_photos/entity/album/provider.dart'; @@ -15,6 +14,7 @@ import 'package:nc_photos/entity/tag.dart'; import 'package:nc_photos/iterable_extension.dart'; import 'package:nc_photos/object_extension.dart'; import 'package:nc_photos/or_null.dart'; +import 'package:np_common/ci_string.dart'; extension SqlTagListExtension on List { Future> convertToAppTag() { diff --git a/app/lib/entity/tag.dart b/app/lib/entity/tag.dart index b1b7f1a2..ae575942 100644 --- a/app/lib/entity/tag.dart +++ b/app/lib/entity/tag.dart @@ -3,7 +3,7 @@ import 'package:nc_photos/account.dart'; import 'package:nc_photos/entity/file.dart'; import 'package:nc_photos/json_util.dart' as json_util; import 'package:nc_photos/or_null.dart'; -import 'package:nc_photos/type.dart'; +import 'package:np_common/type.dart'; import 'package:to_string/to_string.dart'; part 'tag.g.dart'; diff --git a/app/lib/entity/tag/data_source.dart b/app/lib/entity/tag/data_source.dart index 5fb08623..85a42453 100644 --- a/app/lib/entity/tag/data_source.dart +++ b/app/lib/entity/tag/data_source.dart @@ -1,14 +1,14 @@ import 'package:logging/logging.dart'; import 'package:nc_photos/account.dart'; -import 'package:nc_photos/api/api.dart'; +import 'package:nc_photos/api/entity_converter.dart'; import 'package:nc_photos/entity/file.dart'; import 'package:nc_photos/entity/sqlite/database.dart' as sql; import 'package:nc_photos/entity/sqlite/type_converter.dart'; import 'package:nc_photos/entity/tag.dart'; -import 'package:nc_photos/entity/webdav_response_parser.dart'; import 'package:nc_photos/exception.dart'; +import 'package:nc_photos/np_api_util.dart'; +import 'package:np_api/np_api.dart' as api; import 'package:np_codegen/np_codegen.dart'; -import 'package:xml/xml.dart'; part 'data_source.g.dart'; @@ -19,7 +19,7 @@ class TagRemoteDataSource implements TagDataSource { @override list(Account account) async { _log.info("[list] $account"); - final response = await Api(account).systemtags().propfind( + final response = await ApiUtil.fromAccount(account).systemtags().propfind( id: 1, displayName: 1, userVisible: 1, @@ -33,20 +33,22 @@ class TagRemoteDataSource implements TagDataSource { "Server responed with an error: HTTP ${response.statusCode}"); } - final xml = XmlDocument.parse(response.body); - return WebdavResponseParser().parseTags(xml); + final apiTags = await api.TagParser().parse(response.body); + return apiTags.map(ApiTagConverter.fromApi).toList(); } @override listByFile(Account account, File file) async { _log.info("[listByFile] ${file.path}"); - final response = - await Api(account).systemtagsRelations().files(file.fileId!).propfind( - id: 1, - displayName: 1, - userVisible: 1, - userAssignable: 1, - ); + final response = await ApiUtil.fromAccount(account) + .systemtagsRelations() + .files(file.fileId!) + .propfind( + id: 1, + displayName: 1, + userVisible: 1, + userAssignable: 1, + ); if (!response.isGood) { _log.severe("[listByFile] Failed requesting server: $response"); throw ApiException( @@ -55,8 +57,8 @@ class TagRemoteDataSource implements TagDataSource { "Server responed with an error: HTTP ${response.statusCode}"); } - final xml = XmlDocument.parse(response.body); - return WebdavResponseParser().parseTags(xml); + final apiTags = await api.TagParser().parse(response.body); + return apiTags.map(ApiTagConverter.fromApi).toList(); } } diff --git a/app/lib/entity/tagged_file/data_source.dart b/app/lib/entity/tagged_file/data_source.dart index 5586a4ae..e016290d 100644 --- a/app/lib/entity/tagged_file/data_source.dart +++ b/app/lib/entity/tagged_file/data_source.dart @@ -1,14 +1,14 @@ import 'package:logging/logging.dart'; import 'package:nc_photos/account.dart'; -import 'package:nc_photos/api/api.dart'; +import 'package:nc_photos/api/entity_converter.dart'; import 'package:nc_photos/entity/file.dart'; import 'package:nc_photos/entity/tag.dart'; import 'package:nc_photos/entity/tagged_file.dart'; -import 'package:nc_photos/entity/webdav_response_parser.dart'; import 'package:nc_photos/exception.dart'; import 'package:nc_photos/iterable_extension.dart'; +import 'package:nc_photos/np_api_util.dart'; +import 'package:np_api/np_api.dart' as api; import 'package:np_codegen/np_codegen.dart'; -import 'package:xml/xml.dart'; part 'data_source.g.dart'; @@ -20,7 +20,7 @@ class TaggedFileRemoteDataSource implements TaggedFileDataSource { list(Account account, File dir, List tags) async { _log.info( "[list] ${tags.map((t) => t.displayName).toReadableString()} under ${dir.path}"); - final response = await Api(account).files().report( + final response = await ApiUtil.fromAccount(account).files().report( path: dir.path, systemtag: tags.map((t) => t.id).toList(), ); @@ -32,7 +32,7 @@ class TaggedFileRemoteDataSource implements TaggedFileDataSource { "Server responed with an error: HTTP ${response.statusCode}"); } - final xml = XmlDocument.parse(response.body); - return WebdavResponseParser().parseTaggedFiles(xml); + final apiTaggedFiles = await api.TaggedFileParser().parse(response.body); + return apiTaggedFiles.map(ApiTaggedFileConverter.fromApi).toList(); } } diff --git a/app/lib/entity/webdav_response_parser.dart b/app/lib/entity/webdav_response_parser.dart deleted file mode 100644 index 8655f4ef..00000000 --- a/app/lib/entity/webdav_response_parser.dart +++ /dev/null @@ -1,550 +0,0 @@ -import 'dart:convert'; -import 'dart:io'; - -import 'package:flutter/foundation.dart'; -import 'package:logging/logging.dart'; -import 'package:nc_photos/app_init.dart' as app_init; -import 'package:nc_photos/ci_string.dart'; -import 'package:nc_photos/entity/favorite.dart'; -import 'package:nc_photos/entity/file.dart'; -import 'package:nc_photos/entity/tag.dart'; -import 'package:nc_photos/entity/tagged_file.dart'; -import 'package:nc_photos/string_extension.dart'; -import 'package:np_codegen/np_codegen.dart'; -import 'package:xml/xml.dart'; - -part 'webdav_response_parser.g.dart'; - -@npLog -class WebdavResponseParser { - Future> parseFiles(XmlDocument xml) => - compute(_parseFilesIsolate, xml); - - Future> parseFavorites(XmlDocument xml) => - compute(_parseFavoritesIsolate, xml); - - Future> parseTags(XmlDocument xml) => - compute(_parseTagsIsolate, xml); - - Future> parseTaggedFiles(XmlDocument xml) => - compute(_parseTaggedFilesIsolate, xml); - - Map get namespaces => _namespaces; - - List _parseFiles(XmlDocument xml) => _parse(xml, _toFile); - - List _parseFavorites(XmlDocument xml) => - _parse(xml, _toFavorite); - - List _parseTags(XmlDocument xml) => _parse(xml, _toTag); - - List _parseTaggedFiles(XmlDocument xml) => - _parse(xml, _toTaggedFile); - - List _parse(XmlDocument xml, T? Function(XmlElement) mapper) { - _namespaces = _parseNamespaces(xml); - final body = () { - try { - return xml.children.whereType().firstWhere((element) => - element.matchQualifiedName("multistatus", - prefix: "DAV:", namespaces: _namespaces)); - } catch (_) { - _log.shout("[_parse] Missing element: multistatus"); - rethrow; - } - }(); - return body.children - .whereType() - .where((e) => e.matchQualifiedName("response", - prefix: "DAV:", namespaces: _namespaces)) - .map((e) { - try { - return mapper(e); - } catch (e, stackTrace) { - _log.shout("[_parse] Failed parsing XML", e, stackTrace); - return null; - } - }) - .whereType() - .toList(); - } - - Map _parseNamespaces(XmlDocument xml) { - final namespaces = {}; - final xmlContent = xml.descendants.whereType().firstWhere( - (element) => !element.name.qualified.startsWith("?"), - orElse: () => XmlElement(XmlName.fromString(""))); - for (final a in xmlContent.attributes) { - if (a.name.prefix == "xmlns") { - namespaces[a.name.local] = a.value; - } else if (a.name.local == "xmlns") { - namespaces["!"] = a.value; - } - } - // _log.fine("[_parseNamespaces] Namespaces: $namespaces"); - return namespaces; - } - - /// Map contents to File - File _toFile(XmlElement element) { - String? path; - int? contentLength; - String? contentType; - String? etag; - DateTime? lastModified; - bool? isCollection; - int? usedBytes; - bool? hasPreview; - int? fileId; - bool? isFavorite; - CiString? ownerId; - String? ownerDisplayName; - Metadata? metadata; - bool? isArchived; - DateTime? overrideDateTime; - String? trashbinFilename; - String? trashbinOriginalLocation; - DateTime? trashbinDeletionTime; - ImageLocation? location; - - for (final child in element.children.whereType()) { - if (child.matchQualifiedName("href", - prefix: "DAV:", namespaces: _namespaces)) { - path = _hrefToPath(child); - } else if (child.matchQualifiedName("propstat", - prefix: "DAV:", namespaces: _namespaces)) { - final status = child.children - .whereType() - .firstWhere((element) => element.matchQualifiedName("status", - prefix: "DAV:", namespaces: _namespaces)) - .innerText; - if (!status.contains(" 200 ")) { - continue; - } - final prop = child.children.whereType().firstWhere( - (element) => element.matchQualifiedName("prop", - prefix: "DAV:", namespaces: _namespaces)); - final propParser = - _FilePropParser(namespaces: _namespaces, logFilePath: path); - propParser.parse(prop); - contentLength = propParser.contentLength; - contentType = propParser.contentType; - etag = propParser.etag; - lastModified = propParser.lastModified; - isCollection = propParser.isCollection; - usedBytes = propParser.usedBytes; - hasPreview = propParser.hasPreview; - fileId = propParser.fileId; - isFavorite = propParser.isFavorite; - ownerId = propParser.ownerId; - ownerDisplayName = propParser.ownerDisplayName; - metadata = propParser.metadata; - isArchived = propParser.isArchived; - overrideDateTime = propParser.overrideDateTime; - trashbinFilename = propParser.trashbinFilename; - trashbinOriginalLocation = propParser.trashbinOriginalLocation; - trashbinDeletionTime = propParser.trashbinDeletionTime; - location = propParser.location; - } - } - - return File( - path: path!, - contentLength: contentLength, - contentType: contentType, - etag: etag, - lastModified: lastModified, - isCollection: isCollection, - usedBytes: usedBytes, - hasPreview: hasPreview, - fileId: fileId, - isFavorite: isFavorite, - ownerId: ownerId, - ownerDisplayName: ownerDisplayName, - metadata: metadata, - isArchived: isArchived, - overrideDateTime: overrideDateTime, - trashbinFilename: trashbinFilename, - trashbinOriginalLocation: trashbinOriginalLocation, - trashbinDeletionTime: trashbinDeletionTime, - location: location, - ); - } - - /// Map contents to Favorite - Favorite _toFavorite(XmlElement element) { - String? path; - int? fileId; - - for (final child in element.children.whereType()) { - if (child.matchQualifiedName("href", - prefix: "DAV:", namespaces: _namespaces)) { - path = _hrefToPath(child); - } else if (child.matchQualifiedName("propstat", - prefix: "DAV:", namespaces: _namespaces)) { - final status = child.children - .whereType() - .firstWhere((element) => element.matchQualifiedName("status", - prefix: "DAV:", namespaces: _namespaces)) - .innerText; - if (!status.contains(" 200 ")) { - continue; - } - final prop = child.children.whereType().firstWhere( - (element) => element.matchQualifiedName("prop", - prefix: "DAV:", namespaces: _namespaces)); - final propParser = - _FileIdPropParser(namespaces: _namespaces, logFilePath: path); - propParser.parse(prop); - fileId = propParser.fileId; - } - } - - return Favorite( - fileId: fileId!, - ); - } - - /// Map contents to Tag - Tag? _toTag(XmlElement element) { - String? path; - int? id; - String? displayName; - bool? userVisible; - bool? userAssignable; - - for (final child in element.children.whereType()) { - if (child.matchQualifiedName("href", - prefix: "DAV:", namespaces: _namespaces)) { - path = _hrefToPath(child); - } else if (child.matchQualifiedName("propstat", - prefix: "DAV:", namespaces: _namespaces)) { - final status = child.children - .whereType() - .firstWhere((element) => element.matchQualifiedName("status", - prefix: "DAV:", namespaces: _namespaces)) - .innerText; - if (!status.contains(" 200 ")) { - continue; - } - final prop = child.children.whereType().firstWhere( - (element) => element.matchQualifiedName("prop", - prefix: "DAV:", namespaces: _namespaces)); - final propParser = - _TagPropParser(namespaces: _namespaces, logFilePath: path); - propParser.parse(prop); - id = propParser.id; - displayName = propParser.displayName; - userVisible = propParser.userVisible; - userAssignable = propParser.userAssignable; - } - } - if (id == null) { - // the first returned item is not a valid tag - return null; - } - - return Tag( - id: id, - displayName: displayName!, - userVisible: userVisible!, - userAssignable: userAssignable!, - ); - } - - /// Map contents to TaggedFile - TaggedFile _toTaggedFile(XmlElement element) { - String? path; - int? fileId; - - for (final child in element.children.whereType()) { - if (child.matchQualifiedName("href", - prefix: "DAV:", namespaces: _namespaces)) { - path = _hrefToPath(child); - } else if (child.matchQualifiedName("propstat", - prefix: "DAV:", namespaces: _namespaces)) { - final status = child.children - .whereType() - .firstWhere((element) => element.matchQualifiedName("status", - prefix: "DAV:", namespaces: _namespaces)) - .innerText; - if (!status.contains(" 200 ")) { - continue; - } - final prop = child.children.whereType().firstWhere( - (element) => element.matchQualifiedName("prop", - prefix: "DAV:", namespaces: _namespaces)); - final propParser = - _FileIdPropParser(namespaces: _namespaces, logFilePath: path); - propParser.parse(prop); - fileId = propParser.fileId; - } - } - - return TaggedFile( - fileId: fileId!, - ); - } - - String _hrefToPath(XmlElement href) { - final rawPath = Uri.decodeComponent(href.innerText).trimLeftAny("/"); - final pos = rawPath.indexOf("remote.php"); - if (pos == -1) { - // what? - _log.warning("[_hrefToPath] Unknown href value: $rawPath"); - return rawPath; - } else { - return rawPath.substring(pos); - } - } - - var _namespaces = {}; -} - -class _FilePropParser { - _FilePropParser({ - this.namespaces = const {}, - this.logFilePath, - }); - - /// Parse element contents - void parse(XmlElement element) { - for (final child in element.children.whereType()) { - if (child.matchQualifiedName("getlastmodified", - prefix: "DAV:", namespaces: namespaces)) { - _lastModified = HttpDate.parse(child.innerText); - } else if (child.matchQualifiedName("getcontentlength", - prefix: "DAV:", namespaces: namespaces)) { - _contentLength = int.parse(child.innerText); - } else if (child.matchQualifiedName("getcontenttype", - prefix: "DAV:", namespaces: namespaces)) { - _contentType = child.innerText; - } else if (child.matchQualifiedName("getetag", - prefix: "DAV:", namespaces: namespaces)) { - _etag = child.innerText.replaceAll("\"", ""); - } else if (child.matchQualifiedName("quota-used-bytes", - prefix: "DAV:", namespaces: namespaces)) { - _usedBytes = int.parse(child.innerText); - } else if (child.matchQualifiedName("resourcetype", - prefix: "DAV:", namespaces: namespaces)) { - _isCollection = child.children.whereType().any((element) => - element.matchQualifiedName("collection", - prefix: "DAV:", namespaces: namespaces)); - } else if (child.matchQualifiedName("has-preview", - prefix: "http://nextcloud.org/ns", namespaces: namespaces)) { - _hasPreview = child.innerText == "true"; - } else if (child.matchQualifiedName("fileid", - prefix: "http://owncloud.org/ns", namespaces: namespaces)) { - _fileId = int.parse(child.innerText); - } else if (child.matchQualifiedName("favorite", - prefix: "http://owncloud.org/ns", namespaces: namespaces)) { - _isFavorite = child.innerText != "0"; - } else if (child.matchQualifiedName("owner-id", - prefix: "http://owncloud.org/ns", namespaces: namespaces)) { - _ownerId = child.innerText.toCi(); - } else if (child.matchQualifiedName("owner-display-name", - prefix: "http://owncloud.org/ns", namespaces: namespaces)) { - _ownerDisplayName = child.innerText; - } else if (child.matchQualifiedName("trashbin-filename", - prefix: "http://nextcloud.org/ns", namespaces: namespaces)) { - _trashbinFilename = child.innerText; - } else if (child.matchQualifiedName("trashbin-original-location", - prefix: "http://nextcloud.org/ns", namespaces: namespaces)) { - _trashbinOriginalLocation = child.innerText; - } else if (child.matchQualifiedName("trashbin-deletion-time", - prefix: "http://nextcloud.org/ns", namespaces: namespaces)) { - _trashbinDeletionTime = DateTime.fromMillisecondsSinceEpoch( - int.parse(child.innerText) * 1000); - } else if (child.matchQualifiedName("is-archived", - prefix: "com.nkming.nc_photos", namespaces: namespaces)) { - _isArchived = child.innerText == "true"; - } else if (child.matchQualifiedName("override-date-time", - prefix: "com.nkming.nc_photos", namespaces: namespaces)) { - _overrideDateTime = DateTime.parse(child.innerText); - } else if (child.matchQualifiedName("location", - prefix: "com.nkming.nc_photos", namespaces: namespaces)) { - _location = ImageLocation.fromJson(jsonDecode(child.innerText)); - } - } - // 2nd pass that depends on data in 1st pass - for (final child in element.children.whereType()) { - if (child.matchQualifiedName("metadata", - prefix: "com.nkming.nc_photos", namespaces: namespaces)) { - _metadata = Metadata.fromJson( - jsonDecode(child.innerText), - upgraderV1: MetadataUpgraderV1( - fileContentType: _contentType, - logFilePath: logFilePath, - ), - upgraderV2: MetadataUpgraderV2( - fileContentType: _contentType, - logFilePath: logFilePath, - ), - upgraderV3: MetadataUpgraderV3( - fileContentType: _contentType, - logFilePath: logFilePath, - ), - ); - } - } - } - - DateTime? get lastModified => _lastModified; - int? get contentLength => _contentLength; - String? get contentType => _contentType; - String? get etag => _etag; - int? get usedBytes => _usedBytes; - bool? get isCollection => _isCollection; - bool? get hasPreview => _hasPreview; - int? get fileId => _fileId; - bool? get isFavorite => _isFavorite; - CiString? get ownerId => _ownerId; - String? get ownerDisplayName => _ownerDisplayName; - Metadata? get metadata => _metadata; - bool? get isArchived => _isArchived; - DateTime? get overrideDateTime => _overrideDateTime; - String? get trashbinFilename => _trashbinFilename; - String? get trashbinOriginalLocation => _trashbinOriginalLocation; - DateTime? get trashbinDeletionTime => _trashbinDeletionTime; - ImageLocation? get location => _location; - - final Map namespaces; - - /// File path for logging only - final String? logFilePath; - - DateTime? _lastModified; - int? _contentLength; - String? _contentType; - String? _etag; - int? _usedBytes; - bool? _isCollection; - bool? _hasPreview; - int? _fileId; - bool? _isFavorite; - CiString? _ownerId; - String? _ownerDisplayName; - Metadata? _metadata; - bool? _isArchived; - DateTime? _overrideDateTime; - String? _trashbinFilename; - String? _trashbinOriginalLocation; - DateTime? _trashbinDeletionTime; - ImageLocation? _location; -} - -class _FileIdPropParser { - _FileIdPropParser({ - this.namespaces = const {}, - this.logFilePath, - }); - - /// Parse element contents - void parse(XmlElement element) { - for (final child in element.children.whereType()) { - if (child.matchQualifiedName("fileid", - prefix: "http://owncloud.org/ns", namespaces: namespaces)) { - _fileId = int.parse(child.innerText); - } - } - } - - int? get fileId => _fileId; - - final Map namespaces; - - /// File path for logging only - final String? logFilePath; - - int? _fileId; -} - -class _TagPropParser { - _TagPropParser({ - this.namespaces = const {}, - this.logFilePath, - }); - - /// Parse element contents - void parse(XmlElement element) { - for (final child in element.children.whereType()) { - if (child.matchQualifiedName("id", - prefix: "http://owncloud.org/ns", namespaces: namespaces)) { - _id = int.parse(child.innerText); - } else if (child.matchQualifiedName("display-name", - prefix: "http://owncloud.org/ns", namespaces: namespaces)) { - _displayName = child.innerText; - } else if (child.matchQualifiedName("user-visible", - prefix: "http://owncloud.org/ns", namespaces: namespaces)) { - _userVisible = child.innerText == "true"; - } else if (child.matchQualifiedName("user-assignable", - prefix: "http://owncloud.org/ns", namespaces: namespaces)) { - _userAssignable = child.innerText == "true"; - } - } - } - - int? get id => _id; - String? get displayName => _displayName; - bool? get userVisible => _userVisible; - bool? get userAssignable => _userAssignable; - - final Map namespaces; - - /// File path for logging only - final String? logFilePath; - - int? _id; - String? _displayName; - bool? _userVisible; - bool? _userAssignable; -} - -extension on XmlElement { - bool matchQualifiedName( - String local, { - required String prefix, - required Map namespaces, - }) { - final localNamespaces = {}; - for (final a in attributes) { - if (a.name.prefix == "xmlns") { - localNamespaces[a.name.local] = a.value; - } else if (a.name.local == "xmlns") { - localNamespaces["!"] = a.value; - } - } - return name.local == local && - (name.prefix == prefix || - // match default namespace - (name.prefix == null && namespaces["!"] == prefix) || - // match global namespace - namespaces.entries - .where((element2) => element2.value == prefix) - .any((element) => element.key == name.prefix) || - // match local namespace - localNamespaces.entries - .where((element2) => element2.value == prefix) - .any((element) => element.key == name.prefix)); - } -} - -List _parseFilesIsolate(XmlDocument xml) { - app_init.initLog(); - return WebdavResponseParser()._parseFiles(xml); -} - -List _parseFavoritesIsolate(XmlDocument xml) { - app_init.initLog(); - return WebdavResponseParser()._parseFavorites(xml); -} - -List _parseTagsIsolate(XmlDocument xml) { - app_init.initLog(); - return WebdavResponseParser()._parseTags(xml); -} - -List _parseTaggedFilesIsolate(XmlDocument xml) { - app_init.initLog(); - return WebdavResponseParser()._parseTaggedFiles(xml); -} diff --git a/app/lib/exception.dart b/app/lib/exception.dart index dac3c672..9a26bac7 100644 --- a/app/lib/exception.dart +++ b/app/lib/exception.dart @@ -1,4 +1,4 @@ -import 'package:nc_photos/api/api.dart'; +import 'package:np_api/np_api.dart'; class CacheNotFoundException implements Exception { CacheNotFoundException([this.message]); diff --git a/app/lib/legacy/app_password_exchange_bloc.dart b/app/lib/legacy/app_password_exchange_bloc.dart index 4c90cd0e..1a43b9d4 100644 --- a/app/lib/legacy/app_password_exchange_bloc.dart +++ b/app/lib/legacy/app_password_exchange_bloc.dart @@ -4,8 +4,8 @@ import 'package:bloc/bloc.dart'; import 'package:flutter/foundation.dart'; import 'package:logging/logging.dart'; import 'package:nc_photos/account.dart'; -import 'package:nc_photos/api/api.dart'; import 'package:nc_photos/exception.dart'; +import 'package:nc_photos/np_api_util.dart'; import 'package:np_codegen/np_codegen.dart'; import 'package:to_string/to_string.dart'; @@ -96,7 +96,7 @@ class AppPasswordExchangeBloc /// Query the app password for [account] static Future _exchangePassword(Account account) async { - final response = await Api(account).request( + final response = await ApiUtil.fromAccount(account).request( "GET", "ocs/v2.php/core/getapppassword", header: { diff --git a/app/lib/legacy/connect.dart b/app/lib/legacy/connect.dart index 1e330648..1e33ae30 100644 --- a/app/lib/legacy/connect.dart +++ b/app/lib/legacy/connect.dart @@ -6,7 +6,6 @@ import 'package:kiwi/kiwi.dart'; import 'package:logging/logging.dart'; import 'package:nc_photos/account.dart'; import 'package:nc_photos/app_localizations.dart'; -import 'package:nc_photos/ci_string.dart'; import 'package:nc_photos/di_container.dart'; import 'package:nc_photos/entity/file_util.dart' as file_util; import 'package:nc_photos/exception.dart'; @@ -17,10 +16,11 @@ import 'package:nc_photos/legacy/app_password_exchange_bloc.dart'; import 'package:nc_photos/mobile/self_signed_cert_manager.dart'; import 'package:nc_photos/platform/features.dart' as features; import 'package:nc_photos/snack_bar_manager.dart'; -import 'package:nc_photos/string_extension.dart'; import 'package:nc_photos/url_launcher_util.dart'; import 'package:nc_photos/use_case/ls_single_file.dart'; import 'package:np_codegen/np_codegen.dart'; +import 'package:np_common/ci_string.dart'; +import 'package:np_common/string_extension.dart'; part 'connect.g.dart'; diff --git a/app/lib/legacy/sign_in.dart b/app/lib/legacy/sign_in.dart index 01d49573..51cc87e7 100644 --- a/app/lib/legacy/sign_in.dart +++ b/app/lib/legacy/sign_in.dart @@ -5,7 +5,6 @@ import 'package:kiwi/kiwi.dart'; import 'package:logging/logging.dart'; import 'package:nc_photos/account.dart'; import 'package:nc_photos/app_localizations.dart'; -import 'package:nc_photos/ci_string.dart'; import 'package:nc_photos/di_container.dart'; import 'package:nc_photos/entity/sqlite/database.dart' as sql; import 'package:nc_photos/help_utils.dart' as help_utils; @@ -14,12 +13,13 @@ import 'package:nc_photos/legacy/connect.dart'; import 'package:nc_photos/platform/k.dart' as platform_k; import 'package:nc_photos/pref.dart'; import 'package:nc_photos/pref_util.dart' as pref_util; -import 'package:nc_photos/string_extension.dart'; import 'package:nc_photos/theme.dart'; import 'package:nc_photos/url_launcher_util.dart'; import 'package:nc_photos/widget/home.dart'; import 'package:nc_photos/widget/root_picker.dart'; import 'package:np_codegen/np_codegen.dart'; +import 'package:np_common/ci_string.dart'; +import 'package:np_common/string_extension.dart'; part 'sign_in.g.dart'; diff --git a/app/lib/location_util.dart b/app/lib/location_util.dart index d1f52f39..9300a6be 100644 --- a/app/lib/location_util.dart +++ b/app/lib/location_util.dart @@ -1,4 +1,4 @@ -import 'package:nc_photos/ci_string.dart'; +import 'package:np_common/ci_string.dart'; /// Convert a ISO 3166-1 alpha-2 code into country name String? alpha2CodeToName(String cc) => _ccMap.byCc(cc); diff --git a/app/lib/mobile/self_signed_cert_manager.dart b/app/lib/mobile/self_signed_cert_manager.dart index 04d163b2..ea24cdac 100644 --- a/app/lib/mobile/self_signed_cert_manager.dart +++ b/app/lib/mobile/self_signed_cert_manager.dart @@ -4,8 +4,8 @@ import 'dart:typed_data'; import 'package:logging/logging.dart'; import 'package:nc_photos/mobile/android/self_signed_cert.dart'; -import 'package:nc_photos/type.dart'; import 'package:np_codegen/np_codegen.dart'; +import 'package:np_common/type.dart'; import 'package:path/path.dart' as path_lib; import 'package:path_provider/path_provider.dart'; import 'package:uuid/uuid.dart'; diff --git a/app/lib/mobile/universal_storage.dart b/app/lib/mobile/universal_storage.dart index f1e5e0dd..0c22a357 100644 --- a/app/lib/mobile/universal_storage.dart +++ b/app/lib/mobile/universal_storage.dart @@ -2,7 +2,7 @@ import 'dart:io'; import 'dart:typed_data'; import 'package:nc_photos/platform/universal_storage.dart' as itf; -import 'package:nc_photos/string_extension.dart'; +import 'package:np_common/string_extension.dart'; import 'package:path/path.dart' as path_lib; import 'package:path_provider/path_provider.dart'; diff --git a/app/lib/np_api_util.dart b/app/lib/np_api_util.dart new file mode 100644 index 00000000..52732212 --- /dev/null +++ b/app/lib/np_api_util.dart @@ -0,0 +1,14 @@ +import 'package:nc_photos/account.dart'; +import 'package:np_api/np_api.dart'; + +class ApiUtil { + static Api fromAccount(Account account) => Api( + Uri.parse(account.url), + BasicAuth(account.username2, account.password), + ); +} + +class AuthUtil { + static BasicAuth fromAccount(Account account) => + BasicAuth(account.username2, account.password); +} diff --git a/app/lib/use_case/compat/v32.dart b/app/lib/use_case/compat/v32.dart index 6ee4a6ce..01a4325a 100644 --- a/app/lib/use_case/compat/v32.dart +++ b/app/lib/use_case/compat/v32.dart @@ -2,8 +2,8 @@ import 'dart:convert'; import 'package:logging/logging.dart'; import 'package:nc_photos/entity/exif.dart'; -import 'package:nc_photos/type.dart'; import 'package:np_codegen/np_codegen.dart'; +import 'package:np_common/type.dart'; import 'package:shared_preferences/shared_preferences.dart'; part 'v32.g.dart'; diff --git a/app/lib/use_case/compat/v34.dart b/app/lib/use_case/compat/v34.dart index aff4b8f6..0cbb854c 100644 --- a/app/lib/use_case/compat/v34.dart +++ b/app/lib/use_case/compat/v34.dart @@ -3,8 +3,8 @@ import 'dart:convert'; import 'package:logging/logging.dart'; import 'package:nc_photos/account.dart'; import 'package:nc_photos/platform/universal_storage.dart'; -import 'package:nc_photos/type.dart'; import 'package:np_codegen/np_codegen.dart'; +import 'package:np_common/type.dart'; import 'package:shared_preferences/shared_preferences.dart'; part 'v34.g.dart'; diff --git a/app/lib/use_case/download_file.dart b/app/lib/use_case/download_file.dart index 200b12ce..7c29f8d3 100644 --- a/app/lib/use_case/download_file.dart +++ b/app/lib/use_case/download_file.dart @@ -1,9 +1,9 @@ import 'package:nc_photos/account.dart'; -import 'package:nc_photos/api/api.dart'; import 'package:nc_photos/entity/file.dart'; import 'package:nc_photos/entity/file_descriptor.dart'; import 'package:nc_photos/mobile/platform.dart' if (dart.library.html) 'package:nc_photos/web/platform.dart' as platform; +import 'package:nc_photos/np_api_util.dart'; import 'package:nc_photos/platform/download.dart'; class DownloadFile { @@ -18,7 +18,7 @@ class DownloadFile { return platform.DownloadBuilder().build( url: url, headers: { - "authorization": Api.getAuthorizationHeaderValue(account), + "authorization": AuthUtil.fromAccount(account).toHeaderValue(), }, mimeType: file.contentType, filename: file.filename, diff --git a/app/lib/use_case/download_preview.dart b/app/lib/use_case/download_preview.dart index cf42296e..8d3c3c5b 100644 --- a/app/lib/use_case/download_preview.dart +++ b/app/lib/use_case/download_preview.dart @@ -1,9 +1,9 @@ import 'package:nc_photos/account.dart'; -import 'package:nc_photos/api/api.dart'; import 'package:nc_photos/api/api_util.dart' as api_util; import 'package:nc_photos/cache_manager_util.dart'; import 'package:nc_photos/entity/file.dart'; import 'package:nc_photos/k.dart' as k; +import 'package:nc_photos/np_api_util.dart'; import 'package:nc_photos/platform/k.dart' as platform_k; import 'package:nc_photos_plugin/nc_photos_plugin.dart'; @@ -19,7 +19,7 @@ class DownloadPreview { ); final fileInfo = await LargeImageCacheManager.inst.getSingleFile(previewUrl, headers: { - "authorization": Api.getAuthorizationHeaderValue(account), + "authorization": AuthUtil.fromAccount(account).toHeaderValue(), }); return ContentUri.getUriForFile(fileInfo.absolute.path); } diff --git a/app/lib/use_case/ls.dart b/app/lib/use_case/ls.dart index 610af349..5e88deac 100644 --- a/app/lib/use_case/ls.dart +++ b/app/lib/use_case/ls.dart @@ -1,6 +1,6 @@ import 'package:nc_photos/account.dart'; import 'package:nc_photos/entity/file.dart'; -import 'package:nc_photos/string_extension.dart'; +import 'package:np_common/string_extension.dart'; class Ls { Ls(this.fileRepo); diff --git a/app/lib/use_case/remove.dart b/app/lib/use_case/remove.dart index afc7249a..a262e9c8 100644 --- a/app/lib/use_case/remove.dart +++ b/app/lib/use_case/remove.dart @@ -2,7 +2,6 @@ import 'package:event_bus/event_bus.dart'; import 'package:kiwi/kiwi.dart'; import 'package:logging/logging.dart'; import 'package:nc_photos/account.dart'; -import 'package:nc_photos/ci_string.dart'; import 'package:nc_photos/debug_util.dart'; import 'package:nc_photos/di_container.dart'; import 'package:nc_photos/entity/album.dart'; @@ -18,6 +17,7 @@ import 'package:nc_photos/use_case/list_share.dart'; import 'package:nc_photos/use_case/remove_from_album.dart'; import 'package:nc_photos/use_case/remove_share.dart'; import 'package:np_codegen/np_codegen.dart'; +import 'package:np_common/ci_string.dart'; part 'remove.g.dart'; diff --git a/app/lib/use_case/request_public_link.dart b/app/lib/use_case/request_public_link.dart index ebcc83aa..da0ed0f5 100644 --- a/app/lib/use_case/request_public_link.dart +++ b/app/lib/use_case/request_public_link.dart @@ -2,11 +2,11 @@ import 'dart:convert'; import 'package:logging/logging.dart'; import 'package:nc_photos/account.dart'; -import 'package:nc_photos/api/api.dart'; import 'package:nc_photos/entity/file_descriptor.dart'; import 'package:nc_photos/exception.dart'; -import 'package:nc_photos/type.dart'; +import 'package:nc_photos/np_api_util.dart'; import 'package:np_codegen/np_codegen.dart'; +import 'package:np_common/type.dart'; part 'request_public_link.g.dart'; @@ -14,8 +14,11 @@ part 'request_public_link.g.dart'; class RequestPublicLink { /// Request a temporary unique public link to [file] Future call(Account account, FileDescriptor file) async { - final response = - await Api(account).ocs().dav().direct().post(fileId: file.fdId); + final response = await ApiUtil.fromAccount(account) + .ocs() + .dav() + .direct() + .post(fileId: file.fdId); if (!response.isGood) { _log.severe("[call] Failed requesting server: $response"); throw ApiException( diff --git a/app/lib/use_case/share_album_with_user.dart b/app/lib/use_case/share_album_with_user.dart index 3de28b89..2b02beb2 100644 --- a/app/lib/use_case/share_album_with_user.dart +++ b/app/lib/use_case/share_album_with_user.dart @@ -1,6 +1,5 @@ import 'package:logging/logging.dart'; import 'package:nc_photos/account.dart'; -import 'package:nc_photos/ci_string.dart'; import 'package:nc_photos/debug_util.dart'; import 'package:nc_photos/entity/album.dart'; import 'package:nc_photos/entity/album/item.dart'; @@ -13,6 +12,7 @@ import 'package:nc_photos/or_null.dart'; import 'package:nc_photos/use_case/create_share.dart'; import 'package:nc_photos/use_case/update_album.dart'; import 'package:np_codegen/np_codegen.dart'; +import 'package:np_common/ci_string.dart'; part 'share_album_with_user.g.dart'; diff --git a/app/lib/use_case/startup_sync.dart b/app/lib/use_case/startup_sync.dart index 4899e5d0..f50688d2 100644 --- a/app/lib/use_case/startup_sync.dart +++ b/app/lib/use_case/startup_sync.dart @@ -9,11 +9,11 @@ import 'package:nc_photos/app_init.dart' as app_init; import 'package:nc_photos/di_container.dart'; import 'package:nc_photos/event/event.dart'; import 'package:nc_photos/platform/k.dart' as platform_k; -import 'package:nc_photos/type.dart'; import 'package:nc_photos/use_case/sync_favorite.dart'; import 'package:nc_photos/use_case/sync_person.dart'; import 'package:nc_photos/use_case/sync_tag.dart'; import 'package:np_codegen/np_codegen.dart'; +import 'package:np_common/type.dart'; part 'startup_sync.g.dart'; diff --git a/app/lib/use_case/unshare_album_with_user.dart b/app/lib/use_case/unshare_album_with_user.dart index 77fc6457..be9b1e26 100644 --- a/app/lib/use_case/unshare_album_with_user.dart +++ b/app/lib/use_case/unshare_album_with_user.dart @@ -1,6 +1,5 @@ import 'package:logging/logging.dart'; import 'package:nc_photos/account.dart'; -import 'package:nc_photos/ci_string.dart'; import 'package:nc_photos/debug_util.dart'; import 'package:nc_photos/di_container.dart'; import 'package:nc_photos/entity/album.dart'; @@ -13,6 +12,7 @@ import 'package:nc_photos/use_case/remove_share.dart'; import 'package:nc_photos/use_case/unshare_file_from_album.dart'; import 'package:nc_photos/use_case/update_album.dart'; import 'package:np_codegen/np_codegen.dart'; +import 'package:np_common/ci_string.dart'; part 'unshare_album_with_user.g.dart'; diff --git a/app/lib/use_case/unshare_file_from_album.dart b/app/lib/use_case/unshare_file_from_album.dart index 7119196d..e2b8d3ac 100644 --- a/app/lib/use_case/unshare_file_from_album.dart +++ b/app/lib/use_case/unshare_file_from_album.dart @@ -1,6 +1,5 @@ import 'package:logging/logging.dart'; import 'package:nc_photos/account.dart'; -import 'package:nc_photos/ci_string.dart'; import 'package:nc_photos/debug_util.dart'; import 'package:nc_photos/di_container.dart'; import 'package:nc_photos/entity/album.dart'; @@ -14,6 +13,7 @@ import 'package:nc_photos/use_case/list_album.dart'; import 'package:nc_photos/use_case/list_share.dart'; import 'package:nc_photos/use_case/remove_share.dart'; import 'package:np_codegen/np_codegen.dart'; +import 'package:np_common/ci_string.dart'; part 'unshare_file_from_album.g.dart'; diff --git a/app/lib/widget/album_browser_app_bar.dart b/app/lib/widget/album_browser_app_bar.dart index 7aed8671..2a0089e5 100644 --- a/app/lib/widget/album_browser_app_bar.dart +++ b/app/lib/widget/album_browser_app_bar.dart @@ -2,10 +2,10 @@ import 'package:cached_network_image/cached_network_image.dart'; import 'package:cached_network_image_platform_interface/cached_network_image_platform_interface.dart'; import 'package:flutter/material.dart'; import 'package:nc_photos/account.dart'; -import 'package:nc_photos/api/api.dart'; import 'package:nc_photos/app_localizations.dart'; import 'package:nc_photos/cache_manager_util.dart'; import 'package:nc_photos/entity/album.dart'; +import 'package:nc_photos/np_api_util.dart'; class AlbumBrowserAppBar extends StatelessWidget { const AlbumBrowserAppBar({ @@ -124,7 +124,7 @@ Widget? _getAppBarCover( cacheManager: CoverCacheManager.inst, imageUrl: coverPreviewUrl, httpHeaders: { - "Authorization": Api.getAuthorizationHeaderValue(account), + "Authorization": AuthUtil.fromAccount(account).toHeaderValue(), }, filterQuality: FilterQuality.high, errorWidget: (context, url, error) { diff --git a/app/lib/widget/album_share_outlier_browser.dart b/app/lib/widget/album_share_outlier_browser.dart index 4a994d35..aae5321d 100644 --- a/app/lib/widget/album_share_outlier_browser.dart +++ b/app/lib/widget/album_share_outlier_browser.dart @@ -5,7 +5,6 @@ import 'package:logging/logging.dart'; import 'package:nc_photos/account.dart'; import 'package:nc_photos/app_localizations.dart'; import 'package:nc_photos/bloc/list_album_share_outlier.dart'; -import 'package:nc_photos/ci_string.dart'; import 'package:nc_photos/di_container.dart'; import 'package:nc_photos/entity/album.dart'; import 'package:nc_photos/entity/file.dart'; @@ -16,13 +15,14 @@ import 'package:nc_photos/entity/share/data_source.dart'; import 'package:nc_photos/exception_util.dart' as exception_util; import 'package:nc_photos/k.dart' as k; import 'package:nc_photos/snack_bar_manager.dart'; -import 'package:nc_photos/string_extension.dart'; import 'package:nc_photos/use_case/create_share.dart'; import 'package:nc_photos/use_case/remove_share.dart'; import 'package:nc_photos/widget/empty_list_indicator.dart'; import 'package:nc_photos/widget/network_thumbnail.dart'; import 'package:nc_photos/widget/unbounded_list_tile.dart'; import 'package:np_codegen/np_codegen.dart'; +import 'package:np_common/ci_string.dart'; +import 'package:np_common/string_extension.dart'; part 'album_share_outlier_browser.g.dart'; diff --git a/app/lib/widget/builder/album_grid_item_builder.dart b/app/lib/widget/builder/album_grid_item_builder.dart index 4b81e22c..bdb1456e 100644 --- a/app/lib/widget/builder/album_grid_item_builder.dart +++ b/app/lib/widget/builder/album_grid_item_builder.dart @@ -2,7 +2,6 @@ import 'package:cached_network_image/cached_network_image.dart'; import 'package:cached_network_image_platform_interface/cached_network_image_platform_interface.dart'; import 'package:flutter/material.dart'; import 'package:nc_photos/account.dart'; -import 'package:nc_photos/api/api.dart'; import 'package:nc_photos/api/api_util.dart' as api_util; import 'package:nc_photos/app_localizations.dart'; import 'package:nc_photos/cache_manager_util.dart'; @@ -10,6 +9,7 @@ import 'package:nc_photos/entity/album.dart'; import 'package:nc_photos/entity/album/provider.dart'; import 'package:nc_photos/entity/file_descriptor.dart'; import 'package:nc_photos/k.dart' as k; +import 'package:nc_photos/np_api_util.dart'; import 'package:nc_photos/theme.dart'; import 'package:nc_photos/widget/album_grid_item.dart'; @@ -65,7 +65,7 @@ class AlbumGridItemBuilder { cacheManager: CoverCacheManager.inst, imageUrl: previewUrl, httpHeaders: { - "Authorization": Api.getAuthorizationHeaderValue(account), + "Authorization": AuthUtil.fromAccount(account).toHeaderValue(), }, fadeInDuration: const Duration(), filterQuality: FilterQuality.high, diff --git a/app/lib/widget/connect.dart b/app/lib/widget/connect.dart index 3495ed87..4f72085a 100644 --- a/app/lib/widget/connect.dart +++ b/app/lib/widget/connect.dart @@ -7,7 +7,6 @@ import 'package:logging/logging.dart'; import 'package:nc_photos/account.dart'; import 'package:nc_photos/app_localizations.dart'; import 'package:nc_photos/bloc/app_password_exchange.dart'; -import 'package:nc_photos/ci_string.dart'; import 'package:nc_photos/di_container.dart'; import 'package:nc_photos/entity/file_util.dart' as file_util; import 'package:nc_photos/exception.dart'; @@ -17,12 +16,13 @@ import 'package:nc_photos/k.dart' as k; import 'package:nc_photos/mobile/self_signed_cert_manager.dart'; import 'package:nc_photos/platform/features.dart' as features; import 'package:nc_photos/snack_bar_manager.dart'; -import 'package:nc_photos/string_extension.dart'; import 'package:nc_photos/theme.dart'; import 'package:nc_photos/url_launcher_util.dart'; import 'package:nc_photos/use_case/ls_single_file.dart'; import 'package:nc_photos/widget/cloud_progress_indicator.dart'; import 'package:np_codegen/np_codegen.dart'; +import 'package:np_common/ci_string.dart'; +import 'package:np_common/string_extension.dart'; part 'connect.g.dart'; diff --git a/app/lib/widget/home_search_suggestion.dart b/app/lib/widget/home_search_suggestion.dart index 0160e2f1..55f7b5b3 100644 --- a/app/lib/widget/home_search_suggestion.dart +++ b/app/lib/widget/home_search_suggestion.dart @@ -3,7 +3,6 @@ import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:logging/logging.dart'; import 'package:nc_photos/account.dart'; import 'package:nc_photos/bloc/home_search_suggestion.dart'; -import 'package:nc_photos/ci_string.dart'; import 'package:nc_photos/entity/album.dart'; import 'package:nc_photos/entity/person.dart'; import 'package:nc_photos/entity/tag.dart'; @@ -18,6 +17,7 @@ import 'package:nc_photos/widget/person_browser.dart'; import 'package:nc_photos/widget/place_browser.dart'; import 'package:nc_photos/widget/tag_browser.dart'; import 'package:np_codegen/np_codegen.dart'; +import 'package:np_common/ci_string.dart'; part 'home_search_suggestion.g.dart'; diff --git a/app/lib/widget/image_editor.dart b/app/lib/widget/image_editor.dart index 648fbbb5..d58390e8 100644 --- a/app/lib/widget/image_editor.dart +++ b/app/lib/widget/image_editor.dart @@ -4,7 +4,6 @@ import 'package:collection/collection.dart'; import 'package:flutter/material.dart'; import 'package:kiwi/kiwi.dart'; import 'package:nc_photos/account.dart'; -import 'package:nc_photos/api/api.dart'; import 'package:nc_photos/api/api_util.dart' as api_util; import 'package:nc_photos/app_localizations.dart'; import 'package:nc_photos/cache_manager_util.dart'; @@ -13,6 +12,7 @@ import 'package:nc_photos/entity/file_descriptor.dart'; import 'package:nc_photos/help_utils.dart' as help_util; import 'package:nc_photos/k.dart' as k; import 'package:nc_photos/material3.dart'; +import 'package:nc_photos/np_api_util.dart'; import 'package:nc_photos/object_extension.dart'; import 'package:nc_photos/pixel_image_provider.dart'; import 'package:nc_photos/theme.dart'; @@ -282,7 +282,7 @@ class _ImageEditorState extends State { 3072, _buildFilterList(), headers: { - "Authorization": Api.getAuthorizationHeaderValue(widget.account), + "Authorization": AuthUtil.fromAccount(widget.account).toHeaderValue(), }, isSaveToServer: c.pref.isSaveEditResultToServerOr(), ); diff --git a/app/lib/widget/image_enhancer.dart b/app/lib/widget/image_enhancer.dart index 49ab23a2..bbe1212e 100644 --- a/app/lib/widget/image_enhancer.dart +++ b/app/lib/widget/image_enhancer.dart @@ -8,7 +8,6 @@ import 'package:flutter/material.dart'; import 'package:kiwi/kiwi.dart'; import 'package:logging/logging.dart'; import 'package:nc_photos/account.dart'; -import 'package:nc_photos/api/api.dart'; import 'package:nc_photos/app_localizations.dart'; import 'package:nc_photos/di_container.dart'; import 'package:nc_photos/entity/file_descriptor.dart'; @@ -18,6 +17,7 @@ import 'package:nc_photos/k.dart' as k; import 'package:nc_photos/mobile/android/android_info.dart'; import 'package:nc_photos/mobile/android/content_uri_image_provider.dart'; import 'package:nc_photos/mobile/android/k.dart' as android; +import 'package:nc_photos/np_api_util.dart'; import 'package:nc_photos/object_extension.dart'; import 'package:nc_photos/platform/k.dart' as platform_k; import 'package:nc_photos/snack_bar_manager.dart'; @@ -191,7 +191,8 @@ class _ImageEnhancerState extends State { _c.pref.getEnhanceMaxHeightOr(), args["iteration"] ?? 8, headers: { - "Authorization": Api.getAuthorizationHeaderValue(widget.account), + "Authorization": + AuthUtil.fromAccount(widget.account).toHeaderValue(), }, isSaveToServer: widget.isSaveToServer, ); @@ -205,7 +206,8 @@ class _ImageEnhancerState extends State { _c.pref.getEnhanceMaxHeightOr(), args["radius"] ?? 16, headers: { - "Authorization": Api.getAuthorizationHeaderValue(widget.account), + "Authorization": + AuthUtil.fromAccount(widget.account).toHeaderValue(), }, isSaveToServer: widget.isSaveToServer, ); @@ -218,7 +220,8 @@ class _ImageEnhancerState extends State { _c.pref.getEnhanceMaxWidthOr(), _c.pref.getEnhanceMaxHeightOr(), headers: { - "Authorization": Api.getAuthorizationHeaderValue(widget.account), + "Authorization": + AuthUtil.fromAccount(widget.account).toHeaderValue(), }, isSaveToServer: widget.isSaveToServer, ); @@ -235,7 +238,8 @@ class _ImageEnhancerState extends State { args["styleUri"], args["weight"], headers: { - "Authorization": Api.getAuthorizationHeaderValue(widget.account), + "Authorization": + AuthUtil.fromAccount(widget.account).toHeaderValue(), }, isSaveToServer: widget.isSaveToServer, ); @@ -249,7 +253,8 @@ class _ImageEnhancerState extends State { _c.pref.getEnhanceMaxHeightOr(), args["weight"], headers: { - "Authorization": Api.getAuthorizationHeaderValue(widget.account), + "Authorization": + AuthUtil.fromAccount(widget.account).toHeaderValue(), }, isSaveToServer: widget.isSaveToServer, ); @@ -262,7 +267,8 @@ class _ImageEnhancerState extends State { _c.pref.getEnhanceMaxWidthOr(), _c.pref.getEnhanceMaxHeightOr(), headers: { - "Authorization": Api.getAuthorizationHeaderValue(widget.account), + "Authorization": + AuthUtil.fromAccount(widget.account).toHeaderValue(), }, isSaveToServer: widget.isSaveToServer, ); diff --git a/app/lib/widget/image_viewer.dart b/app/lib/widget/image_viewer.dart index 741cbf66..82fc6113 100644 --- a/app/lib/widget/image_viewer.dart +++ b/app/lib/widget/image_viewer.dart @@ -4,7 +4,6 @@ import 'package:flutter/gestures.dart'; import 'package:flutter/widgets.dart'; import 'package:logging/logging.dart'; import 'package:nc_photos/account.dart'; -import 'package:nc_photos/api/api.dart'; import 'package:nc_photos/api/api_util.dart' as api_util; import 'package:nc_photos/cache_manager_util.dart'; import 'package:nc_photos/entity/file_descriptor.dart'; @@ -12,6 +11,7 @@ import 'package:nc_photos/entity/local_file.dart'; import 'package:nc_photos/flutter_util.dart' as flutter_util; import 'package:nc_photos/k.dart' as k; import 'package:nc_photos/mobile/android/content_uri_image_provider.dart'; +import 'package:nc_photos/np_api_util.dart'; import 'package:nc_photos/widget/cached_network_image_mod.dart' as mod; import 'package:nc_photos/widget/network_thumbnail.dart'; import 'package:np_codegen/np_codegen.dart'; @@ -99,7 +99,7 @@ class RemoteImageViewer extends StatefulWidget { LargeImageCacheManager.inst.getFileStream( _getImageUrl(account, file), headers: { - "Authorization": Api.getAuthorizationHeaderValue(account), + "Authorization": AuthUtil.fromAccount(account).toHeaderValue(), }, ); } @@ -157,7 +157,7 @@ class _RemoteImageViewerState extends State { widget.account, widget.file), httpHeaders: { "Authorization": - Api.getAuthorizationHeaderValue(widget.account), + AuthUtil.fromAccount(widget.account).toHeaderValue(), }, fadeInDuration: const Duration(), filterQuality: FilterQuality.high, @@ -170,7 +170,7 @@ class _RemoteImageViewerState extends State { imageUrl: _getImageUrl(widget.account, widget.file), httpHeaders: { "Authorization": - Api.getAuthorizationHeaderValue(widget.account), + AuthUtil.fromAccount(widget.account).toHeaderValue(), }, fit: BoxFit.contain, fadeInDuration: const Duration(), diff --git a/app/lib/widget/network_thumbnail.dart b/app/lib/widget/network_thumbnail.dart index 7f707f5c..50e5a413 100644 --- a/app/lib/widget/network_thumbnail.dart +++ b/app/lib/widget/network_thumbnail.dart @@ -2,11 +2,11 @@ import 'package:cached_network_image/cached_network_image.dart'; import 'package:cached_network_image_platform_interface/cached_network_image_platform_interface.dart'; import 'package:flutter/material.dart'; import 'package:nc_photos/account.dart'; -import 'package:nc_photos/api/api.dart'; import 'package:nc_photos/api/api_util.dart' as api_util; import 'package:nc_photos/cache_manager_util.dart'; import 'package:nc_photos/entity/file_descriptor.dart'; import 'package:nc_photos/k.dart' as k; +import 'package:nc_photos/np_api_util.dart'; /// A square thumbnail widget for a file class NetworkRectThumbnail extends StatelessWidget { @@ -46,7 +46,7 @@ class NetworkRectThumbnail extends StatelessWidget { imageUrl: imageUrl, // imageUrl: "", httpHeaders: { - "Authorization": Api.getAuthorizationHeaderValue(account), + "Authorization": AuthUtil.fromAccount(account).toHeaderValue(), }, fadeInDuration: const Duration(), filterQuality: FilterQuality.high, diff --git a/app/lib/widget/share_album_dialog.dart b/app/lib/widget/share_album_dialog.dart index 5643c057..785ca676 100644 --- a/app/lib/widget/share_album_dialog.dart +++ b/app/lib/widget/share_album_dialog.dart @@ -9,7 +9,6 @@ import 'package:nc_photos/app_localizations.dart'; import 'package:nc_photos/async_util.dart' as async_util; import 'package:nc_photos/bloc/list_sharee.dart'; import 'package:nc_photos/bloc/search_suggestion.dart'; -import 'package:nc_photos/ci_string.dart'; import 'package:nc_photos/di_container.dart'; import 'package:nc_photos/entity/album.dart'; import 'package:nc_photos/entity/sharee.dart'; @@ -21,6 +20,7 @@ import 'package:nc_photos/use_case/unshare_album_with_user.dart'; import 'package:nc_photos/widget/album_share_outlier_browser.dart'; import 'package:nc_photos/widget/dialog_scaffold.dart'; import 'package:np_codegen/np_codegen.dart'; +import 'package:np_common/ci_string.dart'; part 'share_album_dialog.g.dart'; diff --git a/app/lib/widget/shared_file_viewer.dart b/app/lib/widget/shared_file_viewer.dart index 11719832..0eb48b4f 100644 --- a/app/lib/widget/shared_file_viewer.dart +++ b/app/lib/widget/shared_file_viewer.dart @@ -6,7 +6,6 @@ import 'package:intl/intl.dart'; import 'package:kiwi/kiwi.dart'; import 'package:logging/logging.dart'; import 'package:nc_photos/account.dart'; -import 'package:nc_photos/api/api.dart'; import 'package:nc_photos/api/api_util.dart' as api_util; import 'package:nc_photos/app_localizations.dart'; import 'package:nc_photos/cache_manager_util.dart'; @@ -17,6 +16,7 @@ import 'package:nc_photos/entity/share.dart'; import 'package:nc_photos/entity/share/data_source.dart'; import 'package:nc_photos/exception_util.dart' as exception_util; import 'package:nc_photos/k.dart' as k; +import 'package:nc_photos/np_api_util.dart'; import 'package:nc_photos/remote_storage_util.dart' as remote_storage_util; import 'package:nc_photos/snack_bar_manager.dart'; import 'package:nc_photos/use_case/remove.dart'; @@ -102,7 +102,7 @@ class _SharedFileViewerState extends State { imageUrl: previewUrl, httpHeaders: { "Authorization": - Api.getAuthorizationHeaderValue(widget.account), + AuthUtil.fromAccount(widget.account).toHeaderValue(), }, fadeInDuration: const Duration(), filterQuality: FilterQuality.high, diff --git a/app/lib/widget/sign_in.dart b/app/lib/widget/sign_in.dart index 37d198f9..e92beb0a 100644 --- a/app/lib/widget/sign_in.dart +++ b/app/lib/widget/sign_in.dart @@ -12,12 +12,12 @@ import 'package:nc_photos/iterable_extension.dart'; import 'package:nc_photos/legacy/sign_in.dart' as legacy; import 'package:nc_photos/pref.dart'; import 'package:nc_photos/pref_util.dart' as pref_util; -import 'package:nc_photos/string_extension.dart'; import 'package:nc_photos/theme.dart'; import 'package:nc_photos/widget/connect.dart'; import 'package:nc_photos/widget/home.dart'; import 'package:nc_photos/widget/root_picker.dart'; import 'package:np_codegen/np_codegen.dart'; +import 'package:np_common/string_extension.dart'; part 'sign_in.g.dart'; diff --git a/app/lib/widget/tag_picker_dialog.dart b/app/lib/widget/tag_picker_dialog.dart index 91413eaf..be0ba820 100644 --- a/app/lib/widget/tag_picker_dialog.dart +++ b/app/lib/widget/tag_picker_dialog.dart @@ -6,12 +6,12 @@ import 'package:nc_photos/app_localizations.dart'; import 'package:nc_photos/async_util.dart' as async_util; import 'package:nc_photos/bloc/list_tag.dart'; import 'package:nc_photos/bloc/search_suggestion.dart'; -import 'package:nc_photos/ci_string.dart'; import 'package:nc_photos/entity/tag.dart'; import 'package:nc_photos/exception_util.dart' as exception_util; import 'package:nc_photos/k.dart' as k; import 'package:nc_photos/snack_bar_manager.dart'; import 'package:nc_photos/widget/dialog_scaffold.dart'; +import 'package:np_common/ci_string.dart'; class TagPickerDialog extends StatefulWidget { const TagPickerDialog({ diff --git a/app/lib/widget/video_viewer.dart b/app/lib/widget/video_viewer.dart index 00d5daba..f4532ec5 100644 --- a/app/lib/widget/video_viewer.dart +++ b/app/lib/widget/video_viewer.dart @@ -4,13 +4,13 @@ import 'package:flutter/material.dart'; import 'package:kiwi/kiwi.dart'; import 'package:logging/logging.dart'; import 'package:nc_photos/account.dart'; -import 'package:nc_photos/api/api.dart'; import 'package:nc_photos/api/api_util.dart' as api_util; import 'package:nc_photos/app_localizations.dart'; import 'package:nc_photos/di_container.dart'; import 'package:nc_photos/entity/file_descriptor.dart'; import 'package:nc_photos/exception_util.dart' as exception_util; import 'package:nc_photos/k.dart' as k; +import 'package:nc_photos/np_api_util.dart'; import 'package:nc_photos/platform/k.dart' as platform_k; import 'package:nc_photos/snack_bar_manager.dart'; import 'package:nc_photos/use_case/request_public_link.dart'; @@ -110,7 +110,7 @@ class _VideoViewerState extends State _controller = VideoPlayerController.network( url, httpHeaders: { - "Authorization": Api.getAuthorizationHeaderValue(widget.account), + "Authorization": AuthUtil.fromAccount(widget.account).toHeaderValue(), }, ); await _controller.initialize(); diff --git a/app/pubspec.lock b/app/pubspec.lock index b264e96c..5386bf42 100644 --- a/app/pubspec.lock +++ b/app/pubspec.lock @@ -851,6 +851,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "2.0.1" + np_api: + dependency: "direct main" + description: + path: "../np_api" + relative: true + source: path + version: "0.0.1" np_codegen: dependency: "direct main" description: @@ -865,6 +872,13 @@ packages: relative: true source: path version: "1.0.0" + np_common: + dependency: "direct main" + description: + path: "../np_common" + relative: true + source: path + version: "0.0.1" octo_image: dependency: transitive description: @@ -1556,7 +1570,7 @@ packages: source: hosted version: "0.2.0+1" xml: - dependency: "direct main" + dependency: transitive description: name: xml url: "https://pub.dartlang.org" @@ -1570,5 +1584,5 @@ packages: source: hosted version: "3.1.1" sdks: - dart: ">=2.17.0 <3.0.0" + dart: ">=2.18.0 <3.0.0" flutter: ">=3.3.0" diff --git a/app/pubspec.yaml b/app/pubspec.yaml index 198e2076..e380fae0 100644 --- a/app/pubspec.yaml +++ b/app/pubspec.yaml @@ -92,8 +92,12 @@ dependencies: native_device_orientation: ^1.1.0 nc_photos_plugin: path: ../plugin + np_api: + path: ../np_api np_codegen: path: ../codegen + np_common: + path: ../np_common page_view_indicators: ^2.0.0 path: ^1.8.0 path_provider: ^2.0.6 @@ -116,7 +120,6 @@ dependencies: visibility_detector: ^0.3.3 wakelock: ^0.6.2 woozy_search: ^2.0.3 - xml: ^6.1.0 dependency_overrides: video_player_android: diff --git a/app/test/account_test.dart b/app/test/account_test.dart index c5a6aa00..13bc257e 100644 --- a/app/test/account_test.dart +++ b/app/test/account_test.dart @@ -1,5 +1,5 @@ import 'package:nc_photos/account.dart'; -import 'package:nc_photos/ci_string.dart'; +import 'package:np_common/ci_string.dart'; import 'package:test/test.dart'; void main() { diff --git a/app/test/api/entity_converter_test.dart b/app/test/api/entity_converter_test.dart new file mode 100644 index 00000000..32112dc2 --- /dev/null +++ b/app/test/api/entity_converter_test.dart @@ -0,0 +1,244 @@ +import 'package:nc_photos/api/entity_converter.dart'; +import 'package:nc_photos/entity/file.dart'; +import 'package:np_api/np_api.dart' as api; +import 'package:test/test.dart'; + +void main() { + group("ApiFileConverter", () { + group("fromApi", () { + test("file", _files); + test("file w/ metadata", _filesMetadata); + test("file w/ is-archived", _filesIsArchived); + test("file w/ override-date-time", _filesOverrideDateTime); + test("multiple files", _filesMultiple); + test("directory", _filesDir); + test("nextcloud hosted in subdir", _filesServerHostedInSubdir); + }); + }); +} + +Future _files() async { + final apiFile = api.File( + href: "/remote.php/dav/files/admin/Nextcloud intro.mp4", + contentLength: 3963036, + contentType: "video/mp4", + etag: "1324f58d4d5c8d81bed6e4ed9d5ea862", + lastModified: DateTime.utc(2021, 1, 1, 2, 3, 4), + hasPreview: false, + fileId: 123, + isCollection: false, + ); + expect( + ApiFileConverter.fromApi(apiFile), + File( + path: "remote.php/dav/files/admin/Nextcloud intro.mp4", + contentLength: 3963036, + contentType: "video/mp4", + etag: "1324f58d4d5c8d81bed6e4ed9d5ea862", + lastModified: DateTime.utc(2021, 1, 1, 2, 3, 4), + hasPreview: false, + fileId: 123, + isCollection: false, + ), + ); +} + +Future _filesMetadata() async { + final apiFile = api.File( + href: "/remote.php/dav/files/admin/Photos/Nextcloud community.jpg", + contentLength: 797325, + contentType: "image/jpeg", + etag: "8950e39a034e369237d9285e2d815a50", + lastModified: DateTime.utc(2021, 1, 1, 2, 3, 4), + hasPreview: true, + fileId: 123, + isCollection: false, + customProperties: { + "com.nkming.nc_photos:metadata": + "{\"version\":2,\"lastUpdated\":\"2021-01-02T03:04:05.678Z\",\"fileEtag\":\"8950e39a034e369237d9285e2d815a50\",\"imageWidth\":3000,\"imageHeight\":2000}", + }, + ); + expect( + ApiFileConverter.fromApi(apiFile), + File( + path: "remote.php/dav/files/admin/Photos/Nextcloud community.jpg", + contentLength: 797325, + contentType: "image/jpeg", + etag: "8950e39a034e369237d9285e2d815a50", + lastModified: DateTime.utc(2021, 1, 1, 2, 3, 4), + hasPreview: true, + fileId: 123, + isCollection: false, + metadata: Metadata( + lastUpdated: DateTime.utc(2021, 1, 2, 3, 4, 5, 678), + fileEtag: "8950e39a034e369237d9285e2d815a50", + imageWidth: 3000, + imageHeight: 2000, + ), + ), + ); +} + +Future _filesIsArchived() async { + final apiFile = api.File( + href: "/remote.php/dav/files/admin/Photos/Nextcloud community.jpg", + contentLength: 797325, + contentType: "image/jpeg", + etag: "8950e39a034e369237d9285e2d815a50", + lastModified: DateTime.utc(2021, 1, 1, 2, 3, 4), + hasPreview: true, + isCollection: false, + customProperties: { + "com.nkming.nc_photos:is-archived": "true", + }, + ); + expect( + ApiFileConverter.fromApi(apiFile), + File( + path: "remote.php/dav/files/admin/Photos/Nextcloud community.jpg", + contentLength: 797325, + contentType: "image/jpeg", + etag: "8950e39a034e369237d9285e2d815a50", + lastModified: DateTime.utc(2021, 1, 1, 2, 3, 4), + hasPreview: true, + isCollection: false, + isArchived: true, + ), + ); +} + +Future _filesOverrideDateTime() async { + final apiFile = api.File( + href: "/remote.php/dav/files/admin/Photos/Nextcloud community.jpg", + contentLength: 797325, + contentType: "image/jpeg", + etag: "8950e39a034e369237d9285e2d815a50", + lastModified: DateTime.utc(2021, 1, 1, 2, 3, 4), + hasPreview: true, + isCollection: false, + customProperties: { + "com.nkming.nc_photos:override-date-time": "2021-01-02T03:04:05.000Z", + }, + ); + expect( + ApiFileConverter.fromApi(apiFile), + File( + path: "remote.php/dav/files/admin/Photos/Nextcloud community.jpg", + contentLength: 797325, + contentType: "image/jpeg", + etag: "8950e39a034e369237d9285e2d815a50", + lastModified: DateTime.utc(2021, 1, 1, 2, 3, 4), + hasPreview: true, + isCollection: false, + overrideDateTime: DateTime.utc(2021, 1, 2, 3, 4, 5), + ), + ); +} + +Future _filesMultiple() async { + final apiFiles = [ + api.File( + href: "/remote.php/dav/files/admin/Nextcloud intro.mp4", + contentLength: 3963036, + contentType: "video/mp4", + etag: "1324f58d4d5c8d81bed6e4ed9d5ea862", + lastModified: DateTime.utc(2021, 1, 1, 2, 3, 4), + hasPreview: false, + fileId: 123, + isCollection: false, + ), + api.File( + href: "/remote.php/dav/files/admin/Nextcloud.png", + contentLength: 50598, + contentType: "image/png", + etag: "48689d5b17c449d9db492ffe8f7ab8a6", + lastModified: DateTime.utc(2021, 1, 2, 3, 4, 5), + hasPreview: true, + fileId: 124, + isCollection: false, + customProperties: { + "com.nkming.nc_photos:metadata": + "{\"version\":2,\"lastUpdated\":\"2021-01-02T03:04:05.678000Z\",\"fileEtag\":\"48689d5b17c449d9db492ffe8f7ab8a6\",\"imageWidth\":500,\"imageHeight\":500}", + }, + ), + ]; + expect( + apiFiles.map(ApiFileConverter.fromApi).toList(), + [ + File( + path: "remote.php/dav/files/admin/Nextcloud intro.mp4", + contentLength: 3963036, + contentType: "video/mp4", + etag: "1324f58d4d5c8d81bed6e4ed9d5ea862", + lastModified: DateTime.utc(2021, 1, 1, 2, 3, 4), + hasPreview: false, + fileId: 123, + isCollection: false, + ), + File( + path: "remote.php/dav/files/admin/Nextcloud.png", + contentLength: 50598, + contentType: "image/png", + etag: "48689d5b17c449d9db492ffe8f7ab8a6", + lastModified: DateTime.utc(2021, 1, 2, 3, 4, 5), + hasPreview: true, + fileId: 124, + isCollection: false, + metadata: Metadata( + fileEtag: "48689d5b17c449d9db492ffe8f7ab8a6", + imageWidth: 500, + imageHeight: 500, + lastUpdated: DateTime.utc(2021, 1, 2, 3, 4, 5, 678), + ), + ), + ], + ); +} + +Future _filesDir() async { + final apiFile = api.File( + href: "/remote.php/dav/files/admin/Photos/", + etag: "123456789abcd", + lastModified: DateTime.utc(2021, 1, 1, 2, 3, 4), + isCollection: true, + hasPreview: false, + fileId: 123, + ); + expect( + ApiFileConverter.fromApi(apiFile), + File( + path: "remote.php/dav/files/admin/Photos", + etag: "123456789abcd", + lastModified: DateTime.utc(2021, 1, 1, 2, 3, 4), + isCollection: true, + hasPreview: false, + fileId: 123, + ), + ); +} + +Future _filesServerHostedInSubdir() async { + final apiFile = api.File( + href: "/nextcloud/remote.php/dav/files/admin/Nextcloud intro.mp4", + contentLength: 3963036, + contentType: "video/mp4", + etag: "1324f58d4d5c8d81bed6e4ed9d5ea862", + lastModified: DateTime.utc(2021, 1, 1, 2, 3, 4), + hasPreview: false, + fileId: 123, + isCollection: false, + ); + expect( + ApiFileConverter.fromApi(apiFile), + File( + path: "remote.php/dav/files/admin/Nextcloud intro.mp4", + contentLength: 3963036, + contentType: "video/mp4", + etag: "1324f58d4d5c8d81bed6e4ed9d5ea862", + lastModified: DateTime.utc(2021, 1, 1, 2, 3, 4), + hasPreview: false, + fileId: 123, + isCollection: false, + ), + ); +} diff --git a/app/test/bloc/list_album_share_outlier_test.dart b/app/test/bloc/list_album_share_outlier_test.dart index 8badffda..4406addb 100644 --- a/app/test/bloc/list_album_share_outlier_test.dart +++ b/app/test/bloc/list_album_share_outlier_test.dart @@ -1,8 +1,8 @@ import 'package:bloc_test/bloc_test.dart'; import 'package:nc_photos/bloc/list_album_share_outlier.dart'; -import 'package:nc_photos/ci_string.dart'; import 'package:nc_photos/di_container.dart'; import 'package:nc_photos/entity/sqlite/database.dart' as sql; +import 'package:np_common/ci_string.dart'; import 'package:test/test.dart'; import '../mock_type.dart'; diff --git a/app/test/entity/album/data_source_test.dart b/app/test/entity/album/data_source_test.dart index 18896dfe..446fa83e 100644 --- a/app/test/entity/album/data_source_test.dart +++ b/app/test/entity/album/data_source_test.dart @@ -1,4 +1,3 @@ -import 'package:nc_photos/ci_string.dart'; import 'package:nc_photos/di_container.dart'; import 'package:nc_photos/entity/album.dart'; import 'package:nc_photos/entity/album/cover_provider.dart'; @@ -9,6 +8,7 @@ import 'package:nc_photos/entity/album/sort_provider.dart'; import 'package:nc_photos/entity/sqlite/database.dart' as sql; import 'package:nc_photos/exception.dart'; import 'package:nc_photos/or_null.dart'; +import 'package:np_common/ci_string.dart'; import 'package:test/test.dart'; import '../../test_util.dart' as util; diff --git a/app/test/entity/album/sort_provider_test.dart b/app/test/entity/album/sort_provider_test.dart index dcc6fecd..e13c9c13 100644 --- a/app/test/entity/album/sort_provider_test.dart +++ b/app/test/entity/album/sort_provider_test.dart @@ -1,7 +1,7 @@ import 'package:collection/collection.dart'; -import 'package:nc_photos/ci_string.dart'; import 'package:nc_photos/entity/album/item.dart'; import 'package:nc_photos/entity/album/sort_provider.dart'; +import 'package:np_common/ci_string.dart'; import 'package:test/test.dart'; import '../../test_util.dart' as util; diff --git a/app/test/entity/album_test.dart b/app/test/entity/album_test.dart index 6f268180..d21c9771 100644 --- a/app/test/entity/album_test.dart +++ b/app/test/entity/album_test.dart @@ -1,5 +1,4 @@ import 'package:intl/intl.dart'; -import 'package:nc_photos/ci_string.dart'; import 'package:nc_photos/entity/album.dart'; import 'package:nc_photos/entity/album/cover_provider.dart'; import 'package:nc_photos/entity/album/item.dart'; @@ -7,7 +6,8 @@ import 'package:nc_photos/entity/album/provider.dart'; import 'package:nc_photos/entity/album/sort_provider.dart'; import 'package:nc_photos/entity/album/upgrader.dart'; import 'package:nc_photos/entity/file.dart'; -import 'package:nc_photos/type.dart'; +import 'package:np_common/ci_string.dart'; +import 'package:np_common/type.dart'; import 'package:test/test.dart'; import '../test_util.dart' as util; diff --git a/app/test/entity/file_test.dart b/app/test/entity/file_test.dart index b1675c02..03bade8c 100644 --- a/app/test/entity/file_test.dart +++ b/app/test/entity/file_test.dart @@ -1,8 +1,8 @@ -import 'package:nc_photos/ci_string.dart'; import 'package:nc_photos/entity/exif.dart'; import 'package:nc_photos/entity/file.dart'; import 'package:nc_photos/entity/file_descriptor.dart'; import 'package:nc_photos/or_null.dart'; +import 'package:np_common/ci_string.dart'; import 'package:test/test.dart'; void main() { diff --git a/app/test/mock_type.dart b/app/test/mock_type.dart index 4195fcc7..75f0f318 100644 --- a/app/test/mock_type.dart +++ b/app/test/mock_type.dart @@ -4,7 +4,6 @@ import 'dart:typed_data'; import 'package:event_bus/event_bus.dart'; import 'package:nc_photos/account.dart'; -import 'package:nc_photos/ci_string.dart'; import 'package:nc_photos/di_container.dart'; import 'package:nc_photos/entity/album.dart'; import 'package:nc_photos/entity/favorite.dart'; @@ -19,6 +18,7 @@ import 'package:nc_photos/entity/tag.dart'; import 'package:nc_photos/exception_event.dart'; import 'package:nc_photos/future_util.dart' as future_util; import 'package:nc_photos/or_null.dart'; +import 'package:np_common/ci_string.dart'; import 'package:path/path.dart' as path_lib; /// Mock of [AlbumRepo] where all methods will throw UnimplementedError diff --git a/app/test/test_util.dart b/app/test/test_util.dart index 735fdfd4..0bf7d90d 100644 --- a/app/test/test_util.dart +++ b/app/test/test_util.dart @@ -5,7 +5,6 @@ import 'package:equatable/equatable.dart'; import 'package:flutter/foundation.dart'; import 'package:logging/logging.dart'; import 'package:nc_photos/account.dart'; -import 'package:nc_photos/ci_string.dart'; import 'package:nc_photos/entity/album.dart'; import 'package:nc_photos/entity/album/cover_provider.dart'; import 'package:nc_photos/entity/album/item.dart'; @@ -19,6 +18,7 @@ import 'package:nc_photos/entity/sqlite/database.dart' as sql; import 'package:nc_photos/entity/sqlite/type_converter.dart'; import 'package:nc_photos/iterable_extension.dart'; import 'package:nc_photos/or_null.dart'; +import 'package:np_common/ci_string.dart'; import 'package:tuple/tuple.dart'; class FilesBuilder { diff --git a/app/test/use_case/add_to_album_test.dart b/app/test/use_case/add_to_album_test.dart index 018dccef..697cc5f0 100644 --- a/app/test/use_case/add_to_album_test.dart +++ b/app/test/use_case/add_to_album_test.dart @@ -1,6 +1,5 @@ import 'package:event_bus/event_bus.dart'; import 'package:kiwi/kiwi.dart'; -import 'package:nc_photos/ci_string.dart'; import 'package:nc_photos/di_container.dart'; import 'package:nc_photos/entity/album.dart'; import 'package:nc_photos/entity/album/cover_provider.dart'; @@ -12,6 +11,7 @@ import 'package:nc_photos/entity/sqlite/database.dart' as sql; import 'package:nc_photos/or_null.dart'; import 'package:nc_photos/pref.dart'; import 'package:nc_photos/use_case/add_to_album.dart'; +import 'package:np_common/ci_string.dart'; import 'package:test/test.dart'; import '../mock_type.dart'; diff --git a/app/test/use_case/share_album_with_user_test.dart b/app/test/use_case/share_album_with_user_test.dart index ffe6c444..e829689a 100644 --- a/app/test/use_case/share_album_with_user_test.dart +++ b/app/test/use_case/share_album_with_user_test.dart @@ -1,8 +1,8 @@ import 'package:event_bus/event_bus.dart'; import 'package:kiwi/kiwi.dart'; -import 'package:nc_photos/ci_string.dart'; import 'package:nc_photos/or_null.dart'; import 'package:nc_photos/use_case/share_album_with_user.dart'; +import 'package:np_common/ci_string.dart'; import 'package:test/test.dart'; import '../mock_type.dart'; diff --git a/app/test/use_case/unshare_album_with_user_test.dart b/app/test/use_case/unshare_album_with_user_test.dart index 077d11c3..00cd6a55 100644 --- a/app/test/use_case/unshare_album_with_user_test.dart +++ b/app/test/use_case/unshare_album_with_user_test.dart @@ -1,8 +1,8 @@ import 'package:event_bus/event_bus.dart'; import 'package:kiwi/kiwi.dart'; -import 'package:nc_photos/ci_string.dart'; import 'package:nc_photos/di_container.dart'; import 'package:nc_photos/use_case/unshare_album_with_user.dart'; +import 'package:np_common/ci_string.dart'; import 'package:test/test.dart'; import '../mock_type.dart'; diff --git a/np_api/.gitignore b/np_api/.gitignore new file mode 100644 index 00000000..96486fd9 --- /dev/null +++ b/np_api/.gitignore @@ -0,0 +1,30 @@ +# Miscellaneous +*.class +*.log +*.pyc +*.swp +.DS_Store +.atom/ +.buildlog/ +.history +.svn/ +migrate_working_dir/ + +# IntelliJ related +*.iml +*.ipr +*.iws +.idea/ + +# The .vscode folder contains launch configuration and tasks you configure in +# VS Code which you may wish to be included in version control, so this line +# is commented out by default. +#.vscode/ + +# Flutter/Dart/Pub related +# Libraries should not include pubspec.lock, per https://dart.dev/guides/libraries/private-files#pubspeclock. +/pubspec.lock +**/doc/api/ +.dart_tool/ +.packages +build/ diff --git a/np_api/.metadata b/np_api/.metadata new file mode 100644 index 00000000..541c2f65 --- /dev/null +++ b/np_api/.metadata @@ -0,0 +1,10 @@ +# This file tracks properties of this Flutter project. +# Used by Flutter tool to assess capabilities and perform upgrades etc. +# +# This file should be version controlled and should not be manually edited. + +version: + revision: 135454af32477f815a7525073027a3ff9eff1bfd + channel: stable + +project_type: package diff --git a/np_api/analysis_options.yaml b/np_api/analysis_options.yaml new file mode 100644 index 00000000..23babb48 --- /dev/null +++ b/np_api/analysis_options.yaml @@ -0,0 +1,7 @@ +include: package:flutter_lints/flutter.yaml + +linter: + rules: + unawaited_futures: true + avoid_void_async: true + directives_ordering: true diff --git a/np_api/lib/np_api.dart b/np_api/lib/np_api.dart new file mode 100644 index 00000000..63ee52c6 --- /dev/null +++ b/np_api/lib/np_api.dart @@ -0,0 +1,11 @@ +export 'src/api.dart'; +export 'src/entity/entity.dart'; +export 'src/entity/face_parser.dart'; +export 'src/entity/favorite_parser.dart'; +export 'src/entity/file_parser.dart'; +export 'src/entity/person_parser.dart'; +export 'src/entity/share_parser.dart'; +export 'src/entity/sharee_parser.dart'; +export 'src/entity/tag_parser.dart'; +export 'src/entity/tagged_file_parser.dart'; +export 'src/type.dart'; diff --git a/app/lib/api/api.dart b/np_api/lib/src/api.dart similarity index 57% rename from app/lib/api/api.dart rename to np_api/lib/src/api.dart index 7b76a547..21675185 100644 --- a/app/lib/api/api.dart +++ b/np_api/lib/src/api.dart @@ -1,12 +1,9 @@ -import 'dart:convert'; - import 'package:flutter/foundation.dart'; import 'package:http/http.dart' as http; import 'package:logging/logging.dart'; -import 'package:nc_photos/account.dart'; -import 'package:nc_photos/string_extension.dart'; +import 'package:np_api/src/type.dart'; +import 'package:np_api/src/util.dart'; import 'package:np_codegen/np_codegen.dart'; -import 'package:to_string/to_string.dart'; import 'package:xml/xml.dart'; part 'api.g.dart'; @@ -16,52 +13,11 @@ part 'files_api.dart'; part 'files_sharing_api.dart'; part 'systemtag_api.dart'; -@toString -class Response { - Response(this.statusCode, this.headers, this.body); - - bool get isGood => _isHttpStatusGood(statusCode); - - @override - String toString() => _$toString(); - - final int statusCode; - @Format(r"...") - final Map headers; - - /// Content of the response body, String if isResponseString == true during - /// request, Uint8List otherwise - @Format( - r"${kDebugMode ? body.toString().replaceAll(RegExp(r'\n\t'), '').slice(0, 200) : '...'}") - final dynamic body; -} - -class BasicAuth { - BasicAuth(this.username, this.password); - - BasicAuth.fromAccount(Account account) - : this( - account.username2, - account.password, - ); - - @override - toString() { - final authString = base64.encode(utf8.encode("$username:$password")); - return "Basic $authString"; - } - - final String username; - final String password; -} - @npLog class Api { - Api(Account account) - : _baseUrl = Uri.parse(account.url), - _auth = BasicAuth.fromAccount(account); + const Api(this.baseUrl, BasicAuth this.auth); - Api.fromBaseUrl(Uri baseUrl) : _baseUrl = baseUrl; + const Api.fromBaseUrl(this.baseUrl) : auth = null; ApiFiles files() => ApiFiles(this); @@ -71,10 +27,6 @@ class Api { ApiSystemtagsRelations systemtagsRelations() => ApiSystemtagsRelations(this); - static String getAuthorizationHeaderValue(Account account) { - return BasicAuth.fromAccount(account).toString(); - } - Future request( String method, String endpoint, { @@ -86,9 +38,9 @@ class Api { }) async { final url = _makeUri(endpoint, queryParameters: queryParameters); final req = http.Request(method, url); - if (_auth != null) { + if (auth != null) { req.headers.addAll({ - "authorization": _auth.toString(), + "authorization": auth!.toHeaderValue(), }); } if (header != null) { @@ -104,7 +56,7 @@ class Api { } final response = await http.Response.fromStream(await http.Client().send(req)); - if (!_isHttpStatusGood(response.statusCode)) { + if (!isHttpStatusGood(response.statusCode)) { if (response.statusCode == 404) { _log.severe( "[request] HTTP $method (${response.statusCode}): $endpoint", @@ -124,20 +76,18 @@ class Api { String endpoint, { Map? queryParameters, }) { - final path = _baseUrl.path + "/$endpoint"; - if (_baseUrl.scheme == "http") { - return Uri.http(_baseUrl.authority, path, queryParameters); + final path = "${baseUrl.path}/$endpoint"; + if (baseUrl.scheme == "http") { + return Uri.http(baseUrl.authority, path, queryParameters); } else { - return Uri.https(_baseUrl.authority, path, queryParameters); + return Uri.https(baseUrl.authority, path, queryParameters); } } - final Uri _baseUrl; - BasicAuth? _auth; + final Uri baseUrl; + final BasicAuth? auth; } -bool _isHttpStatusGood(int status) => status ~/ 100 == 2; - class ApiOcs { ApiOcs(this._api); diff --git a/app/lib/api/api.g.dart b/np_api/lib/src/api.g.dart similarity index 58% rename from app/lib/api/api.g.dart rename to np_api/lib/src/api.g.dart index 1e1abe96..5b63e654 100644 --- a/app/lib/api/api.g.dart +++ b/np_api/lib/src/api.g.dart @@ -10,21 +10,21 @@ extension _$ApiNpLog on Api { // ignore: unused_element Logger get _log => log; - static final log = Logger("api.api.Api"); + static final log = Logger("src.api.Api"); } extension _$ApiOcsDavDirectNpLog on ApiOcsDavDirect { // ignore: unused_element Logger get _log => log; - static final log = Logger("api.api.ApiOcsDavDirect"); + static final log = Logger("src.api.ApiOcsDavDirect"); } extension _$ApiOcsFacerecognitionPersonsNpLog on ApiOcsFacerecognitionPersons { // ignore: unused_element Logger get _log => log; - static final log = Logger("api.api.ApiOcsFacerecognitionPersons"); + static final log = Logger("src.api.ApiOcsFacerecognitionPersons"); } extension _$ApiOcsFacerecognitionPersonFacesNpLog @@ -32,58 +32,47 @@ extension _$ApiOcsFacerecognitionPersonFacesNpLog // ignore: unused_element Logger get _log => log; - static final log = Logger("api.api.ApiOcsFacerecognitionPersonFaces"); + static final log = Logger("src.api.ApiOcsFacerecognitionPersonFaces"); } extension _$ApiFilesNpLog on ApiFiles { // ignore: unused_element Logger get _log => log; - static final log = Logger("api.api.ApiFiles"); + static final log = Logger("src.api.ApiFiles"); } extension _$ApiOcsFilesSharingSharesNpLog on ApiOcsFilesSharingShares { // ignore: unused_element Logger get _log => log; - static final log = Logger("api.api.ApiOcsFilesSharingShares"); + static final log = Logger("src.api.ApiOcsFilesSharingShares"); } extension _$ApiOcsFilesSharingShareNpLog on ApiOcsFilesSharingShare { // ignore: unused_element Logger get _log => log; - static final log = Logger("api.api.ApiOcsFilesSharingShare"); + static final log = Logger("src.api.ApiOcsFilesSharingShare"); } extension _$ApiOcsFilesSharingShareesNpLog on ApiOcsFilesSharingSharees { // ignore: unused_element Logger get _log => log; - static final log = Logger("api.api.ApiOcsFilesSharingSharees"); + static final log = Logger("src.api.ApiOcsFilesSharingSharees"); } extension _$ApiSystemtagsNpLog on ApiSystemtags { // ignore: unused_element Logger get _log => log; - static final log = Logger("api.api.ApiSystemtags"); + static final log = Logger("src.api.ApiSystemtags"); } extension _$ApiSystemtagsRelationsFilesNpLog on ApiSystemtagsRelationsFiles { // ignore: unused_element Logger get _log => log; - static final log = Logger("api.api.ApiSystemtagsRelationsFiles"); -} - -// ************************************************************************** -// ToStringGenerator -// ************************************************************************** - -extension _$ResponseToString on Response { - String _$toString() { - // ignore: unnecessary_string_interpolations - return "Response {statusCode: $statusCode, headers: ..., body: ${kDebugMode ? body.toString().replaceAll(RegExp(r'\n\t'), '').slice(0, 200) : '...'}}"; - } + static final log = Logger("src.api.ApiSystemtagsRelationsFiles"); } diff --git a/app/lib/api/direct_api.dart b/np_api/lib/src/direct_api.dart similarity index 100% rename from app/lib/api/direct_api.dart rename to np_api/lib/src/direct_api.dart diff --git a/np_api/lib/src/entity/entity.dart b/np_api/lib/src/entity/entity.dart new file mode 100644 index 00000000..42f0ac27 --- /dev/null +++ b/np_api/lib/src/entity/entity.dart @@ -0,0 +1,257 @@ +import 'package:equatable/equatable.dart'; +import 'package:to_string/to_string.dart'; + +part 'entity.g.dart'; + +@toString +class Face with EquatableMixin { + const Face({ + required this.id, + required this.fileId, + }); + + @override + String toString() => _$toString(); + + @override + List get props => [ + id, + fileId, + ]; + + final int id; + final int fileId; +} + +@toString +class Favorite with EquatableMixin { + const Favorite({ + required this.href, + required this.fileId, + }); + + @override + String toString() => _$toString(); + + @override + List get props => [ + href, + fileId, + ]; + + final String href; + final int fileId; +} + +@ToString(ignoreNull: true) +class File with EquatableMixin { + const File({ + required this.href, + this.lastModified, + this.etag, + this.contentType, + this.isCollection, + this.contentLength, + this.fileId, + this.favorite, + this.ownerId, + this.ownerDisplayName, + this.hasPreview, + this.trashbinFilename, + this.trashbinOriginalLocation, + this.trashbinDeletionTime, + this.customProperties, + }); + + @override + String toString() => _$toString(); + + @override + List get props => [ + href, + lastModified, + etag, + contentType, + isCollection, + contentLength, + fileId, + favorite, + ownerId, + ownerDisplayName, + hasPreview, + trashbinFilename, + trashbinOriginalLocation, + trashbinDeletionTime, + customProperties, + ]; + + final String href; + final DateTime? lastModified; + final String? etag; + final String? contentType; + final bool? isCollection; + final int? contentLength; + final int? fileId; + final bool? favorite; + final String? ownerId; + final String? ownerDisplayName; + final bool? hasPreview; + final String? trashbinFilename; + final String? trashbinOriginalLocation; + final DateTime? trashbinDeletionTime; + final Map? customProperties; +} + +@toString +class Person with EquatableMixin { + const Person({ + required this.name, + required this.thumbFaceId, + required this.count, + }); + + @override + String toString() => _$toString(); + + @override + List get props => [ + name, + thumbFaceId, + count, + ]; + + final String name; + final int thumbFaceId; + final int count; +} + +@toString +class Share with EquatableMixin { + const Share({ + required this.id, + required this.shareType, + required this.stime, + required this.uidOwner, + required this.displaynameOwner, + required this.uidFileOwner, + required this.path, + required this.itemType, + required this.mimeType, + required this.itemSource, + required this.shareWith, + required this.shareWithDisplayName, + required this.url, + }); + + @override + String toString() => _$toString(); + + @override + List get props => [ + id, + shareType, + stime, + uidOwner, + displaynameOwner, + uidFileOwner, + path, + itemType, + mimeType, + itemSource, + shareWith, + shareWithDisplayName, + url, + ]; + + final String id; + final int shareType; + final int stime; + final String uidOwner; + final String displaynameOwner; + final String uidFileOwner; + final String path; + final String itemType; + final String mimeType; + final int itemSource; + final String? shareWith; + final String shareWithDisplayName; + final String? url; +} + +@toString +class Sharee with EquatableMixin { + const Sharee({ + required this.type, + required this.label, + required this.shareType, + required this.shareWith, + required this.shareWithDisplayNameUnique, + }); + + @override + String toString() => _$toString(); + + @override + List get props => [ + type, + label, + shareType, + shareWith, + shareWithDisplayNameUnique, + ]; + + final String type; + final String label; + final int shareType; + final String shareWith; + final String? shareWithDisplayNameUnique; +} + +@toString +class Tag with EquatableMixin { + const Tag({ + required this.href, + required this.id, + required this.displayName, + required this.userVisible, + required this.userAssignable, + }); + + @override + String toString() => _$toString(); + + @override + List get props => [ + href, + id, + displayName, + userVisible, + userAssignable, + ]; + + final String href; + final int id; + final String displayName; + final bool userVisible; + final bool userAssignable; +} + +@toString +class TaggedFile with EquatableMixin { + const TaggedFile({ + required this.href, + required this.fileId, + }); + + @override + String toString() => _$toString(); + + @override + List get props => [ + href, + fileId, + ]; + + final String href; + final int fileId; +} diff --git a/np_api/lib/src/entity/entity.g.dart b/np_api/lib/src/entity/entity.g.dart new file mode 100644 index 00000000..f6515466 --- /dev/null +++ b/np_api/lib/src/entity/entity.g.dart @@ -0,0 +1,63 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'entity.dart'; + +// ************************************************************************** +// ToStringGenerator +// ************************************************************************** + +extension _$FaceToString on Face { + String _$toString() { + // ignore: unnecessary_string_interpolations + return "Face {id: $id, fileId: $fileId}"; + } +} + +extension _$FavoriteToString on Favorite { + String _$toString() { + // ignore: unnecessary_string_interpolations + return "Favorite {href: $href, fileId: $fileId}"; + } +} + +extension _$FileToString on File { + String _$toString() { + // ignore: unnecessary_string_interpolations + return "File {href: $href, ${lastModified == null ? "" : "lastModified: $lastModified, "}${etag == null ? "" : "etag: $etag, "}${contentType == null ? "" : "contentType: $contentType, "}${isCollection == null ? "" : "isCollection: $isCollection, "}${contentLength == null ? "" : "contentLength: $contentLength, "}${fileId == null ? "" : "fileId: $fileId, "}${favorite == null ? "" : "favorite: $favorite, "}${ownerId == null ? "" : "ownerId: $ownerId, "}${ownerDisplayName == null ? "" : "ownerDisplayName: $ownerDisplayName, "}${hasPreview == null ? "" : "hasPreview: $hasPreview, "}${trashbinFilename == null ? "" : "trashbinFilename: $trashbinFilename, "}${trashbinOriginalLocation == null ? "" : "trashbinOriginalLocation: $trashbinOriginalLocation, "}${trashbinDeletionTime == null ? "" : "trashbinDeletionTime: $trashbinDeletionTime, "}${customProperties == null ? "" : "customProperties: $customProperties"}}"; + } +} + +extension _$PersonToString on Person { + String _$toString() { + // ignore: unnecessary_string_interpolations + return "Person {name: $name, thumbFaceId: $thumbFaceId, count: $count}"; + } +} + +extension _$ShareToString on Share { + String _$toString() { + // ignore: unnecessary_string_interpolations + return "Share {id: $id, shareType: $shareType, stime: $stime, uidOwner: $uidOwner, displaynameOwner: $displaynameOwner, uidFileOwner: $uidFileOwner, path: $path, itemType: $itemType, mimeType: $mimeType, itemSource: $itemSource, shareWith: $shareWith, shareWithDisplayName: $shareWithDisplayName, url: $url}"; + } +} + +extension _$ShareeToString on Sharee { + String _$toString() { + // ignore: unnecessary_string_interpolations + return "Sharee {type: $type, label: $label, shareType: $shareType, shareWith: $shareWith, shareWithDisplayNameUnique: $shareWithDisplayNameUnique}"; + } +} + +extension _$TagToString on Tag { + String _$toString() { + // ignore: unnecessary_string_interpolations + return "Tag {href: $href, id: $id, displayName: $displayName, userVisible: $userVisible, userAssignable: $userAssignable}"; + } +} + +extension _$TaggedFileToString on TaggedFile { + String _$toString() { + // ignore: unnecessary_string_interpolations + return "TaggedFile {href: $href, fileId: $fileId}"; + } +} diff --git a/np_api/lib/src/entity/face_parser.dart b/np_api/lib/src/entity/face_parser.dart new file mode 100644 index 00000000..bbe28b92 --- /dev/null +++ b/np_api/lib/src/entity/face_parser.dart @@ -0,0 +1,42 @@ +import 'dart:convert'; + +import 'package:flutter/foundation.dart'; +import 'package:logging/logging.dart'; +import 'package:np_api/src/entity/entity.dart'; +import 'package:np_codegen/np_codegen.dart'; +import 'package:np_common/log.dart'; +import 'package:np_common/type.dart'; + +part 'face_parser.g.dart'; + +@npLog +class FaceParser { + Future> parse(String response) => + compute(_parseFacesIsolate, response); + + List _parse(JsonObj json) { + final jsons = json["ocs"]["data"].cast(); + final products = []; + for (final j in jsons) { + try { + products.add(_parseSingle(j)); + } catch (e) { + _log.severe("[_parse] Failed parsing json: ${jsonEncode(j)}", e); + } + } + return products; + } + + Face _parseSingle(JsonObj json) { + return Face( + id: json["id"], + fileId: json["fileId"], + ); + } +} + +List _parseFacesIsolate(String response) { + initLog(); + final json = (jsonDecode(response) as Map).cast(); + return FaceParser()._parse(json); +} diff --git a/np_api/lib/src/entity/face_parser.g.dart b/np_api/lib/src/entity/face_parser.g.dart new file mode 100644 index 00000000..b7578245 --- /dev/null +++ b/np_api/lib/src/entity/face_parser.g.dart @@ -0,0 +1,14 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'face_parser.dart'; + +// ************************************************************************** +// NpLogGenerator +// ************************************************************************** + +extension _$FaceParserNpLog on FaceParser { + // ignore: unused_element + Logger get _log => log; + + static final log = Logger("src.entity.face_parser.FaceParser"); +} diff --git a/np_api/lib/src/entity/favorite_parser.dart b/np_api/lib/src/entity/favorite_parser.dart new file mode 100644 index 00000000..e5d8205b --- /dev/null +++ b/np_api/lib/src/entity/favorite_parser.dart @@ -0,0 +1,74 @@ +import 'package:flutter/foundation.dart'; +import 'package:np_api/src/entity/entity.dart'; +import 'package:np_api/src/entity/parser.dart'; +import 'package:np_common/log.dart'; +import 'package:xml/xml.dart'; + +class FavoriteParser extends XmlResponseParser { + Future> parse(String response) => + compute(_parseFavoritesIsolate, response); + + List _parse(XmlDocument xml) => parseT(xml, _toFavorite); + + /// Map contents to Favorite + Favorite _toFavorite(XmlElement element) { + String? href; + int? fileId; + + for (final child in element.children.whereType()) { + if (child.matchQualifiedName("href", + prefix: "DAV:", namespaces: namespaces)) { + href = Uri.decodeComponent(child.innerText); + } else if (child.matchQualifiedName("propstat", + prefix: "DAV:", namespaces: namespaces)) { + final status = child.children + .whereType() + .firstWhere((element) => element.matchQualifiedName("status", + prefix: "DAV:", namespaces: namespaces)) + .innerText; + if (!status.contains(" 200 ")) { + continue; + } + final prop = child.children.whereType().firstWhere( + (element) => element.matchQualifiedName("prop", + prefix: "DAV:", namespaces: namespaces)); + final propParser = _PropParser(namespaces: namespaces); + propParser.parse(prop); + fileId = propParser.fileId; + } + } + + return Favorite( + href: href!, + fileId: fileId!, + ); + } +} + +class _PropParser { + _PropParser({ + this.namespaces = const {}, + }); + + /// Parse element contents + void parse(XmlElement element) { + for (final child in element.children.whereType()) { + if (child.matchQualifiedName("fileid", + prefix: "http://owncloud.org/ns", namespaces: namespaces)) { + _fileId = int.parse(child.innerText); + } + } + } + + int? get fileId => _fileId; + + final Map namespaces; + + int? _fileId; +} + +List _parseFavoritesIsolate(String response) { + initLog(); + final xml = XmlDocument.parse(response); + return FavoriteParser()._parse(xml); +} diff --git a/np_api/lib/src/entity/file_parser.dart b/np_api/lib/src/entity/file_parser.dart new file mode 100644 index 00000000..72feff49 --- /dev/null +++ b/np_api/lib/src/entity/file_parser.dart @@ -0,0 +1,202 @@ +import 'dart:io'; + +import 'package:flutter/foundation.dart'; +import 'package:logging/logging.dart'; +import 'package:np_api/src/entity/entity.dart'; +import 'package:np_api/src/entity/parser.dart'; +import 'package:np_codegen/np_codegen.dart'; +import 'package:np_common/log.dart'; +import 'package:xml/xml.dart'; + +part 'file_parser.g.dart'; + +@npLog +class FileParser extends XmlResponseParser { + Future> parse(String response) => + compute(_parseFilesIsolate, response); + + List _parse(XmlDocument xml) => parseT(xml, _toFile); + + /// Map contents to File + File _toFile(XmlElement element) { + String? href; + DateTime? lastModified; + String? etag; + String? contentType; + bool? isCollection; + int? contentLength; + int? fileId; + bool? favorite; + String? ownerId; + String? ownerDisplayName; + bool? hasPreview; + String? trashbinFilename; + String? trashbinOriginalLocation; + DateTime? trashbinDeletionTime; + Map? customProperties; + + for (final child in element.children.whereType()) { + if (child.matchQualifiedName("href", + prefix: "DAV:", namespaces: namespaces)) { + href = Uri.decodeComponent(child.innerText); + } else if (child.matchQualifiedName("propstat", + prefix: "DAV:", namespaces: namespaces)) { + final status = child.children + .whereType() + .firstWhere((element) => element.matchQualifiedName("status", + prefix: "DAV:", namespaces: namespaces)) + .innerText; + if (!status.contains(" 200 ")) { + continue; + } + final prop = child.children.whereType().firstWhere( + (element) => element.matchQualifiedName("prop", + prefix: "DAV:", namespaces: namespaces)); + final propParser = _PropParser(namespaces: namespaces); + propParser.parse(prop); + contentLength = propParser.contentLength; + contentType = propParser.contentType; + etag = propParser.etag; + lastModified = propParser.lastModified; + isCollection = propParser.isCollection; + hasPreview = propParser.hasPreview; + fileId = propParser.fileId; + ownerId = propParser.ownerId; + ownerDisplayName = propParser.ownerDisplayName; + trashbinFilename = propParser.trashbinFilename; + trashbinOriginalLocation = propParser.trashbinOriginalLocation; + trashbinDeletionTime = propParser.trashbinDeletionTime; + customProperties = propParser.customProperties; + } + } + + return File( + href: href!, + lastModified: lastModified, + etag: etag, + contentType: contentType, + isCollection: isCollection, + contentLength: contentLength, + fileId: fileId, + favorite: favorite, + ownerId: ownerId, + ownerDisplayName: ownerDisplayName, + hasPreview: hasPreview, + trashbinFilename: trashbinFilename, + trashbinOriginalLocation: trashbinOriginalLocation, + trashbinDeletionTime: trashbinDeletionTime, + customProperties: customProperties, + ); + } +} + +class _PropParser { + _PropParser({ + this.namespaces = const {}, + }); + + /// Parse element contents + void parse(XmlElement element) { + for (final child in element.children.whereType()) { + if (child.matchQualifiedName("getlastmodified", + prefix: "DAV:", namespaces: namespaces)) { + _lastModified = HttpDate.parse(child.innerText); + } else if (child.matchQualifiedName("getetag", + prefix: "DAV:", namespaces: namespaces)) { + _etag = child.innerText.replaceAll("\"", ""); + } else if (child.matchQualifiedName("getcontenttype", + prefix: "DAV:", namespaces: namespaces)) { + _contentType = child.innerText; + } else if (child.matchQualifiedName("resourcetype", + prefix: "DAV:", namespaces: namespaces)) { + _isCollection = child.children.whereType().any((element) => + element.matchQualifiedName("collection", + prefix: "DAV:", namespaces: namespaces)); + } else if (child.matchQualifiedName("getcontentlength", + prefix: "DAV:", namespaces: namespaces)) { + _contentLength = int.parse(child.innerText); + } else if (child.matchQualifiedName("fileid", + prefix: "http://owncloud.org/ns", namespaces: namespaces)) { + _fileId = int.parse(child.innerText); + } else if (child.matchQualifiedName("favorite", + prefix: "http://owncloud.org/ns", namespaces: namespaces)) { + _favorite = child.innerText != "0"; + } else if (child.matchQualifiedName("owner-id", + prefix: "http://owncloud.org/ns", namespaces: namespaces)) { + _ownerId = child.innerText; + } else if (child.matchQualifiedName("owner-display-name", + prefix: "http://owncloud.org/ns", namespaces: namespaces)) { + _ownerDisplayName = child.innerText; + } else if (child.matchQualifiedName("has-preview", + prefix: "http://nextcloud.org/ns", namespaces: namespaces)) { + _hasPreview = child.innerText == "true"; + } else if (child.matchQualifiedName("trashbin-filename", + prefix: "http://nextcloud.org/ns", namespaces: namespaces)) { + _trashbinFilename = child.innerText; + } else if (child.matchQualifiedName("trashbin-original-location", + prefix: "http://nextcloud.org/ns", namespaces: namespaces)) { + _trashbinOriginalLocation = child.innerText; + } else if (child.matchQualifiedName("trashbin-deletion-time", + prefix: "http://nextcloud.org/ns", namespaces: namespaces)) { + _trashbinDeletionTime = DateTime.fromMillisecondsSinceEpoch( + int.parse(child.innerText) * 1000); + } else { + final key = child.name.prefix == null + ? child.localName + : "${_expandNamespace(child, namespaces)}:${child.localName}"; + (_customProperties ??= {})[key] = child.innerText; + } + } + } + + DateTime? get lastModified => _lastModified; + String? get etag => _etag; + String? get contentType => _contentType; + bool? get isCollection => _isCollection; + int? get contentLength => _contentLength; + int? get fileId => _fileId; + bool? get favorite => _favorite; + String? get ownerId => _ownerId; + String? get ownerDisplayName => _ownerDisplayName; + bool? get hasPreview => _hasPreview; + String? get trashbinFilename => _trashbinFilename; + String? get trashbinOriginalLocation => _trashbinOriginalLocation; + DateTime? get trashbinDeletionTime => _trashbinDeletionTime; + Map? get customProperties => _customProperties; + + final Map namespaces; + + DateTime? _lastModified; + String? _etag; + String? _contentType; + bool? _isCollection; + int? _contentLength; + int? _fileId; + bool? _favorite; + String? _ownerId; + String? _ownerDisplayName; + bool? _hasPreview; + String? _trashbinFilename; + String? _trashbinOriginalLocation; + DateTime? _trashbinDeletionTime; + Map? _customProperties; +} + +List _parseFilesIsolate(String response) { + initLog(); + final xml = XmlDocument.parse(response); + return FileParser()._parse(xml); +} + +String _expandNamespace(XmlElement element, Map namespaces) { + if (namespaces.containsKey(element.name.prefix)) { + return namespaces[element.name.prefix]!; + } + final localNamespaces = {}; + for (final a in element.attributes) { + if (a.name.prefix == "xmlns") { + localNamespaces[a.name.local] = a.value; + } + } + return localNamespaces[element.name.prefix] ?? element.name.prefix!; +} diff --git a/np_api/lib/src/entity/file_parser.g.dart b/np_api/lib/src/entity/file_parser.g.dart new file mode 100644 index 00000000..4ec54cd4 --- /dev/null +++ b/np_api/lib/src/entity/file_parser.g.dart @@ -0,0 +1,14 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'file_parser.dart'; + +// ************************************************************************** +// NpLogGenerator +// ************************************************************************** + +extension _$FileParserNpLog on FileParser { + // ignore: unused_element + Logger get _log => log; + + static final log = Logger("src.entity.file_parser.FileParser"); +} diff --git a/np_api/lib/src/entity/parser.dart b/np_api/lib/src/entity/parser.dart new file mode 100644 index 00000000..0f51aeb1 --- /dev/null +++ b/np_api/lib/src/entity/parser.dart @@ -0,0 +1,85 @@ +import 'package:flutter/foundation.dart'; +import 'package:logging/logging.dart'; +import 'package:np_codegen/np_codegen.dart'; +import 'package:xml/xml.dart'; + +part 'parser.g.dart'; + +@npLog +class XmlResponseParser { + List parseT(XmlDocument xml, T? Function(XmlElement) mapper) { + namespaces = _parseNamespaces(xml); + final body = () { + try { + return xml.children.whereType().firstWhere((element) => + element.matchQualifiedName("multistatus", + prefix: "DAV:", namespaces: namespaces)); + } catch (_) { + _log.shout("[_parse] Missing element: multistatus"); + rethrow; + } + }(); + return body.children + .whereType() + .where((e) => e.matchQualifiedName("response", + prefix: "DAV:", namespaces: namespaces)) + .map((e) { + try { + return mapper(e); + } catch (e, stackTrace) { + _log.shout("[_parse] Failed parsing XML", e, stackTrace); + return null; + } + }) + .whereType() + .toList(); + } + + Map _parseNamespaces(XmlDocument xml) { + final namespaces = {}; + final xmlContent = xml.descendants.whereType().firstWhere( + (element) => !element.name.qualified.startsWith("?"), + orElse: () => XmlElement(XmlName.fromString(""))); + for (final a in xmlContent.attributes) { + if (a.name.prefix == "xmlns") { + namespaces[a.name.local] = a.value; + } else if (a.name.local == "xmlns") { + namespaces["!"] = a.value; + } + } + // _log.fine("[_parseNamespaces] Namespaces: $namespaces"); + return namespaces; + } + + @protected + var namespaces = {}; +} + +extension XmlElementExtension on XmlElement { + bool matchQualifiedName( + String local, { + required String prefix, + required Map namespaces, + }) { + final localNamespaces = {}; + for (final a in attributes) { + if (a.name.prefix == "xmlns") { + localNamespaces[a.name.local] = a.value; + } else if (a.name.local == "xmlns") { + localNamespaces["!"] = a.value; + } + } + return name.local == local && + (name.prefix == prefix || + // match default namespace + (name.prefix == null && namespaces["!"] == prefix) || + // match global namespace + namespaces.entries + .where((element2) => element2.value == prefix) + .any((element) => element.key == name.prefix) || + // match local namespace + localNamespaces.entries + .where((element2) => element2.value == prefix) + .any((element) => element.key == name.prefix)); + } +} diff --git a/app/lib/entity/webdav_response_parser.g.dart b/np_api/lib/src/entity/parser.g.dart similarity index 59% rename from app/lib/entity/webdav_response_parser.g.dart rename to np_api/lib/src/entity/parser.g.dart index a5953030..37f18692 100644 --- a/app/lib/entity/webdav_response_parser.g.dart +++ b/np_api/lib/src/entity/parser.g.dart @@ -1,15 +1,14 @@ // GENERATED CODE - DO NOT MODIFY BY HAND -part of 'webdav_response_parser.dart'; +part of 'parser.dart'; // ************************************************************************** // NpLogGenerator // ************************************************************************** -extension _$WebdavResponseParserNpLog on WebdavResponseParser { +extension _$XmlResponseParserNpLog on XmlResponseParser { // ignore: unused_element Logger get _log => log; - static final log = - Logger("entity.webdav_response_parser.WebdavResponseParser"); + static final log = Logger("src.entity.parser.XmlResponseParser"); } diff --git a/np_api/lib/src/entity/person_parser.dart b/np_api/lib/src/entity/person_parser.dart new file mode 100644 index 00000000..9f45d8f3 --- /dev/null +++ b/np_api/lib/src/entity/person_parser.dart @@ -0,0 +1,43 @@ +import 'dart:convert'; + +import 'package:flutter/foundation.dart'; +import 'package:logging/logging.dart'; +import 'package:np_api/src/entity/entity.dart'; +import 'package:np_codegen/np_codegen.dart'; +import 'package:np_common/log.dart'; +import 'package:np_common/type.dart'; + +part 'person_parser.g.dart'; + +@npLog +class PersonParser { + Future> parse(String response) => + compute(_parsePersonsIsolate, response); + + List _parse(JsonObj json) { + final jsons = json["ocs"]["data"].cast(); + final products = []; + for (final j in jsons) { + try { + products.add(_parseSingle(j)); + } catch (e) { + _log.severe("[_parse] Failed parsing json: ${jsonEncode(j)}", e); + } + } + return products; + } + + Person _parseSingle(JsonObj json) { + return Person( + name: json["name"], + thumbFaceId: json["thumbFaceId"], + count: json["count"], + ); + } +} + +List _parsePersonsIsolate(String response) { + initLog(); + final json = (jsonDecode(response) as Map).cast(); + return PersonParser()._parse(json); +} diff --git a/np_api/lib/src/entity/person_parser.g.dart b/np_api/lib/src/entity/person_parser.g.dart new file mode 100644 index 00000000..84dc3f51 --- /dev/null +++ b/np_api/lib/src/entity/person_parser.g.dart @@ -0,0 +1,14 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'person_parser.dart'; + +// ************************************************************************** +// NpLogGenerator +// ************************************************************************** + +extension _$PersonParserNpLog on PersonParser { + // ignore: unused_element + Logger get _log => log; + + static final log = Logger("src.entity.person_parser.PersonParser"); +} diff --git a/np_api/lib/src/entity/share_parser.dart b/np_api/lib/src/entity/share_parser.dart new file mode 100644 index 00000000..68e5ed93 --- /dev/null +++ b/np_api/lib/src/entity/share_parser.dart @@ -0,0 +1,53 @@ +import 'dart:convert'; + +import 'package:flutter/foundation.dart'; +import 'package:logging/logging.dart'; +import 'package:np_api/src/entity/entity.dart'; +import 'package:np_codegen/np_codegen.dart'; +import 'package:np_common/log.dart'; +import 'package:np_common/type.dart'; + +part 'share_parser.g.dart'; + +@npLog +class ShareParser { + Future> parse(String response) => + compute(_parseSharesIsolate, response); + + List _parse(JsonObj json) { + final jsons = json["ocs"]["data"].cast(); + final products = []; + for (final j in jsons) { + try { + products.add(_parseSingle(j)); + } catch (e) { + _log.severe("[_parse] Failed parsing json: ${jsonEncode(j)}", e); + } + } + return products; + } + + Share _parseSingle(JsonObj json) { + return Share( + id: json["id"], + shareType: json["share_type"], + stime: json["stime"], + uidOwner: json["uid_owner"], + displaynameOwner: json["displayname_owner"], + uidFileOwner: json["uid_file_owner"], + path: json["path"], + itemType: json["item_type"], + mimeType: json["mimetype"], + itemSource: json["item_source"], + shareWith: json["share_with"], + shareWithDisplayName: json["share_with_displayname"], + url: json["url"], + ); + } +} + +List _parseSharesIsolate(String response) { + initLog(); + final json = (jsonDecode(response) as Map).cast(); + return ShareParser()._parse(json); +} diff --git a/np_api/lib/src/entity/share_parser.g.dart b/np_api/lib/src/entity/share_parser.g.dart new file mode 100644 index 00000000..53c7bd42 --- /dev/null +++ b/np_api/lib/src/entity/share_parser.g.dart @@ -0,0 +1,14 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'share_parser.dart'; + +// ************************************************************************** +// NpLogGenerator +// ************************************************************************** + +extension _$ShareParserNpLog on ShareParser { + // ignore: unused_element + Logger get _log => log; + + static final log = Logger("src.entity.share_parser.ShareParser"); +} diff --git a/np_api/lib/src/entity/sharee_parser.dart b/np_api/lib/src/entity/sharee_parser.dart new file mode 100644 index 00000000..4d64f3ad --- /dev/null +++ b/np_api/lib/src/entity/sharee_parser.dart @@ -0,0 +1,59 @@ +import 'dart:convert'; + +import 'package:flutter/foundation.dart'; +import 'package:logging/logging.dart'; +import 'package:np_api/src/entity/entity.dart'; +import 'package:np_codegen/np_codegen.dart'; +import 'package:np_common/log.dart'; +import 'package:np_common/type.dart'; + +part 'sharee_parser.g.dart'; + +@npLog +class ShareeParser { + Future> parse(String response) => + compute(_parseShareesIsolate, response); + + List _parse(JsonObj json) { + final jsons = json["ocs"]["data"].cast(); + final products = []; + for (final t in _types) { + for (final j in jsons[t] ?? []) { + try { + products.add(_parseSingle(j, t)); + } catch (e) { + _log.severe("[_parse] Failed parsing json: ${jsonEncode(j)}", e); + } + } + } + return products; + } + + Sharee _parseSingle(JsonObj json, String type) { + return Sharee( + type: type, + label: json["label"], + shareType: json["value"]["shareType"], + shareWith: json["value"]["shareWith"], + shareWithDisplayNameUnique: json["shareWithDisplayNameUnique"], + ); + } +} + +List _parseShareesIsolate(String response) { + initLog(); + final json = (jsonDecode(response) as Map).cast(); + return ShareeParser()._parse(json); +} + +const _types = { + "users", + "groups", + "remotes", + "remote_groups", + "emails", + "circles", + "rooms", + "deck", + "lookup", +}; diff --git a/np_api/lib/src/entity/sharee_parser.g.dart b/np_api/lib/src/entity/sharee_parser.g.dart new file mode 100644 index 00000000..4d857da8 --- /dev/null +++ b/np_api/lib/src/entity/sharee_parser.g.dart @@ -0,0 +1,14 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'sharee_parser.dart'; + +// ************************************************************************** +// NpLogGenerator +// ************************************************************************** + +extension _$ShareeParserNpLog on ShareeParser { + // ignore: unused_element + Logger get _log => log; + + static final log = Logger("src.entity.sharee_parser.ShareeParser"); +} diff --git a/np_api/lib/src/entity/tag_parser.dart b/np_api/lib/src/entity/tag_parser.dart new file mode 100644 index 00000000..e5667435 --- /dev/null +++ b/np_api/lib/src/entity/tag_parser.dart @@ -0,0 +1,102 @@ +import 'package:flutter/foundation.dart'; +import 'package:np_api/src/entity/entity.dart'; +import 'package:np_api/src/entity/parser.dart'; +import 'package:np_common/log.dart'; +import 'package:xml/xml.dart'; + +class TagParser extends XmlResponseParser { + Future> parse(String response) => + compute(_parseTagsIsolate, response); + + List _parse(XmlDocument xml) => parseT(xml, _toTag); + + /// Map contents to Tag + Tag? _toTag(XmlElement element) { + String? href; + int? id; + String? displayName; + bool? userVisible; + bool? userAssignable; + + for (final child in element.children.whereType()) { + if (child.matchQualifiedName("href", + prefix: "DAV:", namespaces: namespaces)) { + href = Uri.decodeComponent(child.innerText); + } else if (child.matchQualifiedName("propstat", + prefix: "DAV:", namespaces: namespaces)) { + final status = child.children + .whereType() + .firstWhere((element) => element.matchQualifiedName("status", + prefix: "DAV:", namespaces: namespaces)) + .innerText; + if (!status.contains(" 200 ")) { + continue; + } + final prop = child.children.whereType().firstWhere( + (element) => element.matchQualifiedName("prop", + prefix: "DAV:", namespaces: namespaces)); + final propParser = _PropParser(namespaces: namespaces); + propParser.parse(prop); + id = propParser.id; + displayName = propParser.displayName; + userVisible = propParser.userVisible; + userAssignable = propParser.userAssignable; + } + } + if (id == null) { + // the first returned item is not a valid tag + return null; + } + + return Tag( + href: href!, + id: id, + displayName: displayName!, + userVisible: userVisible!, + userAssignable: userAssignable!, + ); + } +} + +class _PropParser { + _PropParser({ + this.namespaces = const {}, + }); + + /// Parse element contents + void parse(XmlElement element) { + for (final child in element.children.whereType()) { + if (child.matchQualifiedName("id", + prefix: "http://owncloud.org/ns", namespaces: namespaces)) { + _id = int.parse(child.innerText); + } else if (child.matchQualifiedName("display-name", + prefix: "http://owncloud.org/ns", namespaces: namespaces)) { + _displayName = child.innerText; + } else if (child.matchQualifiedName("user-visible", + prefix: "http://owncloud.org/ns", namespaces: namespaces)) { + _userVisible = child.innerText == "true"; + } else if (child.matchQualifiedName("user-assignable", + prefix: "http://owncloud.org/ns", namespaces: namespaces)) { + _userAssignable = child.innerText == "true"; + } + } + } + + int? get id => _id; + String? get displayName => _displayName; + bool? get userVisible => _userVisible; + bool? get userAssignable => _userAssignable; + + final Map namespaces; + + int? _id; + String? _displayName; + bool? _userVisible; + bool? _userAssignable; +} + +List _parseTagsIsolate(String response) { + initLog(); + final xml = XmlDocument.parse(response); + return TagParser()._parse(xml); +} diff --git a/np_api/lib/src/entity/tagged_file_parser.dart b/np_api/lib/src/entity/tagged_file_parser.dart new file mode 100644 index 00000000..3d5b384f --- /dev/null +++ b/np_api/lib/src/entity/tagged_file_parser.dart @@ -0,0 +1,75 @@ +import 'package:flutter/foundation.dart'; +import 'package:np_api/src/entity/entity.dart'; +import 'package:np_api/src/entity/parser.dart'; +import 'package:np_common/log.dart'; +import 'package:xml/xml.dart'; + +class TaggedFileParser extends XmlResponseParser { + Future> parse(String response) => + compute(_parseTaggedFilesIsolate, response); + + List _parse(XmlDocument xml) => + parseT(xml, _toTaggedFile); + + /// Map contents to TaggedFile + TaggedFile? _toTaggedFile(XmlElement element) { + String? href; + int? fileId; + + for (final child in element.children.whereType()) { + if (child.matchQualifiedName("href", + prefix: "DAV:", namespaces: namespaces)) { + href = Uri.decodeComponent(child.innerText); + } else if (child.matchQualifiedName("propstat", + prefix: "DAV:", namespaces: namespaces)) { + final status = child.children + .whereType() + .firstWhere((element) => element.matchQualifiedName("status", + prefix: "DAV:", namespaces: namespaces)) + .innerText; + if (!status.contains(" 200 ")) { + continue; + } + final prop = child.children.whereType().firstWhere( + (element) => element.matchQualifiedName("prop", + prefix: "DAV:", namespaces: namespaces)); + final propParser = _PropParser(namespaces: namespaces); + propParser.parse(prop); + fileId = propParser.fileId; + } + } + + return TaggedFile( + href: href!, + fileId: fileId!, + ); + } +} + +class _PropParser { + _PropParser({ + this.namespaces = const {}, + }); + + /// Parse element contents + void parse(XmlElement element) { + for (final child in element.children.whereType()) { + if (child.matchQualifiedName("fileid", + prefix: "http://owncloud.org/ns", namespaces: namespaces)) { + _fileId = int.parse(child.innerText); + } + } + } + + int? get fileId => _fileId; + + final Map namespaces; + + int? _fileId; +} + +List _parseTaggedFilesIsolate(String response) { + initLog(); + final xml = XmlDocument.parse(response); + return TaggedFileParser()._parse(xml); +} diff --git a/app/lib/api/face_recognition_api.dart b/np_api/lib/src/face_recognition_api.dart similarity index 100% rename from app/lib/api/face_recognition_api.dart rename to np_api/lib/src/face_recognition_api.dart diff --git a/app/lib/api/files_api.dart b/np_api/lib/src/files_api.dart similarity index 100% rename from app/lib/api/files_api.dart rename to np_api/lib/src/files_api.dart diff --git a/app/lib/api/files_sharing_api.dart b/np_api/lib/src/files_sharing_api.dart similarity index 100% rename from app/lib/api/files_sharing_api.dart rename to np_api/lib/src/files_sharing_api.dart diff --git a/app/lib/api/systemtag_api.dart b/np_api/lib/src/systemtag_api.dart similarity index 100% rename from app/lib/api/systemtag_api.dart rename to np_api/lib/src/systemtag_api.dart diff --git a/np_api/lib/src/type.dart b/np_api/lib/src/type.dart new file mode 100644 index 00000000..a589cff7 --- /dev/null +++ b/np_api/lib/src/type.dart @@ -0,0 +1,44 @@ +import 'dart:convert'; + +import 'package:flutter/foundation.dart'; +import 'package:np_api/src/util.dart'; +import 'package:np_common/string_extension.dart'; +import 'package:to_string/to_string.dart'; + +part 'type.g.dart'; + +@toString +class Response { + Response(this.statusCode, this.headers, this.body); + + bool get isGood => isHttpStatusGood(statusCode); + + @override + String toString() => _$toString(); + + final int statusCode; + @Format(r"...") + final Map headers; + + /// Content of the response body, String if isResponseString == true during + /// request, Uint8List otherwise + @Format( + r"${kDebugMode ? $?.toString().replaceAll(RegExp(r'\n\t'), '').slice(0, 200) : '...'}") + final dynamic body; +} + +@toString +class BasicAuth { + const BasicAuth(this.username, this.password); + + String toHeaderValue() { + final authString = base64.encode(utf8.encode("$username:$password")); + return "Basic $authString"; + } + + @override + String toString() => _$toString(); + + final String username; + final String password; +} diff --git a/np_api/lib/src/type.g.dart b/np_api/lib/src/type.g.dart new file mode 100644 index 00000000..88362717 --- /dev/null +++ b/np_api/lib/src/type.g.dart @@ -0,0 +1,21 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'type.dart'; + +// ************************************************************************** +// ToStringGenerator +// ************************************************************************** + +extension _$ResponseToString on Response { + String _$toString() { + // ignore: unnecessary_string_interpolations + return "Response {statusCode: $statusCode, headers: ..., body: ${kDebugMode ? body.toString().replaceAll(RegExp(r'\n\t'), '').slice(0, 200) : '...'}}"; + } +} + +extension _$BasicAuthToString on BasicAuth { + String _$toString() { + // ignore: unnecessary_string_interpolations + return "BasicAuth {username: $username, password: $password}"; + } +} diff --git a/np_api/lib/src/util.dart b/np_api/lib/src/util.dart new file mode 100644 index 00000000..03900fec --- /dev/null +++ b/np_api/lib/src/util.dart @@ -0,0 +1 @@ +bool isHttpStatusGood(int status) => status ~/ 100 == 2; diff --git a/np_api/pubspec.yaml b/np_api/pubspec.yaml new file mode 100644 index 00000000..0d4c1421 --- /dev/null +++ b/np_api/pubspec.yaml @@ -0,0 +1,75 @@ +name: np_api +description: A new Flutter package project. +version: 0.0.1 +homepage: +publish_to: none + +environment: + sdk: '>=2.18.0 <3.0.0' + flutter: ">=3.3.0" + +dependencies: + equatable: ^2.0.0 + flutter: + sdk: flutter + http: ^0.13.1 + logging: ^1.0.1 + np_codegen: + path: ../codegen + np_common: + path: ../np_common + to_string: + git: + url: https://gitlab.com/nkming2/dart-to-string + ref: to_string-1.0.0 + path: to_string + xml: ^6.1.0 + +dev_dependencies: + build_runner: ^2.1.11 + flutter_lints: ^2.0.0 + np_codegen_build: + path: ../codegen_build + test: any + to_string_build: + git: + url: https://gitlab.com/nkming2/dart-to-string + ref: to_string_build-1.0.0 + path: to_string_build + +# For information on the generic Dart part of this file, see the +# following page: https://dart.dev/tools/pub/pubspec + +# The following section is specific to Flutter packages. +flutter: + + # To add assets to your package, add an assets section, like this: + # assets: + # - images/a_dot_burr.jpeg + # - images/a_dot_ham.jpeg + # + # For details regarding assets in packages, see + # https://flutter.dev/assets-and-images/#from-packages + # + # An image asset can refer to one or more resolution-specific "variants", see + # https://flutter.dev/assets-and-images/#resolution-aware + + # To add custom fonts to your package, add a fonts section here, + # in this "flutter" section. Each entry in this list should have a + # "family" key with the font family name, and a "fonts" key with a + # list giving the asset and other descriptors for the font. For + # example: + # fonts: + # - family: Schyler + # fonts: + # - asset: fonts/Schyler-Regular.ttf + # - asset: fonts/Schyler-Italic.ttf + # style: italic + # - family: Trajan Pro + # fonts: + # - asset: fonts/TrajanPro.ttf + # - asset: fonts/TrajanPro_Bold.ttf + # weight: 700 + # + # For details regarding fonts in packages, see + # https://flutter.dev/custom-fonts/#from-packages diff --git a/np_api/test/entity/face_parser_test.dart b/np_api/test/entity/face_parser_test.dart new file mode 100644 index 00000000..fa28f2c6 --- /dev/null +++ b/np_api/test/entity/face_parser_test.dart @@ -0,0 +1,51 @@ +import 'package:np_api/np_api.dart'; +import 'package:test/test.dart'; + +void main() { + group("FaceParser", () { + test("parse", _faces); + }); +} + +Future _faces() async { + const json = """ +{ + "ocs": { + "meta": { + "status": "ok", + "statuscode": 200, + "message": "OK" + }, + "data": [ + { + "id": 1, + "fileId": 111 + }, + { + "id": 2, + "fileId": 222 + }, + { + "id": 10, + "fileId": 333 + } + ] + } +} +"""; + final results = await FaceParser().parse(json); + expect(results, const [ + Face( + id: 1, + fileId: 111, + ), + Face( + id: 2, + fileId: 222, + ), + Face( + id: 10, + fileId: 333, + ), + ]); +} diff --git a/np_api/test/entity/favorite_parser_test.dart b/np_api/test/entity/favorite_parser_test.dart new file mode 100644 index 00000000..bf82cdcc --- /dev/null +++ b/np_api/test/entity/favorite_parser_test.dart @@ -0,0 +1,47 @@ +import 'package:np_api/np_api.dart'; +import 'package:test/test.dart'; + +void main() { + group("FavoriteParser", () { + test("parse", _favorites); + }); +} + +Future _favorites() async { + const xml = """ + + + + HTTP/1.1 200 OK + /remote.php/dav/files/admin/Nextcloud%20intro.mp4 + + + 12345 + + HTTP/1.1 200 OK + + + + HTTP/1.1 200 OK + /remote.php/dav/files/admin/Nextcloud.png + + + 23456 + + HTTP/1.1 200 OK + + + +"""; + final results = await FavoriteParser().parse(xml); + expect(results, const [ + Favorite( + href: "/remote.php/dav/files/admin/Nextcloud intro.mp4", + fileId: 12345, + ), + Favorite( + href: "/remote.php/dav/files/admin/Nextcloud.png", + fileId: 23456, + ), + ]); +} diff --git a/app/test/entity/webdav_response_parser_test.dart b/np_api/test/entity/file_parser_test.dart similarity index 83% rename from app/test/entity/webdav_response_parser_test.dart rename to np_api/test/entity/file_parser_test.dart index 64aa77da..c45a19f0 100644 --- a/app/test/entity/webdav_response_parser_test.dart +++ b/np_api/test/entity/file_parser_test.dart @@ -1,11 +1,10 @@ -import 'package:nc_photos/entity/file.dart'; -import 'package:nc_photos/entity/webdav_response_parser.dart'; +import 'package:np_api/src/entity/entity.dart'; +import 'package:np_api/src/entity/file_parser.dart'; import 'package:test/test.dart'; -import 'package:xml/xml.dart'; void main() { - group("WebdavFileParser", () { - group("parseFiles", () { + group("FileParser", () { + group("parse", () { test("file", _files); test("file w/ 404 properties", _files404props); test("file w/ metadata", _filesMetadata); @@ -19,7 +18,7 @@ void main() { } Future _files() async { - final xml = XmlDocument.parse(""" + const xml = """ _files() async { -"""); - final results = await WebdavResponseParser().parseFiles(xml); +"""; + final results = await FileParser().parse(xml); expect(results, [ File( - path: "remote.php/dav/files/admin/Nextcloud intro.mp4", + href: "/remote.php/dav/files/admin/Nextcloud intro.mp4", contentLength: 3963036, contentType: "video/mp4", etag: "1324f58d4d5c8d81bed6e4ed9d5ea862", @@ -58,7 +57,7 @@ Future _files() async { } Future _files404props() async { - final xml = XmlDocument.parse(""" + const xml = """ _files404props() async { -"""); - final results = await WebdavResponseParser().parseFiles(xml); +"""; + final results = await FileParser().parse(xml); expect(results, [ File( - path: "remote.php/dav/files/admin/Nextcloud intro.mp4", + href: "/remote.php/dav/files/admin/Nextcloud intro.mp4", contentLength: 3963036, contentType: "video/mp4", etag: "1324f58d4d5c8d81bed6e4ed9d5ea862", @@ -104,7 +103,7 @@ Future _files404props() async { } Future _filesMetadata() async { - final xml = XmlDocument.parse(""" + const xml = """ _filesMetadata() async { -"""); - final results = await WebdavResponseParser().parseFiles(xml); +"""; + final results = await FileParser().parse(xml); expect(results, [ File( - path: "remote.php/dav/files/admin/Photos/Nextcloud community.jpg", + href: "/remote.php/dav/files/admin/Photos/Nextcloud community.jpg", contentLength: 797325, contentType: "image/jpeg", etag: "8950e39a034e369237d9285e2d815a50", @@ -139,18 +138,16 @@ Future _filesMetadata() async { hasPreview: true, fileId: 123, isCollection: false, - metadata: Metadata( - lastUpdated: DateTime.utc(2021, 1, 2, 3, 4, 5, 678), - fileEtag: "8950e39a034e369237d9285e2d815a50", - imageWidth: 3000, - imageHeight: 2000, - ), + customProperties: { + "com.nkming.nc_photos:metadata": + "{\"version\":2,\"lastUpdated\":\"2021-01-02T03:04:05.678Z\",\"fileEtag\":\"8950e39a034e369237d9285e2d815a50\",\"imageWidth\":3000,\"imageHeight\":2000}", + }, ), ]); } Future _filesIsArchived() async { - final xml = XmlDocument.parse(""" + const xml = """ _filesIsArchived() async { -"""); - final results = await WebdavResponseParser().parseFiles(xml); +"""; + final results = await FileParser().parse(xml); expect(results, [ File( - path: "remote.php/dav/files/admin/Photos/Nextcloud community.jpg", + href: "/remote.php/dav/files/admin/Photos/Nextcloud community.jpg", contentLength: 797325, contentType: "image/jpeg", etag: "8950e39a034e369237d9285e2d815a50", lastModified: DateTime.utc(2021, 1, 1, 2, 3, 4), hasPreview: true, isCollection: false, - isArchived: true, + customProperties: { + "com.nkming.nc_photos:is-archived": "true", + }, ), ]); } Future _filesOverrideDateTime() async { - final xml = XmlDocument.parse(""" + const xml = """ _filesOverrideDateTime() async { -"""); - final results = await WebdavResponseParser().parseFiles(xml); +"""; + final results = await FileParser().parse(xml); expect(results, [ File( - path: "remote.php/dav/files/admin/Photos/Nextcloud community.jpg", + href: "/remote.php/dav/files/admin/Photos/Nextcloud community.jpg", contentLength: 797325, contentType: "image/jpeg", etag: "8950e39a034e369237d9285e2d815a50", lastModified: DateTime.utc(2021, 1, 1, 2, 3, 4), hasPreview: true, isCollection: false, - overrideDateTime: DateTime.utc(2021, 1, 2, 3, 4, 5), + customProperties: { + "com.nkming.nc_photos:override-date-time": "2021-01-02T03:04:05.000Z", + }, ), ]); } Future _filesMultiple() async { - final xml = XmlDocument.parse(""" + const xml = """ _filesMultiple() async { -"""); - final results = await WebdavResponseParser().parseFiles(xml); +"""; + final results = await FileParser().parse(xml); expect(results, [ File( - path: "remote.php/dav/files/admin/Nextcloud intro.mp4", + href: "/remote.php/dav/files/admin/Nextcloud intro.mp4", contentLength: 3963036, contentType: "video/mp4", etag: "1324f58d4d5c8d81bed6e4ed9d5ea862", @@ -280,7 +281,7 @@ Future _filesMultiple() async { isCollection: false, ), File( - path: "remote.php/dav/files/admin/Nextcloud.png", + href: "/remote.php/dav/files/admin/Nextcloud.png", contentLength: 50598, contentType: "image/png", etag: "48689d5b17c449d9db492ffe8f7ab8a6", @@ -288,18 +289,16 @@ Future _filesMultiple() async { hasPreview: true, fileId: 124, isCollection: false, - metadata: Metadata( - fileEtag: "48689d5b17c449d9db492ffe8f7ab8a6", - imageWidth: 500, - imageHeight: 500, - lastUpdated: DateTime.utc(2021, 1, 2, 3, 4, 5, 678), - ), + customProperties: { + "com.nkming.nc_photos:metadata": + "{\"version\":2,\"lastUpdated\":\"2021-01-02T03:04:05.678000Z\",\"fileEtag\":\"48689d5b17c449d9db492ffe8f7ab8a6\",\"imageWidth\":500,\"imageHeight\":500}", + }, ), ]); } Future _filesDir() async { - final xml = XmlDocument.parse(""" + const xml = """ _filesDir() async { -"""); - final results = await WebdavResponseParser().parseFiles(xml); +"""; + final results = await FileParser().parse(xml); expect(results, [ File( - path: "remote.php/dav/files/admin/Photos", + href: "/remote.php/dav/files/admin/Photos/", etag: "123456789abcd", lastModified: DateTime.utc(2021, 1, 1, 2, 3, 4), isCollection: true, @@ -344,7 +343,7 @@ Future _filesDir() async { } Future _filesServerHostedInSubdir() async { - final xml = XmlDocument.parse(""" + const xml = """ _filesServerHostedInSubdir() async { -"""); - final results = await WebdavResponseParser().parseFiles(xml); +"""; + final results = await FileParser().parse(xml); expect(results, [ File( - path: "remote.php/dav/files/admin/Nextcloud intro.mp4", + href: "/nextcloud/remote.php/dav/files/admin/Nextcloud intro.mp4", contentLength: 3963036, contentType: "video/mp4", etag: "1324f58d4d5c8d81bed6e4ed9d5ea862", diff --git a/np_api/test/entity/person_parser_test.dart b/np_api/test/entity/person_parser_test.dart new file mode 100644 index 00000000..95e8c15e --- /dev/null +++ b/np_api/test/entity/person_parser_test.dart @@ -0,0 +1,47 @@ +import 'package:np_api/np_api.dart'; +import 'package:test/test.dart'; + +void main() { + group("PersonParser", () { + test("parse", _persons); + }); +} + +Future _persons() async { + const json = """ +{ + "ocs": { + "meta": { + "status": "ok", + "statuscode": 200, + "message": "OK" + }, + "data": [ + { + "name": "Random Person", + "thumbFaceId": 1, + "count": 3 + }, + { + "name": "Random Cat", + "thumbFaceId": 10, + "count": 4 + } + ] + } +} +"""; + final results = await PersonParser().parse(json); + expect(results, const [ + Person( + name: "Random Person", + thumbFaceId: 1, + count: 3, + ), + Person( + name: "Random Cat", + thumbFaceId: 10, + count: 4, + ), + ]); +} diff --git a/np_api/test/entity/share_parser_test.dart b/np_api/test/entity/share_parser_test.dart new file mode 100644 index 00000000..f0fa2fbb --- /dev/null +++ b/np_api/test/entity/share_parser_test.dart @@ -0,0 +1,81 @@ +import 'package:np_api/np_api.dart'; +import 'package:test/test.dart'; + +void main() { + group("ShareParser", () { + test("parse", _shares); + }); +} + +Future _shares() async { + const xml = """ +{ + "ocs": { + "meta": { + "status": "ok", + "statuscode": 200, + "message": "OK" + }, + "data": [ + { + "id": "123", + "share_type": 0, + "uid_owner": "admin", + "displayname_owner": "super", + "permissions": 19, + "can_edit": true, + "can_delete": true, + "stime": 1672531200, + "parent": null, + "expiration": null, + "token": null, + "uid_file_owner": "admin", + "note": "", + "label": null, + "displayname_file_owner": "awesome-admin", + "path": "/Nextcloud.png", + "item_type": "file", + "mimetype": "image/png", + "has_preview": true, + "storage_id": "home::admin", + "storage": 1, + "item_source": 123456, + "file_source": 123456, + "file_parent": 1, + "file_target": "/Nextcloud.png", + "share_with": "user", + "share_with_displayname": "awesome", + "share_with_displayname_unique": "awesome", + "status": { + "status": "offline", + "message": null, + "icon": null, + "clearAt": null + }, + "mail_send": 0, + "hide_download": 0, + "url": "http://192.168.0.1/s/NCNxZJkkqdGPF4J" + } + ] + } +} +"""; + final results = await ShareParser().parse(xml); + expect(results, const [ + Share( + id: "123", + shareType: 0, + stime: 1672531200, + uidOwner: "admin", + displaynameOwner: "super", + uidFileOwner: "admin", + path: "/Nextcloud.png", + itemType: "file", + mimeType: "image/png", + itemSource: 123456, + shareWith: "user", + shareWithDisplayName: "awesome", + url: "http://192.168.0.1/s/NCNxZJkkqdGPF4J", + ), + ]); +} diff --git a/np_api/test/entity/sharee_parser_test.dart b/np_api/test/entity/sharee_parser_test.dart new file mode 100644 index 00000000..33a7fad6 --- /dev/null +++ b/np_api/test/entity/sharee_parser_test.dart @@ -0,0 +1,73 @@ +import 'package:np_api/np_api.dart'; +import 'package:test/test.dart'; + +void main() { + group("ShareeParser", () { + test("parse", _sharees); + }); +} + +Future _sharees() async { + const json = """ +{ + "ocs": { + "meta": { + "status": "ok", + "statuscode": 100, + "message": "OK", + "totalitems": "", + "itemsperpage": "" + }, + "data": { + "exact": { + "users": [], + "groups": [], + "remotes": [], + "remote_groups": [], + "emails": [], + "circles": [], + "rooms": [], + "deck": [] + }, + "users": [ + { + "label": "user", + "subline": "", + "icon": "icon-user", + "value": { + "shareType": 0, + "shareWith": "user" + }, + "shareWithDisplayNameUnique": "user", + "status": { + "status": "offline", + "message": null, + "icon": null, + "clearAt": null + } + } + ], + "groups": [], + "remotes": [], + "remote_groups": [], + "emails": [], + "lookup": [], + "circles": [], + "rooms": [], + "deck": [], + "lookupEnabled": true + } + } +} +"""; + final results = await ShareeParser().parse(json); + expect(results, const [ + Sharee( + type: "users", + label: "user", + shareType: 0, + shareWith: "user", + shareWithDisplayNameUnique: "user", + ), + ]); +} diff --git a/np_api/test/entity/tag_parser_test.dart b/np_api/test/entity/tag_parser_test.dart new file mode 100644 index 00000000..35d1ccca --- /dev/null +++ b/np_api/test/entity/tag_parser_test.dart @@ -0,0 +1,52 @@ +import 'package:np_api/np_api.dart'; +import 'package:test/test.dart'; + +void main() { + group("TagParser", () { + test("parse", _tags); + }); +} + +Future _tags() async { + const xml = """ + + + + /remote.php/dav/systemtags/ + + + + + + + + + HTTP/1.1 404 Not Found + + + + /remote.php/dav/systemtags/1 + + + 1 + super-tag + true + true + true + + HTTP/1.1 200 OK + + + +"""; + final results = await TagParser().parse(xml); + expect(results, const [ + Tag( + href: "/remote.php/dav/systemtags/1", + id: 1, + displayName: "super-tag", + userVisible: true, + userAssignable: true, + ), + ]); +} diff --git a/np_api/test/entity/tagged_file_parser_test.dart b/np_api/test/entity/tagged_file_parser_test.dart new file mode 100644 index 00000000..ee0a2103 --- /dev/null +++ b/np_api/test/entity/tagged_file_parser_test.dart @@ -0,0 +1,33 @@ +import 'package:np_api/np_api.dart'; +import 'package:test/test.dart'; + +void main() { + group("TaggedFileParser", () { + test("parse", _taggedFiles); + }); +} + +Future _taggedFiles() async { + const xml = """ + + + + HTTP/1.1 200 OK + /remote.php/dav/files/admin/Nextcloud.png + + + 12345 + + HTTP/1.1 200 OK + + + +"""; + final results = await TaggedFileParser().parse(xml); + expect(results, const [ + TaggedFile( + href: "/remote.php/dav/files/admin/Nextcloud.png", + fileId: 12345, + ), + ]); +} diff --git a/np_common/.gitignore b/np_common/.gitignore new file mode 100644 index 00000000..96486fd9 --- /dev/null +++ b/np_common/.gitignore @@ -0,0 +1,30 @@ +# Miscellaneous +*.class +*.log +*.pyc +*.swp +.DS_Store +.atom/ +.buildlog/ +.history +.svn/ +migrate_working_dir/ + +# IntelliJ related +*.iml +*.ipr +*.iws +.idea/ + +# The .vscode folder contains launch configuration and tasks you configure in +# VS Code which you may wish to be included in version control, so this line +# is commented out by default. +#.vscode/ + +# Flutter/Dart/Pub related +# Libraries should not include pubspec.lock, per https://dart.dev/guides/libraries/private-files#pubspeclock. +/pubspec.lock +**/doc/api/ +.dart_tool/ +.packages +build/ diff --git a/np_common/.metadata b/np_common/.metadata new file mode 100644 index 00000000..fbfa6dfb --- /dev/null +++ b/np_common/.metadata @@ -0,0 +1,10 @@ +# This file tracks properties of this Flutter project. +# Used by Flutter tool to assess capabilities and perform upgrades etc. +# +# This file should be version controlled and should not be manually edited. + +version: + revision: b8f7f1f9869bb2d116aa6a70dbeac61000b52849 + channel: stable + +project_type: package diff --git a/np_common/analysis_options.yaml b/np_common/analysis_options.yaml new file mode 100644 index 00000000..23babb48 --- /dev/null +++ b/np_common/analysis_options.yaml @@ -0,0 +1,7 @@ +include: package:flutter_lints/flutter.yaml + +linter: + rules: + unawaited_futures: true + avoid_void_async: true + directives_ordering: true diff --git a/app/lib/ci_string.dart b/np_common/lib/ci_string.dart similarity index 97% rename from app/lib/ci_string.dart rename to np_common/lib/ci_string.dart index 8e63ab06..4b1430b7 100644 --- a/app/lib/ci_string.dart +++ b/np_common/lib/ci_string.dart @@ -1,4 +1,4 @@ -import 'package:nc_photos/string_extension.dart'; +import 'package:np_common/string_extension.dart'; /// Case-insensitive string class CiString implements Comparable { diff --git a/np_common/lib/log.dart b/np_common/lib/log.dart new file mode 100644 index 00000000..8ca09aa1 --- /dev/null +++ b/np_common/lib/log.dart @@ -0,0 +1,56 @@ +import 'dart:async'; + +import 'package:flutter/foundation.dart'; +import 'package:logging/logging.dart'; + +void initLog() { + Logger.root.level = kReleaseMode ? Level.WARNING : Level.ALL; + Logger.root.onRecord.listen((record) { + String msg = + "[${record.loggerName}] ${record.level.name} ${record.time}: ${record.message}"; + if (record.error != null) { + msg += " (throw: ${record.error.runtimeType} { ${record.error} })"; + } + if (record.stackTrace != null) { + msg += "\nStack Trace:\n${record.stackTrace}"; + } + + if (kDebugMode) { + // show me colors! + int color; + if (record.level >= Level.SEVERE) { + color = 91; + } else if (record.level >= Level.WARNING) { + color = 33; + } else if (record.level >= Level.INFO) { + color = 34; + } else if (record.level >= Level.FINER) { + color = 32; + } else { + color = 90; + } + msg = "\x1B[${color}m$msg\x1B[0m"; + } + debugPrint(msg, wrapWidth: 1024); + LogStream().add(msg); + }); +} + +class LogStream { + factory LogStream() { + _inst ??= LogStream._(); + return _inst!; + } + + LogStream._(); + + void add(String log) { + _stream.add(log); + } + + Stream get stream => _stream.stream; + + static LogStream? _inst; + + final _stream = StreamController.broadcast(); +} diff --git a/app/lib/string_extension.dart b/np_common/lib/string_extension.dart similarity index 100% rename from app/lib/string_extension.dart rename to np_common/lib/string_extension.dart diff --git a/app/lib/type.dart b/np_common/lib/type.dart similarity index 100% rename from app/lib/type.dart rename to np_common/lib/type.dart diff --git a/np_common/pubspec.yaml b/np_common/pubspec.yaml new file mode 100644 index 00000000..b1569761 --- /dev/null +++ b/np_common/pubspec.yaml @@ -0,0 +1,55 @@ +name: np_common +description: A new Flutter package project. +version: 0.0.1 +homepage: +publish_to: none + +environment: + sdk: '>=2.18.0 <3.0.0' + flutter: ">=3.3.0" + +dependencies: + flutter: + sdk: flutter + logging: ^1.0.1 + +dev_dependencies: + flutter_lints: ^2.0.0 + test: any + +# For information on the generic Dart part of this file, see the +# following page: https://dart.dev/tools/pub/pubspec + +# The following section is specific to Flutter packages. +flutter: + + # To add assets to your package, add an assets section, like this: + # assets: + # - images/a_dot_burr.jpeg + # - images/a_dot_ham.jpeg + # + # For details regarding assets in packages, see + # https://flutter.dev/assets-and-images/#from-packages + # + # An image asset can refer to one or more resolution-specific "variants", see + # https://flutter.dev/assets-and-images/#resolution-aware + + # To add custom fonts to your package, add a fonts section here, + # in this "flutter" section. Each entry in this list should have a + # "family" key with the font family name, and a "fonts" key with a + # list giving the asset and other descriptors for the font. For + # example: + # fonts: + # - family: Schyler + # fonts: + # - asset: fonts/Schyler-Regular.ttf + # - asset: fonts/Schyler-Italic.ttf + # style: italic + # - family: Trajan Pro + # fonts: + # - asset: fonts/TrajanPro.ttf + # - asset: fonts/TrajanPro_Bold.ttf + # weight: 700 + # + # For details regarding fonts in packages, see + # https://flutter.dev/custom-fonts/#from-packages diff --git a/app/test/ci_string_test.dart b/np_common/test/ci_string_test.dart similarity index 98% rename from app/test/ci_string_test.dart rename to np_common/test/ci_string_test.dart index 05754dfa..f5531754 100644 --- a/app/test/ci_string_test.dart +++ b/np_common/test/ci_string_test.dart @@ -1,6 +1,6 @@ // ignore_for_file: unrelated_type_equality_checks -import 'package:nc_photos/ci_string.dart'; +import 'package:np_common/ci_string.dart'; import 'package:test/test.dart'; void main() { diff --git a/app/test/string_extension_test.dart b/np_common/test/string_extension_test.dart similarity index 96% rename from app/test/string_extension_test.dart rename to np_common/test/string_extension_test.dart index 4cfab836..9ac2e24b 100644 --- a/app/test/string_extension_test.dart +++ b/np_common/test/string_extension_test.dart @@ -1,4 +1,4 @@ -import 'package:nc_photos/string_extension.dart'; +import 'package:np_common/string_extension.dart'; import 'package:test/test.dart'; void main() {