Refactor: move nextcloud api to dedicated pacakge

This commit is contained in:
Ming Ming 2023-02-23 22:49:17 +08:00
parent 6e00d44871
commit 7a17f2b274
133 changed files with 2668 additions and 952 deletions

View file

@ -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';

View file

@ -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';

View file

@ -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,
);
}
}

View file

@ -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");
}

View file

@ -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';

View file

@ -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';

View file

@ -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';

View file

@ -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';

View file

@ -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 {

View file

@ -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

View file

@ -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';

View file

@ -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';

View file

@ -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';

View file

@ -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';

View file

@ -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';

View file

@ -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';

View file

@ -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';

View file

@ -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<JsonObj> dataJson = json["ocs"]["data"].cast<JsonObj>();
return _FaceParser().parseList(dataJson);
}
}
@npLog
class _FaceParser {
List<Face> parseList(List<JsonObj> jsons) {
final product = <Face>[];
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();
}
}

View file

@ -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");
}

View file

@ -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();
}
}

View file

@ -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';

View file

@ -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<String, String>? customNamespaces,
List<String>? 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;

View file

@ -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(

View file

@ -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);

View file

@ -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<JsonObj> dataJson = json["ocs"]["data"].cast<JsonObj>();
return _PersonParser().parseList(dataJson);
final apiPersons = await api.PersonParser().parse(response.body);
return apiPersons.map(ApiPersonConverter.fromApi).toList();
}
}

View file

@ -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';

View file

@ -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';

View file

@ -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<Share> _onListResult(Response response) {
Future<List<Share>> _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<JsonObj> dataJson = json["ocs"]["data"].cast<JsonObj>();
return _ShareParser().parseList(dataJson);
final apiShares = await api.ShareParser().parse(response.body);
return apiShares.map(ApiShareConverter.fromApi).toList();
}
}

View file

@ -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';

View file

@ -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();
}
}

View file

@ -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,

View file

@ -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<sql.Tag> {
Future<List<Tag>> convertToAppTag() {

View file

@ -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';

View file

@ -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();
}
}

View file

@ -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<Tag> 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();
}
}

View file

@ -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<List<File>> parseFiles(XmlDocument xml) =>
compute(_parseFilesIsolate, xml);
Future<List<Favorite>> parseFavorites(XmlDocument xml) =>
compute(_parseFavoritesIsolate, xml);
Future<List<Tag>> parseTags(XmlDocument xml) =>
compute(_parseTagsIsolate, xml);
Future<List<TaggedFile>> parseTaggedFiles(XmlDocument xml) =>
compute(_parseTaggedFilesIsolate, xml);
Map<String, String> get namespaces => _namespaces;
List<File> _parseFiles(XmlDocument xml) => _parse<File>(xml, _toFile);
List<Favorite> _parseFavorites(XmlDocument xml) =>
_parse<Favorite>(xml, _toFavorite);
List<Tag> _parseTags(XmlDocument xml) => _parse<Tag>(xml, _toTag);
List<TaggedFile> _parseTaggedFiles(XmlDocument xml) =>
_parse<TaggedFile>(xml, _toTaggedFile);
List<T> _parse<T>(XmlDocument xml, T? Function(XmlElement) mapper) {
_namespaces = _parseNamespaces(xml);
final body = () {
try {
return xml.children.whereType<XmlElement>().firstWhere((element) =>
element.matchQualifiedName("multistatus",
prefix: "DAV:", namespaces: _namespaces));
} catch (_) {
_log.shout("[_parse] Missing element: multistatus");
rethrow;
}
}();
return body.children
.whereType<XmlElement>()
.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<T>()
.toList();
}
Map<String, String> _parseNamespaces(XmlDocument xml) {
final namespaces = <String, String>{};
final xmlContent = xml.descendants.whereType<XmlElement>().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 <DAV:response> 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<XmlElement>()) {
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<XmlElement>()
.firstWhere((element) => element.matchQualifiedName("status",
prefix: "DAV:", namespaces: _namespaces))
.innerText;
if (!status.contains(" 200 ")) {
continue;
}
final prop = child.children.whereType<XmlElement>().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 <DAV:response> contents to Favorite
Favorite _toFavorite(XmlElement element) {
String? path;
int? fileId;
for (final child in element.children.whereType<XmlElement>()) {
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<XmlElement>()
.firstWhere((element) => element.matchQualifiedName("status",
prefix: "DAV:", namespaces: _namespaces))
.innerText;
if (!status.contains(" 200 ")) {
continue;
}
final prop = child.children.whereType<XmlElement>().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 <DAV:response> contents to Tag
Tag? _toTag(XmlElement element) {
String? path;
int? id;
String? displayName;
bool? userVisible;
bool? userAssignable;
for (final child in element.children.whereType<XmlElement>()) {
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<XmlElement>()
.firstWhere((element) => element.matchQualifiedName("status",
prefix: "DAV:", namespaces: _namespaces))
.innerText;
if (!status.contains(" 200 ")) {
continue;
}
final prop = child.children.whereType<XmlElement>().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 <DAV:response> contents to TaggedFile
TaggedFile _toTaggedFile(XmlElement element) {
String? path;
int? fileId;
for (final child in element.children.whereType<XmlElement>()) {
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<XmlElement>()
.firstWhere((element) => element.matchQualifiedName("status",
prefix: "DAV:", namespaces: _namespaces))
.innerText;
if (!status.contains(" 200 ")) {
continue;
}
final prop = child.children.whereType<XmlElement>().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 = <String, String>{};
}
class _FilePropParser {
_FilePropParser({
this.namespaces = const {},
this.logFilePath,
});
/// Parse <DAV:prop> element contents
void parse(XmlElement element) {
for (final child in element.children.whereType<XmlElement>()) {
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<XmlElement>().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<XmlElement>()) {
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<String, String> 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 <DAV:prop> element contents
void parse(XmlElement element) {
for (final child in element.children.whereType<XmlElement>()) {
if (child.matchQualifiedName("fileid",
prefix: "http://owncloud.org/ns", namespaces: namespaces)) {
_fileId = int.parse(child.innerText);
}
}
}
int? get fileId => _fileId;
final Map<String, String> namespaces;
/// File path for logging only
final String? logFilePath;
int? _fileId;
}
class _TagPropParser {
_TagPropParser({
this.namespaces = const {},
this.logFilePath,
});
/// Parse <DAV:prop> element contents
void parse(XmlElement element) {
for (final child in element.children.whereType<XmlElement>()) {
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<String, String> 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<String, String> namespaces,
}) {
final localNamespaces = <String, String>{};
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<File> _parseFilesIsolate(XmlDocument xml) {
app_init.initLog();
return WebdavResponseParser()._parseFiles(xml);
}
List<Favorite> _parseFavoritesIsolate(XmlDocument xml) {
app_init.initLog();
return WebdavResponseParser()._parseFavorites(xml);
}
List<Tag> _parseTagsIsolate(XmlDocument xml) {
app_init.initLog();
return WebdavResponseParser()._parseTags(xml);
}
List<TaggedFile> _parseTaggedFilesIsolate(XmlDocument xml) {
app_init.initLog();
return WebdavResponseParser()._parseTaggedFiles(xml);
}

View file

@ -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]);

View file

@ -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<String> _exchangePassword(Account account) async {
final response = await Api(account).request(
final response = await ApiUtil.fromAccount(account).request(
"GET",
"ocs/v2.php/core/getapppassword",
header: {

View file

@ -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';

View file

@ -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';

View file

@ -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);

View file

@ -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';

View file

@ -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';

14
app/lib/np_api_util.dart Normal file
View file

@ -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);
}

View file

@ -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';

View file

@ -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';

View file

@ -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,

View file

@ -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);
}

View file

@ -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);

View file

@ -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';

View file

@ -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<String> 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(

View file

@ -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';

View file

@ -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';

View file

@ -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';

View file

@ -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';

View file

@ -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) {

View file

@ -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';

View file

@ -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,

View file

@ -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';

View file

@ -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';

View file

@ -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<ImageEditor> {
3072,
_buildFilterList(),
headers: {
"Authorization": Api.getAuthorizationHeaderValue(widget.account),
"Authorization": AuthUtil.fromAccount(widget.account).toHeaderValue(),
},
isSaveToServer: c.pref.isSaveEditResultToServerOr(),
);

View file

@ -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<ImageEnhancer> {
_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<ImageEnhancer> {
_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<ImageEnhancer> {
_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<ImageEnhancer> {
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<ImageEnhancer> {
_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<ImageEnhancer> {
_c.pref.getEnhanceMaxWidthOr(),
_c.pref.getEnhanceMaxHeightOr(),
headers: {
"Authorization": Api.getAuthorizationHeaderValue(widget.account),
"Authorization":
AuthUtil.fromAccount(widget.account).toHeaderValue(),
},
isSaveToServer: widget.isSaveToServer,
);

View file

@ -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<RemoteImageViewer> {
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<RemoteImageViewer> {
imageUrl: _getImageUrl(widget.account, widget.file),
httpHeaders: {
"Authorization":
Api.getAuthorizationHeaderValue(widget.account),
AuthUtil.fromAccount(widget.account).toHeaderValue(),
},
fit: BoxFit.contain,
fadeInDuration: const Duration(),

View file

@ -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,

View file

@ -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';

View file

@ -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<SharedFileViewer> {
imageUrl: previewUrl,
httpHeaders: {
"Authorization":
Api.getAuthorizationHeaderValue(widget.account),
AuthUtil.fromAccount(widget.account).toHeaderValue(),
},
fadeInDuration: const Duration(),
filterQuality: FilterQuality.high,

View file

@ -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';

View file

@ -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({

View file

@ -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<VideoViewer>
_controller = VideoPlayerController.network(
url,
httpHeaders: {
"Authorization": Api.getAuthorizationHeaderValue(widget.account),
"Authorization": AuthUtil.fromAccount(widget.account).toHeaderValue(),
},
);
await _controller.initialize();

View file

@ -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"

View file

@ -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:

View file

@ -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() {

View file

@ -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<void> _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<void> _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<void> _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<void> _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<void> _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<void> _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<void> _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,
),
);
}

View file

@ -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';

View file

@ -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;

View file

@ -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;

View file

@ -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;

View file

@ -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() {

View file

@ -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

View file

@ -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 {

View file

@ -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';

View file

@ -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';

View file

@ -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';

30
np_api/.gitignore vendored Normal file
View file

@ -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/

10
np_api/.metadata Normal file
View file

@ -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

View file

@ -0,0 +1,7 @@
include: package:flutter_lints/flutter.yaml
linter:
rules:
unawaited_futures: true
avoid_void_async: true
directives_ordering: true

11
np_api/lib/np_api.dart Normal file
View file

@ -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';

View file

@ -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<String, String> 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<Response> 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<String, String>? 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);

View file

@ -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");
}

View file

@ -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<Object?> 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<Object?> 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<Object?> 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<String, String>? customProperties;
}
@toString
class Person with EquatableMixin {
const Person({
required this.name,
required this.thumbFaceId,
required this.count,
});
@override
String toString() => _$toString();
@override
List<Object?> 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<Object?> 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<Object?> 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<Object?> 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<Object?> get props => [
href,
fileId,
];
final String href;
final int fileId;
}

View file

@ -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}";
}
}

View file

@ -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<List<Face>> parse(String response) =>
compute(_parseFacesIsolate, response);
List<Face> _parse(JsonObj json) {
final jsons = json["ocs"]["data"].cast<JsonObj>();
final products = <Face>[];
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<Face> _parseFacesIsolate(String response) {
initLog();
final json = (jsonDecode(response) as Map).cast<String, dynamic>();
return FaceParser()._parse(json);
}

View file

@ -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");
}

View file

@ -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<List<Favorite>> parse(String response) =>
compute(_parseFavoritesIsolate, response);
List<Favorite> _parse(XmlDocument xml) => parseT<Favorite>(xml, _toFavorite);
/// Map <DAV:response> contents to Favorite
Favorite _toFavorite(XmlElement element) {
String? href;
int? fileId;
for (final child in element.children.whereType<XmlElement>()) {
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<XmlElement>()
.firstWhere((element) => element.matchQualifiedName("status",
prefix: "DAV:", namespaces: namespaces))
.innerText;
if (!status.contains(" 200 ")) {
continue;
}
final prop = child.children.whereType<XmlElement>().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 <DAV:prop> element contents
void parse(XmlElement element) {
for (final child in element.children.whereType<XmlElement>()) {
if (child.matchQualifiedName("fileid",
prefix: "http://owncloud.org/ns", namespaces: namespaces)) {
_fileId = int.parse(child.innerText);
}
}
}
int? get fileId => _fileId;
final Map<String, String> namespaces;
int? _fileId;
}
List<Favorite> _parseFavoritesIsolate(String response) {
initLog();
final xml = XmlDocument.parse(response);
return FavoriteParser()._parse(xml);
}

View file

@ -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<List<File>> parse(String response) =>
compute(_parseFilesIsolate, response);
List<File> _parse(XmlDocument xml) => parseT<File>(xml, _toFile);
/// Map <DAV:response> 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<String, String>? customProperties;
for (final child in element.children.whereType<XmlElement>()) {
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<XmlElement>()
.firstWhere((element) => element.matchQualifiedName("status",
prefix: "DAV:", namespaces: namespaces))
.innerText;
if (!status.contains(" 200 ")) {
continue;
}
final prop = child.children.whereType<XmlElement>().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 <DAV:prop> element contents
void parse(XmlElement element) {
for (final child in element.children.whereType<XmlElement>()) {
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<XmlElement>().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<String, String>? get customProperties => _customProperties;
final Map<String, String> 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<String, String>? _customProperties;
}
List<File> _parseFilesIsolate(String response) {
initLog();
final xml = XmlDocument.parse(response);
return FileParser()._parse(xml);
}
String _expandNamespace(XmlElement element, Map<String, String> namespaces) {
if (namespaces.containsKey(element.name.prefix)) {
return namespaces[element.name.prefix]!;
}
final localNamespaces = <String, String>{};
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!;
}

View file

@ -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");
}

View file

@ -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<T> parseT<T>(XmlDocument xml, T? Function(XmlElement) mapper) {
namespaces = _parseNamespaces(xml);
final body = () {
try {
return xml.children.whereType<XmlElement>().firstWhere((element) =>
element.matchQualifiedName("multistatus",
prefix: "DAV:", namespaces: namespaces));
} catch (_) {
_log.shout("[_parse] Missing element: multistatus");
rethrow;
}
}();
return body.children
.whereType<XmlElement>()
.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<T>()
.toList();
}
Map<String, String> _parseNamespaces(XmlDocument xml) {
final namespaces = <String, String>{};
final xmlContent = xml.descendants.whereType<XmlElement>().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 = <String, String>{};
}
extension XmlElementExtension on XmlElement {
bool matchQualifiedName(
String local, {
required String prefix,
required Map<String, String> namespaces,
}) {
final localNamespaces = <String, String>{};
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));
}
}

View file

@ -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");
}

View file

@ -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<List<Person>> parse(String response) =>
compute(_parsePersonsIsolate, response);
List<Person> _parse(JsonObj json) {
final jsons = json["ocs"]["data"].cast<JsonObj>();
final products = <Person>[];
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<Person> _parsePersonsIsolate(String response) {
initLog();
final json = (jsonDecode(response) as Map).cast<String, dynamic>();
return PersonParser()._parse(json);
}

Some files were not shown because too many files have changed in this diff Show more