Refactor: make webdav parser more generic

This commit is contained in:
Ming Ming 2022-01-26 03:36:41 +08:00
parent 313d5f3dcb
commit 4c88070a7e
3 changed files with 177 additions and 164 deletions

View file

@ -62,7 +62,7 @@ class FileWebdavDataSource implements FileDataSource {
} }
final xml = XmlDocument.parse(response.body); final xml = XmlDocument.parse(response.body);
var files = WebdavFileParser()(xml); var files = WebdavResponseParser().parseFiles(xml);
// _log.fine("[list] Parsed files: [$files]"); // _log.fine("[list] Parsed files: [$files]");
files = files.where((element) => _validateFile(element)).map((e) { files = files.where((element) => _validateFile(element)).map((e) {
if (e.metadata == null || e.metadata!.fileEtag == e.etag) { if (e.metadata == null || e.metadata!.fileEtag == e.etag) {

View file

@ -7,8 +7,12 @@ import 'package:nc_photos/entity/file.dart';
import 'package:nc_photos/string_extension.dart'; import 'package:nc_photos/string_extension.dart';
import 'package:xml/xml.dart'; import 'package:xml/xml.dart';
class WebdavFileParser { class WebdavResponseParser {
List<File> call(XmlDocument xml) { List<File> parseFiles(XmlDocument xml) => _parse<File>(xml, _toFile);
Map<String, String> get namespaces => _namespaces;
List<T> _parse<T>(XmlDocument xml, T Function(XmlElement) mapper) {
_namespaces = _parseNamespaces(xml); _namespaces = _parseNamespaces(xml);
final body = () { final body = () {
try { try {
@ -16,28 +20,26 @@ class WebdavFileParser {
element.matchQualifiedName("multistatus", element.matchQualifiedName("multistatus",
prefix: "DAV:", namespaces: _namespaces)); prefix: "DAV:", namespaces: _namespaces));
} catch (_) { } catch (_) {
_log.shout("[call] Missing element: multistatus"); _log.shout("[_parse] Missing element: multistatus");
rethrow; rethrow;
} }
}(); }();
return body.children return body.children
.whereType<XmlElement>() .whereType<XmlElement>()
.where((element) => element.matchQualifiedName("response", .where((e) => e.matchQualifiedName("response",
prefix: "DAV:", namespaces: _namespaces)) prefix: "DAV:", namespaces: _namespaces))
.map((element) { .map((e) {
try { try {
return _toFile(element); return mapper(e);
} catch (e, stacktrace) { } catch (e, stackTrace) {
_log.shout("[call] Failed parsing XML", e, stacktrace); _log.shout("[_parse] Failed parsing XML", e, stackTrace);
return null; return null;
} }
}) })
.whereType<File>() .whereType<T>()
.toList(); .toList();
} }
Map<String, String> get namespaces => _namespaces;
Map<String, String> _parseNamespaces(XmlDocument xml) { Map<String, String> _parseNamespaces(XmlDocument xml) {
final namespaces = <String, String>{}; final namespaces = <String, String>{};
final xmlContent = xml.descendants.whereType<XmlElement>().firstWhere( final xmlContent = xml.descendants.whereType<XmlElement>().firstWhere(
@ -91,7 +93,7 @@ class WebdavFileParser {
(element) => element.matchQualifiedName("prop", (element) => element.matchQualifiedName("prop",
prefix: "DAV:", namespaces: _namespaces)); prefix: "DAV:", namespaces: _namespaces));
final propParser = final propParser =
_PropParser(namespaces: _namespaces, logFilePath: path); _FilePropParser(namespaces: _namespaces, logFilePath: path);
propParser.parse(prop); propParser.parse(prop);
contentLength = propParser.contentLength; contentLength = propParser.contentLength;
contentType = propParser.contentType; contentType = propParser.contentType;
@ -149,8 +151,8 @@ class WebdavFileParser {
Logger("entity.webdav_response_parser.WebdavResponseParser"); Logger("entity.webdav_response_parser.WebdavResponseParser");
} }
class _PropParser { class _FilePropParser {
_PropParser({ _FilePropParser({
this.namespaces = const {}, this.namespaces = const {},
this.logFilePath, this.logFilePath,
}); });

View file

@ -5,8 +5,21 @@ import 'package:xml/xml.dart';
void main() { void main() {
group("WebdavFileParser", () { group("WebdavFileParser", () {
test("file", () { group("parseFiles", () {
final xml = XmlDocument.parse(""" test("file", _files);
test("file w/ 404 properties", _files404props);
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);
});
});
}
void _files() {
final xml = XmlDocument.parse("""
<?xml version="1.0"?> <?xml version="1.0"?>
<d:multistatus xmlns:d="DAV:" <d:multistatus xmlns:d="DAV:"
xmlns:s="http://sabredav.org/ns" xmlns:s="http://sabredav.org/ns"
@ -29,23 +42,23 @@ void main() {
</d:response> </d:response>
</d:multistatus> </d:multistatus>
"""); """);
final results = WebdavFileParser()(xml); final results = WebdavResponseParser().parseFiles(xml);
expect(results, [ expect(results, [
File( File(
path: "remote.php/dav/files/admin/Nextcloud intro.mp4", path: "remote.php/dav/files/admin/Nextcloud intro.mp4",
contentLength: 3963036, contentLength: 3963036,
contentType: "video/mp4", contentType: "video/mp4",
etag: "1324f58d4d5c8d81bed6e4ed9d5ea862", etag: "1324f58d4d5c8d81bed6e4ed9d5ea862",
lastModified: DateTime.utc(2021, 1, 1, 2, 3, 4), lastModified: DateTime.utc(2021, 1, 1, 2, 3, 4),
hasPreview: false, hasPreview: false,
fileId: 123, fileId: 123,
isCollection: false, isCollection: false,
), ),
]); ]);
}); }
test("file w/ 404 properties", () { void _files404props() {
final xml = XmlDocument.parse(""" final xml = XmlDocument.parse("""
<?xml version="1.0"?> <?xml version="1.0"?>
<d:multistatus xmlns:d="DAV:" <d:multistatus xmlns:d="DAV:"
xmlns:s="http://sabredav.org/ns" xmlns:s="http://sabredav.org/ns"
@ -75,23 +88,23 @@ void main() {
</d:response> </d:response>
</d:multistatus> </d:multistatus>
"""); """);
final results = WebdavFileParser()(xml); final results = WebdavResponseParser().parseFiles(xml);
expect(results, [ expect(results, [
File( File(
path: "remote.php/dav/files/admin/Nextcloud intro.mp4", path: "remote.php/dav/files/admin/Nextcloud intro.mp4",
contentLength: 3963036, contentLength: 3963036,
contentType: "video/mp4", contentType: "video/mp4",
etag: "1324f58d4d5c8d81bed6e4ed9d5ea862", etag: "1324f58d4d5c8d81bed6e4ed9d5ea862",
lastModified: DateTime.utc(2021, 1, 1, 2, 3, 4), lastModified: DateTime.utc(2021, 1, 1, 2, 3, 4),
hasPreview: false, hasPreview: false,
fileId: 123, fileId: 123,
isCollection: false, isCollection: false,
), ),
]); ]);
}); }
test("file w/ metadata", () { void _filesMetadata() {
final xml = XmlDocument.parse(""" final xml = XmlDocument.parse("""
<?xml version="1.0"?> <?xml version="1.0"?>
<d:multistatus xmlns:d="DAV:" <d:multistatus xmlns:d="DAV:"
xmlns:s="http://sabredav.org/ns" xmlns:s="http://sabredav.org/ns"
@ -115,29 +128,29 @@ void main() {
</d:response> </d:response>
</d:multistatus> </d:multistatus>
"""); """);
final results = WebdavFileParser()(xml); final results = WebdavResponseParser().parseFiles(xml);
expect(results, [ expect(results, [
File( File(
path: "remote.php/dav/files/admin/Photos/Nextcloud community.jpg", path: "remote.php/dav/files/admin/Photos/Nextcloud community.jpg",
contentLength: 797325, contentLength: 797325,
contentType: "image/jpeg", contentType: "image/jpeg",
etag: "8950e39a034e369237d9285e2d815a50", etag: "8950e39a034e369237d9285e2d815a50",
lastModified: DateTime.utc(2021, 1, 1, 2, 3, 4), lastModified: DateTime.utc(2021, 1, 1, 2, 3, 4),
hasPreview: true, hasPreview: true,
fileId: 123, fileId: 123,
isCollection: false, isCollection: false,
metadata: Metadata( metadata: Metadata(
lastUpdated: DateTime.utc(2021, 1, 2, 3, 4, 5, 678), lastUpdated: DateTime.utc(2021, 1, 2, 3, 4, 5, 678),
fileEtag: "8950e39a034e369237d9285e2d815a50", fileEtag: "8950e39a034e369237d9285e2d815a50",
imageWidth: 3000, imageWidth: 3000,
imageHeight: 2000, imageHeight: 2000,
), ),
), ),
]); ]);
}); }
test("file w/ is-archived", () { void _filesIsArchived() {
final xml = XmlDocument.parse(""" final xml = XmlDocument.parse("""
<?xml version="1.0"?> <?xml version="1.0"?>
<d:multistatus xmlns:d="DAV:" <d:multistatus xmlns:d="DAV:"
xmlns:s="http://sabredav.org/ns" xmlns:s="http://sabredav.org/ns"
@ -160,23 +173,23 @@ void main() {
</d:response> </d:response>
</d:multistatus> </d:multistatus>
"""); """);
final results = WebdavFileParser()(xml); final results = WebdavResponseParser().parseFiles(xml);
expect(results, [ expect(results, [
File( File(
path: "remote.php/dav/files/admin/Photos/Nextcloud community.jpg", path: "remote.php/dav/files/admin/Photos/Nextcloud community.jpg",
contentLength: 797325, contentLength: 797325,
contentType: "image/jpeg", contentType: "image/jpeg",
etag: "8950e39a034e369237d9285e2d815a50", etag: "8950e39a034e369237d9285e2d815a50",
lastModified: DateTime.utc(2021, 1, 1, 2, 3, 4), lastModified: DateTime.utc(2021, 1, 1, 2, 3, 4),
hasPreview: true, hasPreview: true,
isCollection: false, isCollection: false,
isArchived: true, isArchived: true,
), ),
]); ]);
}); }
test("file w/ override-date-time", () { void _filesOverrideDateTime() {
final xml = XmlDocument.parse(""" final xml = XmlDocument.parse("""
<?xml version="1.0"?> <?xml version="1.0"?>
<d:multistatus xmlns:d="DAV:" <d:multistatus xmlns:d="DAV:"
xmlns:s="http://sabredav.org/ns" xmlns:s="http://sabredav.org/ns"
@ -199,23 +212,23 @@ void main() {
</d:response> </d:response>
</d:multistatus> </d:multistatus>
"""); """);
final results = WebdavFileParser()(xml); final results = WebdavResponseParser().parseFiles(xml);
expect(results, [ expect(results, [
File( File(
path: "remote.php/dav/files/admin/Photos/Nextcloud community.jpg", path: "remote.php/dav/files/admin/Photos/Nextcloud community.jpg",
contentLength: 797325, contentLength: 797325,
contentType: "image/jpeg", contentType: "image/jpeg",
etag: "8950e39a034e369237d9285e2d815a50", etag: "8950e39a034e369237d9285e2d815a50",
lastModified: DateTime.utc(2021, 1, 1, 2, 3, 4), lastModified: DateTime.utc(2021, 1, 1, 2, 3, 4),
hasPreview: true, hasPreview: true,
isCollection: false, isCollection: false,
overrideDateTime: DateTime.utc(2021, 1, 2, 3, 4, 5), overrideDateTime: DateTime.utc(2021, 1, 2, 3, 4, 5),
), ),
]); ]);
}); }
test("multiple files", () { void _filesMultiple() {
final xml = XmlDocument.parse(""" final xml = XmlDocument.parse("""
<?xml version="1.0"?> <?xml version="1.0"?>
<d:multistatus xmlns:d="DAV:" <d:multistatus xmlns:d="DAV:"
xmlns:s="http://sabredav.org/ns" xmlns:s="http://sabredav.org/ns"
@ -254,39 +267,39 @@ void main() {
</d:response> </d:response>
</d:multistatus> </d:multistatus>
"""); """);
final results = WebdavFileParser()(xml); final results = WebdavResponseParser().parseFiles(xml);
expect(results, [ expect(results, [
File( File(
path: "remote.php/dav/files/admin/Nextcloud intro.mp4", path: "remote.php/dav/files/admin/Nextcloud intro.mp4",
contentLength: 3963036, contentLength: 3963036,
contentType: "video/mp4", contentType: "video/mp4",
etag: "1324f58d4d5c8d81bed6e4ed9d5ea862", etag: "1324f58d4d5c8d81bed6e4ed9d5ea862",
lastModified: DateTime.utc(2021, 1, 1, 2, 3, 4), lastModified: DateTime.utc(2021, 1, 1, 2, 3, 4),
hasPreview: false, hasPreview: false,
fileId: 123, fileId: 123,
isCollection: false, isCollection: false,
), ),
File( File(
path: "remote.php/dav/files/admin/Nextcloud.png", path: "remote.php/dav/files/admin/Nextcloud.png",
contentLength: 50598, contentLength: 50598,
contentType: "image/png", contentType: "image/png",
etag: "48689d5b17c449d9db492ffe8f7ab8a6", etag: "48689d5b17c449d9db492ffe8f7ab8a6",
lastModified: DateTime.utc(2021, 1, 2, 3, 4, 5), lastModified: DateTime.utc(2021, 1, 2, 3, 4, 5),
hasPreview: true, hasPreview: true,
fileId: 124, fileId: 124,
isCollection: false, isCollection: false,
metadata: Metadata( metadata: Metadata(
fileEtag: "48689d5b17c449d9db492ffe8f7ab8a6", fileEtag: "48689d5b17c449d9db492ffe8f7ab8a6",
imageWidth: 500, imageWidth: 500,
imageHeight: 500, imageHeight: 500,
lastUpdated: DateTime.utc(2021, 1, 2, 3, 4, 5, 678), lastUpdated: DateTime.utc(2021, 1, 2, 3, 4, 5, 678),
), ),
), ),
]); ]);
}); }
test("directory", () { void _filesDir() {
final xml = XmlDocument.parse(""" final xml = XmlDocument.parse("""
<?xml version="1.0"?> <?xml version="1.0"?>
<d:multistatus xmlns:d="DAV:" <d:multistatus xmlns:d="DAV:"
xmlns:s="http://sabredav.org/ns" xmlns:s="http://sabredav.org/ns"
@ -317,21 +330,21 @@ void main() {
</d:response> </d:response>
</d:multistatus> </d:multistatus>
"""); """);
final results = WebdavFileParser()(xml); final results = WebdavResponseParser().parseFiles(xml);
expect(results, [ expect(results, [
File( File(
path: "remote.php/dav/files/admin/Photos", path: "remote.php/dav/files/admin/Photos",
etag: "123456789abcd", etag: "123456789abcd",
lastModified: DateTime.utc(2021, 1, 1, 2, 3, 4), lastModified: DateTime.utc(2021, 1, 1, 2, 3, 4),
isCollection: true, isCollection: true,
hasPreview: false, hasPreview: false,
fileId: 123, fileId: 123,
), ),
]); ]);
}); }
test("nextcloud hosed in subdir", () { void _filesServerHostedInSubdir() {
final xml = XmlDocument.parse(""" final xml = XmlDocument.parse("""
<?xml version="1.0"?> <?xml version="1.0"?>
<d:multistatus xmlns:d="DAV:" <d:multistatus xmlns:d="DAV:"
xmlns:s="http://sabredav.org/ns" xmlns:s="http://sabredav.org/ns"
@ -354,19 +367,17 @@ void main() {
</d:response> </d:response>
</d:multistatus> </d:multistatus>
"""); """);
final results = WebdavFileParser()(xml); final results = WebdavResponseParser().parseFiles(xml);
expect(results, [ expect(results, [
File( File(
path: "remote.php/dav/files/admin/Nextcloud intro.mp4", path: "remote.php/dav/files/admin/Nextcloud intro.mp4",
contentLength: 3963036, contentLength: 3963036,
contentType: "video/mp4", contentType: "video/mp4",
etag: "1324f58d4d5c8d81bed6e4ed9d5ea862", etag: "1324f58d4d5c8d81bed6e4ed9d5ea862",
lastModified: DateTime.utc(2021, 1, 1, 2, 3, 4), lastModified: DateTime.utc(2021, 1, 1, 2, 3, 4),
hasPreview: false, hasPreview: false,
fileId: 123, fileId: 123,
isCollection: false, isCollection: false,
), ),
]); ]);
});
});
} }