Browse Source

Merge pull request #262 from provokateurin/feature/webdav-serializable

Make WebDav serializable
pull/275/head
Kate 2 years ago committed by GitHub
parent
commit
a1b333e75f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 8
      packages/app/pubspec.lock
  2. 1
      packages/neon/neon/lib/neon.dart
  3. 60
      packages/neon/neon/lib/src/utils/request_manager.dart
  4. 1
      packages/neon/neon/pubspec.yaml
  5. 37
      packages/neon/neon_files/lib/blocs/browser.dart
  6. 8
      packages/neon/neon_files/lib/blocs/files.dart
  7. 1
      packages/neon/neon_files/lib/neon_files.dart
  8. 1
      packages/neon/neon_files/lib/utils/upload_task.dart
  9. 73
      packages/nextcloud/bin/generate_props.dart
  10. 4
      packages/nextcloud/lib/nextcloud.dart
  11. 224
      packages/nextcloud/lib/src/webdav/client.dart
  12. 243
      packages/nextcloud/lib/src/webdav/file.dart
  13. 26
      packages/nextcloud/lib/src/webdav/props.csv
  14. 423
      packages/nextcloud/lib/src/webdav/props.dart
  15. 1915
      packages/nextcloud/lib/src/webdav/props.g.dart
  16. 137
      packages/nextcloud/lib/src/webdav/webdav.dart
  17. 477
      packages/nextcloud/lib/src/webdav/webdav.g.dart
  18. 2
      packages/nextcloud/pubspec.yaml
  19. 295
      packages/nextcloud/test/webdav.dart
  20. 1
      tool/generate-nextcloud.sh

8
packages/app/pubspec.lock

@ -1281,6 +1281,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "6.2.2" version: "6.2.2"
xml_annotation:
dependency: transitive
description:
name: xml_annotation
sha256: "4c67438bd8cbe4c717b66f3991631ea3cdd372dc77255f9fd166a6b6e1cf6965"
url: "https://pub.dev"
source: hosted
version: "2.2.0"
yaml: yaml:
dependency: transitive dependency: transitive
description: description:

1
packages/neon/neon/lib/neon.dart

@ -41,6 +41,7 @@ import 'package:url_launcher/url_launcher_string.dart';
import 'package:webview_flutter/webview_flutter.dart'; import 'package:webview_flutter/webview_flutter.dart';
import 'package:window_manager/window_manager.dart'; import 'package:window_manager/window_manager.dart';
import 'package:xdg_directories/xdg_directories.dart' as xdg; import 'package:xdg_directories/xdg_directories.dart' as xdg;
import 'package:xml/xml.dart' as xml;
export 'l10n/localizations.dart'; export 'l10n/localizations.dart';
export 'src/models/account.dart'; export 'src/models/account.dart';

60
packages/neon/neon/lib/src/utils/request_manager.dart

@ -14,8 +14,50 @@ class RequestManager {
final Future<R> Function() call, final Future<R> Function() call,
final T Function(R) unwrap, { final T Function(R) unwrap, {
final bool disableTimeout = false, final bool disableTimeout = false,
final int retries = 0, }) async =>
}) async { _wrap<T, R>(
clientID,
k,
subject,
call,
unwrap,
(final data) => json.encode(serializeNextcloud<R>(data)),
(final data) => deserializeNextcloud<R>(json.decode(data)),
disableTimeout,
0,
);
Future wrapWebDav<T>(
final String clientID,
final String k,
final BehaviorSubject<Result<T>> subject,
final Future<WebDavMultistatus> Function() call,
final T Function(WebDavMultistatus) unwrap, {
final bool disableTimeout = false,
}) async =>
_wrap<T, WebDavMultistatus>(
clientID,
k,
subject,
call,
unwrap,
(final data) => data.toXmlElement(namespaces: namespaces).toXmlString(),
(final data) => WebDavMultistatus.fromXmlElement(xml.XmlDocument.parse(data).rootElement),
disableTimeout,
0,
);
Future _wrap<T, R>(
final String clientID,
final String k,
final BehaviorSubject<Result<T>> subject,
final Future<R> Function() call,
final T Function(R) unwrap,
final String Function(R) serialize,
final R Function(String) deserialize,
final bool disableTimeout,
final int retries,
) async {
if (subject.valueOrNull?.data != null) { if (subject.valueOrNull?.data != null) {
subject.add( subject.add(
Result( Result(
@ -35,7 +77,7 @@ class RequestManager {
try { try {
subject.add( subject.add(
Result( Result(
unwrap(deserializeNextcloud<R>(json.decode((await cache!.get(key))!))), unwrap(await compute(deserialize, (await cache!.get(key))!)),
null, null,
loading: true, loading: true,
cached: true, cached: true,
@ -49,21 +91,23 @@ class RequestManager {
try { try {
final response = await (disableTimeout ? call() : timeout(call)); final response = await (disableTimeout ? call() : timeout(call));
await cache?.set(key, json.encode(serializeNextcloud<R>(response))); await cache?.set(key, await compute(serialize, response));
subject.add(Result.success(unwrap(response))); subject.add(Result.success(unwrap(response)));
} catch (e, s) { } catch (e, s) {
debugPrint(e.toString()); debugPrint(e.toString());
debugPrint(s.toString()); debugPrint(s.toString());
if (e is NextcloudApiException && e.statusCode >= 500 && retries < 3) { if (e is NextcloudApiException && e.statusCode >= 500 && retries < 3) {
debugPrint('Retrying...'); debugPrint('Retrying...');
await wrapNextcloud( await _wrap(
clientID, clientID,
k, k,
subject, subject,
call, call,
unwrap, unwrap,
disableTimeout: disableTimeout, serialize,
retries: retries + 1, deserialize,
disableTimeout,
retries + 1,
); );
return; return;
} }
@ -71,7 +115,7 @@ class RequestManager {
try { try {
subject.add( subject.add(
Result( Result(
unwrap(deserializeNextcloud<R>(json.decode((await cache!.get(key))!))), unwrap(await compute(deserialize, (await cache!.get(key))!)),
null, null,
loading: false, loading: false,
cached: true, cached: true,

1
packages/neon/neon/pubspec.yaml

@ -46,6 +46,7 @@ dependencies:
webview_flutter: ^3.0.0 webview_flutter: ^3.0.0
window_manager: ^0.2.5 window_manager: ^0.2.5
xdg_directories: ^0.2.0+1 xdg_directories: ^0.2.0+1
xml: ^6.2.2
dev_dependencies: dev_dependencies:
build_runner: ^2.1.7 build_runner: ^2.1.7

37
packages/neon/neon_files/lib/blocs/browser.dart

@ -14,12 +14,14 @@ abstract class FilesBrowserBlocStates {
class FilesBrowserBloc extends InteractiveBloc implements FilesBrowserBlocEvents, FilesBrowserBlocStates { class FilesBrowserBloc extends InteractiveBloc implements FilesBrowserBlocEvents, FilesBrowserBlocStates {
FilesBrowserBloc( FilesBrowserBloc(
this._requestManager,
this.options, this.options,
this.client, this.client,
) { ) {
unawaited(refresh()); unawaited(refresh());
} }
final RequestManager _requestManager;
final FilesAppSpecificOptions options; final FilesAppSpecificOptions options;
final NextcloudClient client; final NextcloudClient client;
@ -38,26 +40,23 @@ class FilesBrowserBloc extends InteractiveBloc implements FilesBrowserBlocEvents
@override @override
Future refresh() async { Future refresh() async {
// TODO: We have to do this manually, because we can't cache WebDAV stuff right now await _requestManager.wrapWebDav<List<WebDavFile>>(
try { client.id,
files.add(Result.loading()); 'files-${path.value.join('/')}',
final data = await client.webdav.ls( files,
() async => client.webdav.ls(
path.value.join('/'), path.value.join('/'),
props: { prop: WebDavPropfindProp(
WebDavProps.davContentType.name, davgetcontenttype: true,
WebDavProps.davETag.name, davgetetag: true,
WebDavProps.davLastModified.name, davgetlastmodified: true,
WebDavProps.ncHasPreview.name, nchaspreview: true,
WebDavProps.ocSize.name, ocsize: true,
WebDavProps.ocFavorite.name, ocfavorite: true,
}, ),
); ),
files.add(Result.success(data)); (final response) => response.toWebDavFiles(client.webdav),
} catch (e, s) { );
debugPrint(e.toString());
debugPrint(s.toString());
files.add(Result.error(e));
}
} }
@override @override

8
packages/neon/neon_files/lib/blocs/files.dart

@ -30,6 +30,7 @@ class FilesBloc extends InteractiveBloc implements FilesBlocEvents, FilesBlocSta
FilesBloc( FilesBloc(
this.options, this.options,
this.client, this.client,
this._requestManager,
this._platform, this._platform,
) { ) {
options.uploadQueueParallelism.stream.listen((final value) { options.uploadQueueParallelism.stream.listen((final value) {
@ -42,6 +43,7 @@ class FilesBloc extends InteractiveBloc implements FilesBlocEvents, FilesBlocSta
final FilesAppSpecificOptions options; final FilesAppSpecificOptions options;
final NextcloudClient client; final NextcloudClient client;
final RequestManager _requestManager;
final NeonPlatform _platform; final NeonPlatform _platform;
late final browser = getNewFilesBrowserBloc(); late final browser = getNewFilesBrowserBloc();
@ -64,7 +66,7 @@ class FilesBloc extends InteractiveBloc implements FilesBlocEvents, FilesBlocSta
@override @override
void addFavorite(final List<String> path) { void addFavorite(final List<String> path) {
wrapAction(() async => client.webdav.updateProps(path.join('/'), {WebDavProps.ocFavorite.name: '1'})); wrapAction(() async => client.webdav.updateProps(path.join('/'), WebDavProp(ocfavorite: 1)));
} }
@override @override
@ -120,7 +122,7 @@ class FilesBloc extends InteractiveBloc implements FilesBlocEvents, FilesBlocSta
wrapAction( wrapAction(
() async => client.webdav.updateProps( () async => client.webdav.updateProps(
path.join('/'), path.join('/'),
{WebDavProps.ocFavorite.name: '0'}, WebDavProp(ocfavorite: 0),
), ),
); );
} }
@ -193,5 +195,5 @@ class FilesBloc extends InteractiveBloc implements FilesBlocEvents, FilesBlocSta
} }
} }
FilesBrowserBloc getNewFilesBrowserBloc() => FilesBrowserBloc(options, client); FilesBrowserBloc getNewFilesBrowserBloc() => FilesBrowserBloc(_requestManager, options, client);
} }

1
packages/neon/neon_files/lib/neon_files.dart

@ -51,6 +51,7 @@ class FilesApp extends AppImplementation<FilesBloc, FilesAppSpecificOptions> {
FilesBloc buildBloc(final NextcloudClient client) => FilesBloc( FilesBloc buildBloc(final NextcloudClient client) => FilesBloc(
options, options,
client, client,
requestManager,
platform, platform,
); );

1
packages/neon/neon_files/lib/utils/upload_task.dart

@ -24,6 +24,7 @@ class UploadTask {
return Uint8List.fromList(chunk); return Uint8List.fromList(chunk);
}), }),
path.join('/'), path.join('/'),
lastModified: lastModified,
); );
} }
} }

73
packages/nextcloud/bin/generate_props.dart

@ -0,0 +1,73 @@
import 'dart:io';
void main() {
final props = File('lib/src/webdav/props.csv').readAsLinesSync().map((final line) => line.split(','));
final valueProps = <String>[];
final findProps = <String>[];
final variables = <String>[];
for (final prop in props) {
final namespacePrefix = prop[0];
final namespaceVariable = convertNamespace(namespacePrefix);
final type = prop[2];
final name = prop[1];
final variable = namespacePrefix + name.toLowerCase().replaceAll(RegExp('[^a-z]'), '');
valueProps.addAll([
"@annotation.XmlElement(name: '$name', namespace: $namespaceVariable, includeIfNull: false)",
'$type? $variable;',
]);
findProps.addAll([
"@annotation.XmlElement(name: '$name', namespace: $namespaceVariable, includeIfNull: false)",
'bool? $variable;',
]);
variables.add(variable);
}
File('lib/src/webdav/props.dart').writeAsStringSync(
[
'// ignore_for_file: public_member_api_docs',
"import 'package:nextcloud/src/webdav/webdav.dart';",
"import 'package:xml/xml.dart';",
"import 'package:xml_annotation/xml_annotation.dart' as annotation;",
"part 'props.g.dart';",
'',
...generateClass('WebDavPropfindProp', 'prop', 'namespaceDav', findProps, variables),
...generateClass('WebDavProp', 'prop', 'namespaceDav', valueProps, variables),
...generateClass('WebDavOcFilterRules', 'filter-rules', 'namespaceOwncloud', valueProps, variables),
].join('\n'),
);
}
List<String> generateClass(
final String name,
final String elementName,
final String namespace,
final List<String> props,
final List<String> variables,
) =>
[
'@annotation.XmlSerializable(createMixin: true)',
"@annotation.XmlRootElement(name: '$elementName', namespace: $namespace)",
'class $name with _\$${name}XmlSerializableMixin {',
' $name({',
...variables.map((final variable) => ' this.$variable,'),
' });',
' factory $name.fromXmlElement(final XmlElement element) => _\$${name}FromXmlElement(element);',
...props.map((final prop) => ' $prop'),
'}',
];
String convertNamespace(final String namespacePrefix) {
switch (namespacePrefix) {
case 'dav':
return 'namespaceDav';
case 'oc':
return 'namespaceOwncloud';
case 'nc':
return 'namespaceNextcloud';
case 'ocs':
return 'namespaceOpenCollaborationServices';
case 'ocm':
return 'namespaceOpenCloudMesh';
default:
throw Exception('Unknown namespace prefix "$namespacePrefix"');
}
}

4
packages/nextcloud/lib/nextcloud.dart

@ -5,7 +5,6 @@ import 'dart:io';
import 'dart:typed_data'; import 'dart:typed_data';
import 'package:crypto/crypto.dart'; import 'package:crypto/crypto.dart';
import 'package:intl/intl.dart';
import 'package:nextcloud/nextcloud.dart'; import 'package:nextcloud/nextcloud.dart';
import 'package:nextcloud/src/nextcloud.openapi.dart' as openapi; import 'package:nextcloud/src/nextcloud.openapi.dart' as openapi;
import 'package:version/version.dart'; import 'package:version/version.dart';
@ -14,6 +13,8 @@ import 'package:xml/xml.dart' as xml;
export 'package:crypton/crypton.dart' show RSAKeypair, RSAPrivateKey, RSAPublicKey; export 'package:crypton/crypton.dart' show RSAKeypair, RSAPrivateKey, RSAPublicKey;
export 'src/nextcloud.openapi.dart' hide NextcloudClient; export 'src/nextcloud.openapi.dart' hide NextcloudClient;
export 'src/webdav/props.dart';
export 'src/webdav/webdav.dart';
part 'src/app_type.dart'; part 'src/app_type.dart';
part 'src/client.dart'; part 'src/client.dart';
@ -21,4 +22,3 @@ part 'src/helpers.dart';
part 'src/version_supported.dart'; part 'src/version_supported.dart';
part 'src/webdav/client.dart'; part 'src/webdav/client.dart';
part 'src/webdav/file.dart'; part 'src/webdav/file.dart';
part 'src/webdav/props.dart';

224
packages/nextcloud/lib/src/webdav/client.dart

@ -14,18 +14,6 @@ class WebDavClient {
/// Base path used on the server /// Base path used on the server
final String basePath; final String basePath;
/// XML namespaces supported by this client.
///
/// For Nextcloud namespaces see [WebDav/Requesting properties](https://docs.nextcloud.com/server/latest/developer_manual/client_apis/WebDAV/basic.html#requesting-properties).
final Map<String, String> namespaces = {
'DAV:': 'd',
'http://owncloud.org/ns': 'oc',
'http://nextcloud.org/ns': 'nc',
'http://open-collaboration-services.org/ns': 'ocs',
'http://open-cloud-mesh.org/ns': 'ocm',
'http://sabredav.org/ns': 's', // mostly used in error responses
};
Future<HttpClientResponse> _send( Future<HttpClientResponse> _send(
final String method, final String method,
final String url, final String url,
@ -65,12 +53,6 @@ class WebDavClient {
return response; return response;
} }
/// Registers a custom namespace for properties.
///
/// Requires a unique [namespaceUri] and [prefix].
void registerNamespace(final String namespaceUri, final String prefix) =>
namespaces.putIfAbsent(namespaceUri, () => prefix);
String _constructPath([final String? path]) => [ String _constructPath([final String? path]) => [
rootClient.baseURL, rootClient.baseURL,
basePath, basePath,
@ -90,25 +72,6 @@ class WebDavClient {
.where((final part) => part.isNotEmpty) .where((final part) => part.isNotEmpty)
.join('/'); .join('/');
String _buildPropsRequest(final Set<String> props) {
final builder = xml.XmlBuilder();
builder
..processing('xml', 'version="1.0"')
..element(
'd:propfind',
nest: () {
namespaces.forEach(builder.namespace);
builder.element(
'd:prop',
nest: () {
props.forEach(builder.element);
},
);
},
);
return builder.buildDocument().toString();
}
/// returns the WebDAV capabilities of the server /// returns the WebDAV capabilities of the server
Future<WebDavStatus> status() async { Future<WebDavStatus> status() async {
final response = await _send( final response = await _send(
@ -175,18 +138,37 @@ class WebDavClient {
[204], [204],
); );
Map<String, String>? _generateUploadHeaders({
required final DateTime? lastModified,
required final DateTime? created,
}) {
final headers = <String, String>{
if (lastModified != null) ...{
'X-OC-Mtime': (lastModified.millisecondsSinceEpoch ~/ 1000).toString(),
},
if (created != null) ...{
'X-OC-CTime': (created.millisecondsSinceEpoch ~/ 1000).toString(),
},
};
return headers.isNotEmpty ? headers : null;
}
/// upload a new file with [localData] as content to [remotePath] /// upload a new file with [localData] as content to [remotePath]
Future<HttpClientResponse> upload( Future<HttpClientResponse> upload(
final Uint8List localData, final Uint8List localData,
final String remotePath, { final String remotePath, {
final DateTime? lastModified, final DateTime? lastModified,
final DateTime? created,
}) => }) =>
_send( _send(
'PUT', 'PUT',
_constructPath(remotePath), _constructPath(remotePath),
[200, 201, 204], [200, 201, 204],
data: Stream.value(localData), data: Stream.value(localData),
headers: lastModified != null ? {'X-OC-Mtime': (lastModified.millisecondsSinceEpoch ~/ 1000).toString()} : null, headers: _generateUploadHeaders(
lastModified: lastModified,
created: created,
),
); );
/// upload a new file with [localData] as content to [remotePath] /// upload a new file with [localData] as content to [remotePath]
@ -194,13 +176,17 @@ class WebDavClient {
final Stream<Uint8List> localData, final Stream<Uint8List> localData,
final String remotePath, { final String remotePath, {
final DateTime? lastModified, final DateTime? lastModified,
final DateTime? created,
}) async => }) async =>
_send( _send(
'PUT', 'PUT',
_constructPath(remotePath), _constructPath(remotePath),
[200, 201, 204], [200, 201, 204],
data: localData, data: localData,
headers: lastModified != null ? {'X-OC-Mtime': (lastModified.millisecondsSinceEpoch ~/ 1000).toString()} : null, headers: _generateUploadHeaders(
lastModified: lastModified,
created: created,
),
); );
/// download [remotePath] and store the response file contents to String /// download [remotePath] and store the response file contents to String
@ -221,100 +207,69 @@ class WebDavClient {
[200], [200],
); );
Future<WebDavMultistatus> _parseResponse(final HttpClientResponse response) async =>
WebDavMultistatus.fromXmlElement(xml.XmlDocument.parse(await response.body).rootElement);
/// list the directories and files under given [remotePath]. /// list the directories and files under given [remotePath].
/// ///
/// Optionally populates the given [props] on the returned files. /// Optionally populates the given [prop]s on the returned files.
Future<List<WebDavFile>> ls( Future<WebDavMultistatus> ls(
final String remotePath, { final String remotePath, {
final Set<String>? props, final WebDavPropfindProp? prop,
final int? depth,
}) async { }) async {
final response = await _send( final response = await _send(
'PROPFIND', 'PROPFIND',
_constructPath(remotePath), _constructPath(remotePath),
[207, 301], [207, 301],
data: Stream.value(Uint8List.fromList(utf8.encode(_buildPropsRequest(props ?? {})))), data: Stream.value(
Uint8List.fromList(
utf8.encode(
WebDavPropfind(prop: prop ?? WebDavPropfindProp()).toXmlElement(namespaces: namespaces).toXmlString(),
),
),
),
headers: {
if (depth != null) ...{
'Depth': depth.toString(),
},
},
); );
if (response.statusCode == 301) { if (response.statusCode == 301) {
return ls(response.headers['location']!.first); return ls(
response.headers['location']!.first,
prop: prop,
depth: depth,
);
} }
return treeFromWebDavXml( return _parseResponse(response);
basePath,
namespaces,
await response.body,
)..removeAt(0);
} }
/// Runs the filter-files report with the given [propFilters] on the /// Runs the filter-files report with the given [filterRules] on the
/// [remotePath]. /// [remotePath].
/// ///
/// Optionally populates the given [props] on the returned files. /// Optionally populates the given [prop]s on the returned files.
Future<List<WebDavFile>> filter( Future<WebDavMultistatus> filter(
final String remotePath, final String remotePath,
final Map<String, String> propFilters, { final WebDavOcFilterRules filterRules, {
final Set<String> props = const {}, final WebDavPropfindProp? prop,
}) async { }) async {
final builder = xml.XmlBuilder();
builder
..processing('xml', 'version="1.0"')
..element(
'oc:filter-files',
nest: () {
namespaces.forEach(builder.namespace);
builder
..element(
'oc:filter-rules',
nest: () {
propFilters.forEach((final key, final value) {
builder.element(
key,
nest: () {
builder.text(value);
},
);
});
},
)
..element(
'd:prop',
nest: () {
props.forEach(builder.element);
},
);
},
);
final response = await _send( final response = await _send(
'REPORT', 'REPORT',
_constructPath(remotePath), _constructPath(remotePath),
[200, 207], [200, 207],
data: Stream.value(Uint8List.fromList(utf8.encode(builder.buildDocument().toString()))), data: Stream.value(
); Uint8List.fromList(
return treeFromWebDavXml( utf8.encode(
basePath, WebDavOcFilterFiles(
namespaces, filterRules: filterRules,
await response.body, prop: prop ?? WebDavPropfindProp(),
); ).toXmlElement(namespaces: namespaces).toXmlString(),
} ),
),
/// Retrieves properties for the given [remotePath]. ),
///
/// Populates all available properties by default, but a reduced set can be
/// specified via [props].
Future<WebDavFile> getProps(
final String remotePath, {
final Set<String>? props,
}) async {
final response = await _send(
'PROPFIND',
_constructPath(remotePath),
[200, 207],
data: Stream.value(Uint8List.fromList(utf8.encode(_buildPropsRequest(props ?? {})))),
headers: {'Depth': '0'},
);
return fileFromWebDavXml(
basePath,
namespaces,
await response.body,
); );
return _parseResponse(response);
} }
/// Update (string) properties of the given [remotePath]. /// Update (string) properties of the given [remotePath].
@ -322,42 +277,29 @@ class WebDavClient {
/// Returns true if the update was successful. /// Returns true if the update was successful.
Future<bool> updateProps( Future<bool> updateProps(
final String remotePath, final String remotePath,
final Map<String, String> props, final WebDavProp prop,
) async { ) async {
final builder = xml.XmlBuilder();
builder
..processing('xml', 'version="1.0"')
..element(
'd:propertyupdate',
nest: () {
namespaces.forEach(builder.namespace);
builder.element(
'd:set',
nest: () {
builder.element(
'd:prop',
nest: () {
props.forEach((final key, final value) {
builder.element(
key,
nest: () {
builder.text(value);
},
);
});
},
);
},
);
},
);
final response = await _send( final response = await _send(
'PROPPATCH', 'PROPPATCH',
_constructPath(remotePath), _constructPath(remotePath),
[200, 207], [200, 207],
data: Stream.value(Uint8List.fromList(utf8.encode(builder.buildDocument().toString()))), data: Stream.value(
Uint8List.fromList(
utf8.encode(
WebDavPropertyupdate(set: WebDavSet(prop: prop)).toXmlElement(namespaces: namespaces).toXmlString(),
),
),
),
); );
return checkUpdateFromWebDavXml(await response.body); final data = await _parseResponse(response);
for (final a in data.responses) {
for (final b in a.propstats) {
if (!b.status.contains('200')) {
return false;
}
}
}
return true;
} }
/// Move a file from [sourcePath] to [destinationPath] /// Move a file from [sourcePath] to [destinationPath]

243
packages/nextcloud/lib/src/webdav/file.dart

@ -1,142 +1,88 @@
part of '../../nextcloud.dart'; part of '../../nextcloud.dart';
// ignore: public_member_api_docs
extension WebDavMultistatusFile on WebDavMultistatus {
/// Convert the [WebDavMultistatus] into a [WebDavFile] for easier handling
List<WebDavFile> toWebDavFiles(final WebDavClient client) => responses
.sublist(1)
.where((final response) => response.href != null)
.map(
(final response) => WebDavFile(
basePath: client.basePath,
response: response,
),
)
.toList();
}
/// WebDavFile class /// WebDavFile class
class WebDavFile { class WebDavFile {
/// Creates a new WebDavFile object with the given path /// Creates a new WebDavFile object with the given path
WebDavFile({ WebDavFile({
required this.path, required final String basePath,
required final List<xml.XmlElement> props, required final WebDavResponse response,
required final Map<String, String> namespaces, }) : _basePath = basePath,
}) : _props = props, _response = response;
_namespaces = namespaces;
/// The path of file final String _basePath;
final String path; final WebDavResponse _response;
final List<xml.XmlElement> _props; /// Get the props of the file
final Map<String, String> _namespaces; late final WebDavProp props =
_response.propstats.singleWhere((final propstat) => propstat.status.contains('200')).prop;
/// Gets a prop by it's qualified name. It already does namespace matching /// The path of file
xml.XmlElement? getProp(final String qualifiedName) { late final String path =
final name = xml.XmlName.fromString(qualifiedName); Uri.decodeFull(_response.href!.substring(Uri.encodeFull(_basePath).length, _response.href!.length));
final namespaceUri = _namespaces.keys.singleWhere((final namespaceUri) => _namespaces[namespaceUri] == name.prefix);
final matches =
_props.where((final prop) => prop.name.local == name.local && prop.namespaceUri == namespaceUri).toList();
if (matches.isNotEmpty) {
return matches[0];
}
return null;
}
/// The fileid namespaced by the instance id, globally unique /// The fileid namespaced by the instance id, globally unique
String? get id => getProp(WebDavProps.ocId.name)?.text; late final String? id = props.ocid;
/// The unique id for the file within the instance /// The unique id for the file within the instance
String? get fileId => getProp(WebDavProps.ocFileId.name)?.text; late final String? fileId = props.ocfileid;
/// Whether this is a collection resource type
bool? get isCollection {
final prop = getProp(WebDavProps.davResourceType.name);
if (prop != null) {
return prop.getElement('d:collection') != null;
}
return null;
}
/// Mime-type of the file /// Mime-type of the file
String? get mimeType => getProp(WebDavProps.davContentType.name)?.text; late final String? mimeType = props.davgetcontenttype;
/// ETag of the file /// ETag of the file
String? get etag => getProp(WebDavProps.davETag.name)?.text; late final String? etag = props.davgetetag;
/// File content length or folder size /// File content length or folder size
int? get size { late final int? size = props.ocsize ?? props.davgetcontentlength;
for (final prop in [
getProp(WebDavProps.ocSize.name),
getProp(WebDavProps.davContentLength.name),
]) {
if (prop != null) {
return int.parse(prop.text);
}
}
return null;
}
/// The user id of the owner of a shared file /// The user id of the owner of a shared file
String? get ownerId => getProp(WebDavProps.ocOwnerId.name)?.text; late final String? ownerId = props.ocownerid;
/// The display name of the owner of a shared file /// The display name of the owner of a shared file
String? get ownerDisplay => getProp(WebDavProps.ocOwnerDisplayName.name)?.text; late final String? ownerDisplay = props.ocownerdisplayname;
/// Share note /// Share note
String? get note => getProp(WebDavProps.ncNote.name)?.text; late final String? note = props.ncnote;
/// Last modified date of the file /// Last modified date of the file
DateTime? get lastModified { late final DateTime? lastModified = () {
final prop = getProp(WebDavProps.davLastModified.name); if (props.davgetlastmodified != null) {
if (prop != null) { return webdavDateFormat.parseUtc(props.davgetlastmodified!);
return DateFormat('E, d MMM yyyy HH:mm:ss', 'en_US').parseUtc(prop.text);
} }
return null; return null;
} }();
/// Upload date of the file /// Upload date of the file
DateTime? get uploadedDate { late final DateTime? uploadedDate =
final prop = getProp(WebDavProps.ncUploadTime.name); props.ncuploadtime != null ? DateTime.fromMillisecondsSinceEpoch(props.ncuploadtime! * 1000) : null;
if (prop != null) {
return DateTime.fromMillisecondsSinceEpoch(int.parse(prop.text) * 1000);
}
return null;
}
/// Creation date of the file as provided by uploader /// Creation date of the file as provided by uploader
DateTime? get createdDate { late final DateTime? createdDate =
final prop = getProp(WebDavProps.ncCreationTime.name); props.nccreationtime != null ? DateTime.fromMillisecondsSinceEpoch(props.nccreationtime! * 1000) : null;
if (prop != null) {
return DateTime.fromMillisecondsSinceEpoch(int.parse(prop.text) * 1000);
}
return null;
}
/// List of types of shares of the file
List<int>? get shareTypes {
final prop = getProp(WebDavProps.ocShareTypes.name);
if (prop != null) {
return prop.findElements('oc:share-type').map((final element) => int.parse(element.text)).toList();
}
return null;
}
/// User IDs of sharees
List<String>? get sharees {
final prop = getProp(WebDavProps.ncShareees.name);
if (prop != null) {
return prop.findAllElements('nc:id').map((final e) => e.text).toList();
}
return null;
}
/// Whether this file is marked as favorite /// Whether this file is marked as favorite
bool? get favorite { late final bool? favorite = props.ocfavorite == null ? null : props.ocfavorite == 1;
final prop = getProp(WebDavProps.ocFavorite.name);
if (prop != null) {
return prop.text == '1';
}
return null;
}
/// Whether this file has a preview image /// Whether this file has a preview image
bool? get hasPreview { late final bool? hasPreview = props.nchaspreview;
final prop = getProp(WebDavProps.ncHasPreview.name);
if (prop != null) {
return prop.text == 'true';
}
return null;
}
/// Returns the decoded name of the file / folder without the whole path /// Returns the decoded name of the file / folder without the whole path
String get name { late final String name = () {
// normalised path (remove trailing slash) // normalised path (remove trailing slash)
final end = path.endsWith('/') ? path.length - 1 : path.length; final end = path.endsWith('/') ? path.length - 1 : path.length;
final segments = Uri.parse(path, 0, end).pathSegments; final segments = Uri.parse(path, 0, end).pathSegments;
@ -144,101 +90,8 @@ class WebDavFile {
return segments.last; return segments.last;
} }
return ''; return '';
} }();
/// Returns if the file is a directory /// Returns if the file is a directory
bool get isDirectory => path.endsWith('/') || (isCollection ?? false); late final bool isDirectory = path.endsWith('/');
// coverage:ignore-start
@override
String toString() =>
// ignore: lines_longer_than_80_chars
'WebDavFile{name: $name, id: $id, isDirectory: $isDirectory, path: $path, mimeType: $mimeType, size: $size, modificationTime: $lastModified, shareTypes: $shareTypes}';
// coverage:ignore-end
}
/// Converts a single d:response to a [WebDavFile]
WebDavFile _fromWebDavXml(
final String basePath,
final Map<String, String> namespaces,
final xml.XmlElement response,
) {
final davItemName = response.findElements('d:href').single.text;
final filePath = Uri.decodeFull(davItemName.substring(Uri.encodeFull(basePath).length, davItemName.length));
final allProps = <xml.XmlElement>[];
final propStatElements = response.findElements('d:propstat');
for (final propStat in propStatElements) {
final status = propStat.getElement('d:status')!.text;
final props = propStat.getElement('d:prop');
if (!status.contains('200')) {
// Skip any props that are not returned correctly (e.g. not found)
continue;
}
for (final prop in props!.nodes.whereType<xml.XmlElement>()) {
if (prop.children.isNotEmpty && prop.text.isNotEmpty) {
allProps.add(prop);
}
}
}
return WebDavFile(
path: filePath,
props: allProps,
namespaces: namespaces,
);
}
/// Extract a file from the webdav xml
WebDavFile fileFromWebDavXml(
final String basePath,
final Map<String, String> namespaces,
final String xmlStr,
) {
final xmlDocument = xml.XmlDocument.parse(xmlStr);
return _fromWebDavXml(
basePath,
namespaces,
xmlDocument.findAllElements('d:response').single,
);
}
/// Extract the file tree from the webdav xml
List<WebDavFile> treeFromWebDavXml(
final String basePath,
final Map<String, String> namespaces,
final String xmlStr,
) {
// Initialize a list to store the FileInfo Objects
final tree = [];
// parse the xml using the xml.XmlDocument.parse method
final xmlDocument = xml.XmlDocument.parse(xmlStr);
// Iterate over the response to find all folders / files and parse the information
for (final response in xmlDocument.findAllElements('d:response')) {
tree.add(
_fromWebDavXml(
basePath,
namespaces,
response,
),
);
}
return tree.cast<WebDavFile>();
}
/// Returns false if some updates have failed.
bool checkUpdateFromWebDavXml(final String xmlStr) {
final xmlDocument = xml.XmlDocument.parse(xmlStr);
final response = xmlDocument.findAllElements('d:response').single;
final propStatElements = response.findElements('d:propstat');
for (final propStat in propStatElements) {
final status = propStat.getElement('d:status')!.text;
if (!status.contains('200')) {
return false;
}
}
return true;
} }

26
packages/nextcloud/lib/src/webdav/props.csv

@ -0,0 +1,26 @@
dav,getlastmodified,String
dav,getetag,String
dav,getcontenttype,String
dav,getcontentlength,int
oc,id,String
oc,fileid,String
oc,favorite,int
oc,comments-href,String
oc,comments-count,int
oc,comments-unread,int
oc,downloadURL,String
oc,owner-id,String
oc,owner-display-name,String
oc,size,int
oc,permissions,String
nc,note,String
nc,data-fingerprint,String
nc,has-preview,bool
nc,mount-type,String
nc,is-encrypted,int
nc,metadata_etag,String
nc,upload_time,int
nc,creation_time,int
nc,rich-workspace,String
ocs,share-permissions,int
ocm,share-permissions,String
1 dav getlastmodified String
2 dav getetag String
3 dav getcontenttype String
4 dav getcontentlength int
5 oc id String
6 oc fileid String
7 oc favorite int
8 oc comments-href String
9 oc comments-count int
10 oc comments-unread int
11 oc downloadURL String
12 oc owner-id String
13 oc owner-display-name String
14 oc size int
15 oc permissions String
16 nc note String
17 nc data-fingerprint String
18 nc has-preview bool
19 nc mount-type String
20 nc is-encrypted int
21 nc metadata_etag String
22 nc upload_time int
23 nc creation_time int
24 nc rich-workspace String
25 ocs share-permissions int
26 ocm share-permissions String

423
packages/nextcloud/lib/src/webdav/props.dart

@ -1,166 +1,263 @@
part of '../../nextcloud.dart'; // ignore_for_file: public_member_api_docs
import 'package:nextcloud/src/webdav/webdav.dart';
/// Mapping of all WebDAV properties. import 'package:xml/xml.dart';
enum WebDavProps { import 'package:xml_annotation/xml_annotation.dart' as annotation;
/// Contains the Last-Modified header value . part 'props.g.dart';
davLastModified('d:getlastmodified'),
@annotation.XmlSerializable(createMixin: true)
/// Contains the ETag header value. @annotation.XmlRootElement(name: 'prop', namespace: namespaceDav)
davETag('d:getetag'), class WebDavPropfindProp with _$WebDavPropfindPropXmlSerializableMixin {
WebDavPropfindProp({
/// Contains the Content-Type header value. this.davgetlastmodified,
davContentType('d:getcontenttype'), this.davgetetag,
this.davgetcontenttype,
/// Specifies the nature of the resource. this.davgetcontentlength,
davResourceType('d:resourcetype'), this.ocid,
this.ocfileid,
/// Contains the Content-Length header. this.ocfavorite,
davContentLength('d:getcontentlength'), this.occommentshref,
this.occommentscount,
/// The fileid namespaced by the instance id, globally unique this.occommentsunread,
ocId('oc:id'), this.ocdownloadurl,
this.ocownerid,
/// The unique id for the file within the instance this.ocownerdisplayname,
ocFileId('oc:fileid'), this.ocsize,
this.ocpermissions,
/// List of user specified tags. Can be modified. this.ncnote,
ocTags('oc:tags'), this.ncdatafingerprint,
this.nchaspreview,
/// Whether a resource is tagged as favorite. this.ncmounttype,
/// Can be modified and reported on with list-files. this.ncisencrypted,
ocFavorite('oc:favorite'), this.ncmetadataetag,
this.ncuploadtime,
/// List of collaborative tags. Can be reported on with list-files. this.nccreationtime,
/// this.ncrichworkspace,
/// Valid system tags are: this.ocssharepermissions,
/// - oc:id this.ocmsharepermissions,
/// - oc:display-name });
/// - oc:user-visible factory WebDavPropfindProp.fromXmlElement(final XmlElement element) => _$WebDavPropfindPropFromXmlElement(element);
/// - oc:user-assignable @annotation.XmlElement(name: 'getlastmodified', namespace: namespaceDav, includeIfNull: false)
/// - oc:groups bool? davgetlastmodified;
/// - oc:can-assign @annotation.XmlElement(name: 'getetag', namespace: namespaceDav, includeIfNull: false)
ocSystemTag('oc:systemtag'), bool? davgetetag;
@annotation.XmlElement(name: 'getcontenttype', namespace: namespaceDav, includeIfNull: false)
/// Can be reported on with list-files. bool? davgetcontenttype;
ocCircle('oc:circle'), @annotation.XmlElement(name: 'getcontentlength', namespace: namespaceDav, includeIfNull: false)
bool? davgetcontentlength;
/// Link to the comments for this resource. @annotation.XmlElement(name: 'id', namespace: namespaceOwncloud, includeIfNull: false)
ocCommentsHref('oc:comments-href'), bool? ocid;
@annotation.XmlElement(name: 'fileid', namespace: namespaceOwncloud, includeIfNull: false)
/// Number of comments. bool? ocfileid;
ocCommentsCount('oc:comments-count'), @annotation.XmlElement(name: 'favorite', namespace: namespaceOwncloud, includeIfNull: false)
bool? ocfavorite;
/// Number of unread comments. @annotation.XmlElement(name: 'comments-href', namespace: namespaceOwncloud, includeIfNull: false)
ocCommentsUnread('oc:comments-unread'), bool? occommentshref;
@annotation.XmlElement(name: 'comments-count', namespace: namespaceOwncloud, includeIfNull: false)
/// Download URL. bool? occommentscount;
ocDownloadURL('oc:downloadURL'), @annotation.XmlElement(name: 'comments-unread', namespace: namespaceOwncloud, includeIfNull: false)
bool? occommentsunread;
/// The user id of the owner of a shared file @annotation.XmlElement(name: 'downloadURL', namespace: namespaceOwncloud, includeIfNull: false)
ocOwnerId('oc:owner-id'), bool? ocdownloadurl;
@annotation.XmlElement(name: 'owner-id', namespace: namespaceOwncloud, includeIfNull: false)
/// The display name of the owner of a shared file bool? ocownerid;
ocOwnerDisplayName('oc:owner-display-name'), @annotation.XmlElement(name: 'owner-display-name', namespace: namespaceOwncloud, includeIfNull: false)
bool? ocownerdisplayname;
/// Share types of this file. @annotation.XmlElement(name: 'size', namespace: namespaceOwncloud, includeIfNull: false)
/// bool? ocsize;
/// Returns a list of share-type objects where: @annotation.XmlElement(name: 'permissions', namespace: namespaceOwncloud, includeIfNull: false)
/// - 0: user share bool? ocpermissions;
/// - 1: group share @annotation.XmlElement(name: 'note', namespace: namespaceNextcloud, includeIfNull: false)
/// - 2: usergroup share bool? ncnote;
/// - 3: public link @annotation.XmlElement(name: 'data-fingerprint', namespace: namespaceNextcloud, includeIfNull: false)
/// - 4: email bool? ncdatafingerprint;
/// - 5: contact @annotation.XmlElement(name: 'has-preview', namespace: namespaceNextcloud, includeIfNull: false)
/// - 6: remote (federated cloud) bool? nchaspreview;
/// - 7: circle @annotation.XmlElement(name: 'mount-type', namespace: namespaceNextcloud, includeIfNull: false)
/// - 8: guest bool? ncmounttype;
/// - 9: remote group @annotation.XmlElement(name: 'is-encrypted', namespace: namespaceNextcloud, includeIfNull: false)
/// - 10: room (talk conversation) bool? ncisencrypted;
/// - 11: userroom @annotation.XmlElement(name: 'metadata_etag', namespace: namespaceNextcloud, includeIfNull: false)
/// See also [OCS Share API](https://docs.nextcloud.com/server/19/developer_manual/client_apis/OCS/ocs-share-api.html) bool? ncmetadataetag;
ocShareTypes('oc:share-types'), @annotation.XmlElement(name: 'upload_time', namespace: namespaceNextcloud, includeIfNull: false)
bool? ncuploadtime;
/// List of users this file is shared with. @annotation.XmlElement(name: 'creation_time', namespace: namespaceNextcloud, includeIfNull: false)
/// bool? nccreationtime;
/// Returns a list of sharee objects with: @annotation.XmlElement(name: 'rich-workspace', namespace: namespaceNextcloud, includeIfNull: false)
/// - id bool? ncrichworkspace;
/// - display-name @annotation.XmlElement(name: 'share-permissions', namespace: namespaceOpenCollaborationServices, includeIfNull: false)
/// - type (share type) bool? ocssharepermissions;
ncShareees('nc:sharees'), @annotation.XmlElement(name: 'share-permissions', namespace: namespaceOpenCloudMesh, includeIfNull: false)
bool? ocmsharepermissions;
/// Share note. }
ncNote('nc:note'),
/// Checksums as provided during upload.
///
/// Returns a list of checksum objects.
ocChecksums('oc:checksums'),
/// Unlike [[davContentLength]], this property also works for folders
/// reporting the size of everything in the folder.
ocSize('oc:size'),
/// WebDAV permissions:
///
/// - S: shared
/// - R: shareable
/// - M: mounted
/// - G: readable
/// - D: deletable
/// - NV: updateable, renameable, moveble
/// - W: updateable (file)
/// - CK: creatable
ocPermissions('oc:permissions'),
/// Nextcloud CRUDS permissions:
///
/// - 1: read
/// - 2: update
/// - 4: create
/// - 8: delete
/// - 16: share
/// - 31: all
ocsSharePermissions('ocs:share-permissions'),
/// OCM permissions:
///
/// - share
/// - read
/// - write
ocmSharePermissions('ocm:share-permissions'),
/// system data-fingerprint
ncDataFingerprint('nc:data-fingerprint'),
/// Whether a preview is available.
ncHasPreview('nc:has-preview'),
/// Mount type, e.g. global, group, user, personal, shared, shared-root, external
ncMountType('nc:mount-type'),
/// Is this file is encrypted, 0 for false or 1 for true.
ncIsEncrypted('nc:is-encrypted'),
// ignore: public_member_api_docs
ncMetadataETag('nc:metadata_etag'),
/// Date this file was uploaded.
ncUploadTime('nc:upload_time'),
/// Creation time of the file as provided during upload.
ncCreationTime('nc:creation_time'),
// ignore: public_member_api_docs
ncRichWorkspace('nc:rich-workspace');
// ignore: public_member_api_docs
const WebDavProps(this.name);
/// Name of the prop @annotation.XmlSerializable(createMixin: true)
final String name; @annotation.XmlRootElement(name: 'prop', namespace: namespaceDav)
class WebDavProp with _$WebDavPropXmlSerializableMixin {
WebDavProp({
this.davgetlastmodified,
this.davgetetag,
this.davgetcontenttype,
this.davgetcontentlength,
this.ocid,
this.ocfileid,
this.ocfavorite,
this.occommentshref,
this.occommentscount,
this.occommentsunread,
this.ocdownloadurl,
this.ocownerid,
this.ocownerdisplayname,
this.ocsize,
this.ocpermissions,
this.ncnote,
this.ncdatafingerprint,
this.nchaspreview,
this.ncmounttype,
this.ncisencrypted,
this.ncmetadataetag,
this.ncuploadtime,
this.nccreationtime,
this.ncrichworkspace,
this.ocssharepermissions,
this.ocmsharepermissions,
});
factory WebDavProp.fromXmlElement(final XmlElement element) => _$WebDavPropFromXmlElement(element);
@annotation.XmlElement(name: 'getlastmodified', namespace: namespaceDav, includeIfNull: false)
String? davgetlastmodified;
@annotation.XmlElement(name: 'getetag', namespace: namespaceDav, includeIfNull: false)
String? davgetetag;
@annotation.XmlElement(name: 'getcontenttype', namespace: namespaceDav, includeIfNull: false)
String? davgetcontenttype;
@annotation.XmlElement(name: 'getcontentlength', namespace: namespaceDav, includeIfNull: false)
int? davgetcontentlength;
@annotation.XmlElement(name: 'id', namespace: namespaceOwncloud, includeIfNull: false)
String? ocid;
@annotation.XmlElement(name: 'fileid', namespace: namespaceOwncloud, includeIfNull: false)
String? ocfileid;
@annotation.XmlElement(name: 'favorite', namespace: namespaceOwncloud, includeIfNull: false)
int? ocfavorite;
@annotation.XmlElement(name: 'comments-href', namespace: namespaceOwncloud, includeIfNull: false)
String? occommentshref;
@annotation.XmlElement(name: 'comments-count', namespace: namespaceOwncloud, includeIfNull: false)
int? occommentscount;
@annotation.XmlElement(name: 'comments-unread', namespace: namespaceOwncloud, includeIfNull: false)
int? occommentsunread;
@annotation.XmlElement(name: 'downloadURL', namespace: namespaceOwncloud, includeIfNull: false)
String? ocdownloadurl;
@annotation.XmlElement(name: 'owner-id', namespace: namespaceOwncloud, includeIfNull: false)
String? ocownerid;
@annotation.XmlElement(name: 'owner-display-name', namespace: namespaceOwncloud, includeIfNull: false)
String? ocownerdisplayname;
@annotation.XmlElement(name: 'size', namespace: namespaceOwncloud, includeIfNull: false)
int? ocsize;
@annotation.XmlElement(name: 'permissions', namespace: namespaceOwncloud, includeIfNull: false)
String? ocpermissions;
@annotation.XmlElement(name: 'note', namespace: namespaceNextcloud, includeIfNull: false)
String? ncnote;
@annotation.XmlElement(name: 'data-fingerprint', namespace: namespaceNextcloud, includeIfNull: false)
String? ncdatafingerprint;
@annotation.XmlElement(name: 'has-preview', namespace: namespaceNextcloud, includeIfNull: false)
bool? nchaspreview;
@annotation.XmlElement(name: 'mount-type', namespace: namespaceNextcloud, includeIfNull: false)
String? ncmounttype;
@annotation.XmlElement(name: 'is-encrypted', namespace: namespaceNextcloud, includeIfNull: false)
int? ncisencrypted;
@annotation.XmlElement(name: 'metadata_etag', namespace: namespaceNextcloud, includeIfNull: false)
String? ncmetadataetag;
@annotation.XmlElement(name: 'upload_time', namespace: namespaceNextcloud, includeIfNull: false)
int? ncuploadtime;
@annotation.XmlElement(name: 'creation_time', namespace: namespaceNextcloud, includeIfNull: false)
int? nccreationtime;
@annotation.XmlElement(name: 'rich-workspace', namespace: namespaceNextcloud, includeIfNull: false)
String? ncrichworkspace;
@annotation.XmlElement(name: 'share-permissions', namespace: namespaceOpenCollaborationServices, includeIfNull: false)
int? ocssharepermissions;
@annotation.XmlElement(name: 'share-permissions', namespace: namespaceOpenCloudMesh, includeIfNull: false)
String? ocmsharepermissions;
}
// coverage:ignore-start @annotation.XmlSerializable(createMixin: true)
@override @annotation.XmlRootElement(name: 'filter-rules', namespace: namespaceOwncloud)
String toString() => name; class WebDavOcFilterRules with _$WebDavOcFilterRulesXmlSerializableMixin {
// coverage:ignore-end WebDavOcFilterRules({
this.davgetlastmodified,
this.davgetetag,
this.davgetcontenttype,
this.davgetcontentlength,
this.ocid,
this.ocfileid,
this.ocfavorite,
this.occommentshref,
this.occommentscount,
this.occommentsunread,
this.ocdownloadurl,
this.ocownerid,
this.ocownerdisplayname,
this.ocsize,
this.ocpermissions,
this.ncnote,
this.ncdatafingerprint,
this.nchaspreview,
this.ncmounttype,
this.ncisencrypted,
this.ncmetadataetag,
this.ncuploadtime,
this.nccreationtime,
this.ncrichworkspace,
this.ocssharepermissions,
this.ocmsharepermissions,
});
factory WebDavOcFilterRules.fromXmlElement(final XmlElement element) => _$WebDavOcFilterRulesFromXmlElement(element);
@annotation.XmlElement(name: 'getlastmodified', namespace: namespaceDav, includeIfNull: false)
String? davgetlastmodified;
@annotation.XmlElement(name: 'getetag', namespace: namespaceDav, includeIfNull: false)
String? davgetetag;
@annotation.XmlElement(name: 'getcontenttype', namespace: namespaceDav, includeIfNull: false)
String? davgetcontenttype;
@annotation.XmlElement(name: 'getcontentlength', namespace: namespaceDav, includeIfNull: false)
int? davgetcontentlength;
@annotation.XmlElement(name: 'id', namespace: namespaceOwncloud, includeIfNull: false)
String? ocid;
@annotation.XmlElement(name: 'fileid', namespace: namespaceOwncloud, includeIfNull: false)
String? ocfileid;
@annotation.XmlElement(name: 'favorite', namespace: namespaceOwncloud, includeIfNull: false)
int? ocfavorite;
@annotation.XmlElement(name: 'comments-href', namespace: namespaceOwncloud, includeIfNull: false)
String? occommentshref;
@annotation.XmlElement(name: 'comments-count', namespace: namespaceOwncloud, includeIfNull: false)
int? occommentscount;
@annotation.XmlElement(name: 'comments-unread', namespace: namespaceOwncloud, includeIfNull: false)
int? occommentsunread;
@annotation.XmlElement(name: 'downloadURL', namespace: namespaceOwncloud, includeIfNull: false)
String? ocdownloadurl;
@annotation.XmlElement(name: 'owner-id', namespace: namespaceOwncloud, includeIfNull: false)
String? ocownerid;
@annotation.XmlElement(name: 'owner-display-name', namespace: namespaceOwncloud, includeIfNull: false)
String? ocownerdisplayname;
@annotation.XmlElement(name: 'size', namespace: namespaceOwncloud, includeIfNull: false)
int? ocsize;
@annotation.XmlElement(name: 'permissions', namespace: namespaceOwncloud, includeIfNull: false)
String? ocpermissions;
@annotation.XmlElement(name: 'note', namespace: namespaceNextcloud, includeIfNull: false)
String? ncnote;
@annotation.XmlElement(name: 'data-fingerprint', namespace: namespaceNextcloud, includeIfNull: false)
String? ncdatafingerprint;
@annotation.XmlElement(name: 'has-preview', namespace: namespaceNextcloud, includeIfNull: false)
bool? nchaspreview;
@annotation.XmlElement(name: 'mount-type', namespace: namespaceNextcloud, includeIfNull: false)
String? ncmounttype;
@annotation.XmlElement(name: 'is-encrypted', namespace: namespaceNextcloud, includeIfNull: false)
int? ncisencrypted;
@annotation.XmlElement(name: 'metadata_etag', namespace: namespaceNextcloud, includeIfNull: false)
String? ncmetadataetag;
@annotation.XmlElement(name: 'upload_time', namespace: namespaceNextcloud, includeIfNull: false)
int? ncuploadtime;
@annotation.XmlElement(name: 'creation_time', namespace: namespaceNextcloud, includeIfNull: false)
int? nccreationtime;
@annotation.XmlElement(name: 'rich-workspace', namespace: namespaceNextcloud, includeIfNull: false)
String? ncrichworkspace;
@annotation.XmlElement(name: 'share-permissions', namespace: namespaceOpenCollaborationServices, includeIfNull: false)
int? ocssharepermissions;
@annotation.XmlElement(name: 'share-permissions', namespace: namespaceOpenCloudMesh, includeIfNull: false)
String? ocmsharepermissions;
} }

1915
packages/nextcloud/lib/src/webdav/props.g.dart

File diff suppressed because it is too large Load Diff

137
packages/nextcloud/lib/src/webdav/webdav.dart

@ -0,0 +1,137 @@
// ignore_for_file: public_member_api_docs
import 'package:intl/intl.dart';
import 'package:nextcloud/src/webdav/props.dart';
import 'package:xml/xml.dart';
import 'package:xml_annotation/xml_annotation.dart' as annotation;
part 'webdav.g.dart';
/// Format used in WebDAV
final webdavDateFormat = DateFormat('E, d MMM yyyy HH:mm:ss', 'en_US');
const namespaceDav = 'DAV:';
const namespaceOwncloud = 'http://owncloud.org/ns';
const namespaceNextcloud = 'http://nextcloud.org/ns';
const namespaceOpenCollaborationServices = 'http://open-collaboration-services.org/ns';
const namespaceOpenCloudMesh = 'http://open-cloud-mesh.org/ns';
final Map<String, String> namespaces = {
namespaceDav: 'd',
namespaceOwncloud: 'oc',
namespaceNextcloud: 'nc',
namespaceOpenCollaborationServices: 'ocs',
namespaceOpenCloudMesh: 'ocm',
};
@annotation.XmlSerializable(createMixin: true)
@annotation.XmlRootElement(name: 'multistatus', namespace: namespaceDav)
class WebDavMultistatus with _$WebDavMultistatusXmlSerializableMixin {
WebDavMultistatus({
required this.responses,
});
factory WebDavMultistatus.fromXmlElement(final XmlElement element) => _$WebDavMultistatusFromXmlElement(element);
@annotation.XmlElement(name: 'response', namespace: namespaceDav)
final List<WebDavResponse> responses;
}
@annotation.XmlSerializable(createMixin: true)
@annotation.XmlRootElement(name: 'response', namespace: namespaceDav)
class WebDavResponse with _$WebDavResponseXmlSerializableMixin {
WebDavResponse({
required this.href,
required this.propstats,
});
factory WebDavResponse.fromXmlElement(final XmlElement element) => _$WebDavResponseFromXmlElement(element);
@annotation.XmlElement(name: 'href', namespace: namespaceDav)
final String? href;
@annotation.XmlElement(name: 'propstat', namespace: namespaceDav)
final List<WebDavPropstat> propstats;
}
@annotation.XmlSerializable(createMixin: true)
@annotation.XmlRootElement(name: 'propstat', namespace: namespaceDav)
class WebDavPropstat with _$WebDavPropstatXmlSerializableMixin {
WebDavPropstat({
required this.status,
required this.prop,
});
factory WebDavPropstat.fromXmlElement(final XmlElement element) => _$WebDavPropstatFromXmlElement(element);
@annotation.XmlElement(name: 'status', namespace: namespaceDav)
final String status;
@annotation.XmlElement(name: 'prop', namespace: namespaceDav)
final WebDavProp prop;
}
@annotation.XmlSerializable(createMixin: true)
@annotation.XmlRootElement(name: 'propertyupdate', namespace: namespaceDav)
class WebDavPropertyupdate with _$WebDavPropertyupdateXmlSerializableMixin {
WebDavPropertyupdate({
required this.set,
});
factory WebDavPropertyupdate.fromXmlElement(final XmlElement element) =>
_$WebDavPropertyupdateFromXmlElement(element);
@annotation.XmlElement(name: 'set', namespace: namespaceDav)
final WebDavSet set;
}
@annotation.XmlSerializable(createMixin: true)
@annotation.XmlRootElement(name: 'propertyupdate', namespace: namespaceDav)
class WebDavSet with _$WebDavSetXmlSerializableMixin {
WebDavSet({
required this.prop,
});
factory WebDavSet.fromXmlElement(final XmlElement element) => _$WebDavSetFromXmlElement(element);
@annotation.XmlElement(name: 'prop', namespace: namespaceDav)
final WebDavProp prop;
}
@annotation.XmlSerializable(createMixin: true)
@annotation.XmlRootElement(name: 'propfind', namespace: namespaceDav)
class WebDavPropfind with _$WebDavPropfindXmlSerializableMixin {
WebDavPropfind({
required this.prop,
});
factory WebDavPropfind.fromXmlElement(final XmlElement element) => _$WebDavPropfindFromXmlElement(element);
@annotation.XmlElement(name: 'prop', namespace: namespaceDav)
final WebDavPropfindProp prop;
}
@annotation.XmlSerializable(createMixin: true)
@annotation.XmlRootElement(name: 'filter-files', namespace: namespaceOwncloud)
class WebDavOcFilterFiles with _$WebDavOcFilterFilesXmlSerializableMixin {
WebDavOcFilterFiles({
required this.filterRules,
required this.prop,
});
factory WebDavOcFilterFiles.fromXmlElement(final XmlElement element) => _$WebDavOcFilterFilesFromXmlElement(element);
@annotation.XmlElement(name: 'filter-rules', namespace: namespaceOwncloud)
final WebDavOcFilterRules filterRules;
@annotation.XmlElement(name: 'prop', namespace: namespaceDav)
final WebDavPropfindProp prop;
}
// TODO: d:resourcetype
// TODO: oc:checksum
// TODO: oc:tags
// TODO: oc:systemtag
// TODO: oc:circle
// TODO: oc:share-types
// TODO: nc:sharees

477
packages/nextcloud/lib/src/webdav/webdav.g.dart

@ -0,0 +1,477 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'webdav.dart';
// **************************************************************************
// XmlSerializableGenerator
// **************************************************************************
void _$WebDavMultistatusBuildXmlChildren(WebDavMultistatus instance, XmlBuilder builder,
{Map<String, String> namespaces = const {}}) {
final responses = instance.responses;
final responsesSerialized = responses;
for (final value in responsesSerialized) {
builder.element('response', namespace: 'DAV:', nest: () {
value.buildXmlChildren(builder, namespaces: namespaces);
});
}
}
void _$WebDavMultistatusBuildXmlElement(WebDavMultistatus instance, XmlBuilder builder,
{Map<String, String> namespaces = const {}}) {
builder.element('multistatus', namespace: 'DAV:', namespaces: namespaces, nest: () {
instance.buildXmlChildren(builder, namespaces: namespaces);
});
}
WebDavMultistatus _$WebDavMultistatusFromXmlElement(XmlElement element) {
final responses = element.getElements('response', namespace: 'DAV:')!;
return WebDavMultistatus(responses: responses.map((e) => WebDavResponse.fromXmlElement(e)).toList());
}
List<XmlAttribute> _$WebDavMultistatusToXmlAttributes(WebDavMultistatus instance,
{Map<String, String?> namespaces = const {}}) {
final attributes = <XmlAttribute>[];
return attributes;
}
List<XmlNode> _$WebDavMultistatusToXmlChildren(WebDavMultistatus instance,
{Map<String, String?> namespaces = const {}}) {
final children = <XmlNode>[];
final responses = instance.responses;
final responsesSerialized = responses;
final responsesConstructed = responsesSerialized.map((e) => XmlElement(XmlName('response', namespaces['DAV:']),
e.toXmlAttributes(namespaces: namespaces), e.toXmlChildren(namespaces: namespaces)));
children.addAll(responsesConstructed);
return children;
}
XmlElement _$WebDavMultistatusToXmlElement(WebDavMultistatus instance, {Map<String, String?> namespaces = const {}}) {
return XmlElement(
XmlName('multistatus', namespaces['DAV:']),
[...namespaces.toXmlAttributes(), ...instance.toXmlAttributes(namespaces: namespaces)],
instance.toXmlChildren(namespaces: namespaces));
}
mixin _$WebDavMultistatusXmlSerializableMixin {
void buildXmlChildren(XmlBuilder builder, {Map<String, String> namespaces = const {}}) =>
_$WebDavMultistatusBuildXmlChildren(this as WebDavMultistatus, builder, namespaces: namespaces);
void buildXmlElement(XmlBuilder builder, {Map<String, String> namespaces = const {}}) =>
_$WebDavMultistatusBuildXmlElement(this as WebDavMultistatus, builder, namespaces: namespaces);
List<XmlAttribute> toXmlAttributes({Map<String, String?> namespaces = const {}}) =>
_$WebDavMultistatusToXmlAttributes(this as WebDavMultistatus, namespaces: namespaces);
List<XmlNode> toXmlChildren({Map<String, String?> namespaces = const {}}) =>
_$WebDavMultistatusToXmlChildren(this as WebDavMultistatus, namespaces: namespaces);
XmlElement toXmlElement({Map<String, String?> namespaces = const {}}) =>
_$WebDavMultistatusToXmlElement(this as WebDavMultistatus, namespaces: namespaces);
}
void _$WebDavResponseBuildXmlChildren(WebDavResponse instance, XmlBuilder builder,
{Map<String, String> namespaces = const {}}) {
final href = instance.href;
final hrefSerialized = href;
builder.element('href', namespace: 'DAV:', nest: () {
if (hrefSerialized != null) {
builder.text(hrefSerialized);
}
});
final propstats = instance.propstats;
final propstatsSerialized = propstats;
for (final value in propstatsSerialized) {
builder.element('propstat', namespace: 'DAV:', nest: () {
value.buildXmlChildren(builder, namespaces: namespaces);
});
}
}
void _$WebDavResponseBuildXmlElement(WebDavResponse instance, XmlBuilder builder,
{Map<String, String> namespaces = const {}}) {
builder.element('response', namespace: 'DAV:', namespaces: namespaces, nest: () {
instance.buildXmlChildren(builder, namespaces: namespaces);
});
}
WebDavResponse _$WebDavResponseFromXmlElement(XmlElement element) {
final href = element.getElement('href', namespace: 'DAV:')?.getText();
final propstats = element.getElements('propstat', namespace: 'DAV:')!;
return WebDavResponse(href: href, propstats: propstats.map((e) => WebDavPropstat.fromXmlElement(e)).toList());
}
List<XmlAttribute> _$WebDavResponseToXmlAttributes(WebDavResponse instance,
{Map<String, String?> namespaces = const {}}) {
final attributes = <XmlAttribute>[];
return attributes;
}
List<XmlNode> _$WebDavResponseToXmlChildren(WebDavResponse instance, {Map<String, String?> namespaces = const {}}) {
final children = <XmlNode>[];
final href = instance.href;
final hrefSerialized = href;
final hrefConstructed =
XmlElement(XmlName('href', namespaces['DAV:']), [], hrefSerialized != null ? [XmlText(hrefSerialized)] : []);
children.add(hrefConstructed);
final propstats = instance.propstats;
final propstatsSerialized = propstats;
final propstatsConstructed = propstatsSerialized.map((e) => XmlElement(XmlName('propstat', namespaces['DAV:']),
e.toXmlAttributes(namespaces: namespaces), e.toXmlChildren(namespaces: namespaces)));
children.addAll(propstatsConstructed);
return children;
}
XmlElement _$WebDavResponseToXmlElement(WebDavResponse instance, {Map<String, String?> namespaces = const {}}) {
return XmlElement(
XmlName('response', namespaces['DAV:']),
[...namespaces.toXmlAttributes(), ...instance.toXmlAttributes(namespaces: namespaces)],
instance.toXmlChildren(namespaces: namespaces));
}
mixin _$WebDavResponseXmlSerializableMixin {
void buildXmlChildren(XmlBuilder builder, {Map<String, String> namespaces = const {}}) =>
_$WebDavResponseBuildXmlChildren(this as WebDavResponse, builder, namespaces: namespaces);
void buildXmlElement(XmlBuilder builder, {Map<String, String> namespaces = const {}}) =>
_$WebDavResponseBuildXmlElement(this as WebDavResponse, builder, namespaces: namespaces);
List<XmlAttribute> toXmlAttributes({Map<String, String?> namespaces = const {}}) =>
_$WebDavResponseToXmlAttributes(this as WebDavResponse, namespaces: namespaces);
List<XmlNode> toXmlChildren({Map<String, String?> namespaces = const {}}) =>
_$WebDavResponseToXmlChildren(this as WebDavResponse, namespaces: namespaces);
XmlElement toXmlElement({Map<String, String?> namespaces = const {}}) =>
_$WebDavResponseToXmlElement(this as WebDavResponse, namespaces: namespaces);
}
void _$WebDavPropstatBuildXmlChildren(WebDavPropstat instance, XmlBuilder builder,
{Map<String, String> namespaces = const {}}) {
final status = instance.status;
final statusSerialized = status;
builder.element('status', namespace: 'DAV:', nest: () {
builder.text(statusSerialized);
});
final prop = instance.prop;
final propSerialized = prop;
builder.element('prop', namespace: 'DAV:', nest: () {
propSerialized.buildXmlChildren(builder, namespaces: namespaces);
});
}
void _$WebDavPropstatBuildXmlElement(WebDavPropstat instance, XmlBuilder builder,
{Map<String, String> namespaces = const {}}) {
builder.element('propstat', namespace: 'DAV:', namespaces: namespaces, nest: () {
instance.buildXmlChildren(builder, namespaces: namespaces);
});
}
WebDavPropstat _$WebDavPropstatFromXmlElement(XmlElement element) {
final status = element.getElement('status', namespace: 'DAV:')!.getText()!;
final prop = element.getElement('prop', namespace: 'DAV:')!;
return WebDavPropstat(status: status, prop: WebDavProp.fromXmlElement(prop));
}
List<XmlAttribute> _$WebDavPropstatToXmlAttributes(WebDavPropstat instance,
{Map<String, String?> namespaces = const {}}) {
final attributes = <XmlAttribute>[];
return attributes;
}
List<XmlNode> _$WebDavPropstatToXmlChildren(WebDavPropstat instance, {Map<String, String?> namespaces = const {}}) {
final children = <XmlNode>[];
final status = instance.status;
final statusSerialized = status;
final statusConstructed = XmlElement(XmlName('status', namespaces['DAV:']), [], [XmlText(statusSerialized)]);
children.add(statusConstructed);
final prop = instance.prop;
final propSerialized = prop;
final propConstructed = XmlElement(XmlName('prop', namespaces['DAV:']),
propSerialized.toXmlAttributes(namespaces: namespaces), propSerialized.toXmlChildren(namespaces: namespaces));
children.add(propConstructed);
return children;
}
XmlElement _$WebDavPropstatToXmlElement(WebDavPropstat instance, {Map<String, String?> namespaces = const {}}) {
return XmlElement(
XmlName('propstat', namespaces['DAV:']),
[...namespaces.toXmlAttributes(), ...instance.toXmlAttributes(namespaces: namespaces)],
instance.toXmlChildren(namespaces: namespaces));
}
mixin _$WebDavPropstatXmlSerializableMixin {
void buildXmlChildren(XmlBuilder builder, {Map<String, String> namespaces = const {}}) =>
_$WebDavPropstatBuildXmlChildren(this as WebDavPropstat, builder, namespaces: namespaces);
void buildXmlElement(XmlBuilder builder, {Map<String, String> namespaces = const {}}) =>
_$WebDavPropstatBuildXmlElement(this as WebDavPropstat, builder, namespaces: namespaces);
List<XmlAttribute> toXmlAttributes({Map<String, String?> namespaces = const {}}) =>
_$WebDavPropstatToXmlAttributes(this as WebDavPropstat, namespaces: namespaces);
List<XmlNode> toXmlChildren({Map<String, String?> namespaces = const {}}) =>
_$WebDavPropstatToXmlChildren(this as WebDavPropstat, namespaces: namespaces);
XmlElement toXmlElement({Map<String, String?> namespaces = const {}}) =>
_$WebDavPropstatToXmlElement(this as WebDavPropstat, namespaces: namespaces);
}
void _$WebDavPropertyupdateBuildXmlChildren(WebDavPropertyupdate instance, XmlBuilder builder,
{Map<String, String> namespaces = const {}}) {
final set = instance.set;
final setSerialized = set;
builder.element('set', namespace: 'DAV:', nest: () {
setSerialized.buildXmlChildren(builder, namespaces: namespaces);
});
}
void _$WebDavPropertyupdateBuildXmlElement(WebDavPropertyupdate instance, XmlBuilder builder,
{Map<String, String> namespaces = const {}}) {
builder.element('propertyupdate', namespace: 'DAV:', namespaces: namespaces, nest: () {
instance.buildXmlChildren(builder, namespaces: namespaces);
});
}
WebDavPropertyupdate _$WebDavPropertyupdateFromXmlElement(XmlElement element) {
final set = element.getElement('set', namespace: 'DAV:')!;
return WebDavPropertyupdate(set: WebDavSet.fromXmlElement(set));
}
List<XmlAttribute> _$WebDavPropertyupdateToXmlAttributes(WebDavPropertyupdate instance,
{Map<String, String?> namespaces = const {}}) {
final attributes = <XmlAttribute>[];
return attributes;
}
List<XmlNode> _$WebDavPropertyupdateToXmlChildren(WebDavPropertyupdate instance,
{Map<String, String?> namespaces = const {}}) {
final children = <XmlNode>[];
final set = instance.set;
final setSerialized = set;
final setConstructed = XmlElement(XmlName('set', namespaces['DAV:']),
setSerialized.toXmlAttributes(namespaces: namespaces), setSerialized.toXmlChildren(namespaces: namespaces));
children.add(setConstructed);
return children;
}
XmlElement _$WebDavPropertyupdateToXmlElement(WebDavPropertyupdate instance,
{Map<String, String?> namespaces = const {}}) {
return XmlElement(
XmlName('propertyupdate', namespaces['DAV:']),
[...namespaces.toXmlAttributes(), ...instance.toXmlAttributes(namespaces: namespaces)],
instance.toXmlChildren(namespaces: namespaces));
}
mixin _$WebDavPropertyupdateXmlSerializableMixin {
void buildXmlChildren(XmlBuilder builder, {Map<String, String> namespaces = const {}}) =>
_$WebDavPropertyupdateBuildXmlChildren(this as WebDavPropertyupdate, builder, namespaces: namespaces);
void buildXmlElement(XmlBuilder builder, {Map<String, String> namespaces = const {}}) =>
_$WebDavPropertyupdateBuildXmlElement(this as WebDavPropertyupdate, builder, namespaces: namespaces);
List<XmlAttribute> toXmlAttributes({Map<String, String?> namespaces = const {}}) =>
_$WebDavPropertyupdateToXmlAttributes(this as WebDavPropertyupdate, namespaces: namespaces);
List<XmlNode> toXmlChildren({Map<String, String?> namespaces = const {}}) =>
_$WebDavPropertyupdateToXmlChildren(this as WebDavPropertyupdate, namespaces: namespaces);
XmlElement toXmlElement({Map<String, String?> namespaces = const {}}) =>
_$WebDavPropertyupdateToXmlElement(this as WebDavPropertyupdate, namespaces: namespaces);
}
void _$WebDavSetBuildXmlChildren(WebDavSet instance, XmlBuilder builder, {Map<String, String> namespaces = const {}}) {
final prop = instance.prop;
final propSerialized = prop;
builder.element('prop', namespace: 'DAV:', nest: () {
propSerialized.buildXmlChildren(builder, namespaces: namespaces);
});
}
void _$WebDavSetBuildXmlElement(WebDavSet instance, XmlBuilder builder, {Map<String, String> namespaces = const {}}) {
builder.element('propertyupdate', namespace: 'DAV:', namespaces: namespaces, nest: () {
instance.buildXmlChildren(builder, namespaces: namespaces);
});
}
WebDavSet _$WebDavSetFromXmlElement(XmlElement element) {
final prop = element.getElement('prop', namespace: 'DAV:')!;
return WebDavSet(prop: WebDavProp.fromXmlElement(prop));
}
List<XmlAttribute> _$WebDavSetToXmlAttributes(WebDavSet instance, {Map<String, String?> namespaces = const {}}) {
final attributes = <XmlAttribute>[];
return attributes;
}
List<XmlNode> _$WebDavSetToXmlChildren(WebDavSet instance, {Map<String, String?> namespaces = const {}}) {
final children = <XmlNode>[];
final prop = instance.prop;
final propSerialized = prop;
final propConstructed = XmlElement(XmlName('prop', namespaces['DAV:']),
propSerialized.toXmlAttributes(namespaces: namespaces), propSerialized.toXmlChildren(namespaces: namespaces));
children.add(propConstructed);
return children;
}
XmlElement _$WebDavSetToXmlElement(WebDavSet instance, {Map<String, String?> namespaces = const {}}) {
return XmlElement(
XmlName('propertyupdate', namespaces['DAV:']),
[...namespaces.toXmlAttributes(), ...instance.toXmlAttributes(namespaces: namespaces)],
instance.toXmlChildren(namespaces: namespaces));
}
mixin _$WebDavSetXmlSerializableMixin {
void buildXmlChildren(XmlBuilder builder, {Map<String, String> namespaces = const {}}) =>
_$WebDavSetBuildXmlChildren(this as WebDavSet, builder, namespaces: namespaces);
void buildXmlElement(XmlBuilder builder, {Map<String, String> namespaces = const {}}) =>
_$WebDavSetBuildXmlElement(this as WebDavSet, builder, namespaces: namespaces);
List<XmlAttribute> toXmlAttributes({Map<String, String?> namespaces = const {}}) =>
_$WebDavSetToXmlAttributes(this as WebDavSet, namespaces: namespaces);
List<XmlNode> toXmlChildren({Map<String, String?> namespaces = const {}}) =>
_$WebDavSetToXmlChildren(this as WebDavSet, namespaces: namespaces);
XmlElement toXmlElement({Map<String, String?> namespaces = const {}}) =>
_$WebDavSetToXmlElement(this as WebDavSet, namespaces: namespaces);
}
void _$WebDavPropfindBuildXmlChildren(WebDavPropfind instance, XmlBuilder builder,
{Map<String, String> namespaces = const {}}) {
final prop = instance.prop;
final propSerialized = prop;
builder.element('prop', namespace: 'DAV:', nest: () {
propSerialized.buildXmlChildren(builder, namespaces: namespaces);
});
}
void _$WebDavPropfindBuildXmlElement(WebDavPropfind instance, XmlBuilder builder,
{Map<String, String> namespaces = const {}}) {
builder.element('propfind', namespace: 'DAV:', namespaces: namespaces, nest: () {
instance.buildXmlChildren(builder, namespaces: namespaces);
});
}
WebDavPropfind _$WebDavPropfindFromXmlElement(XmlElement element) {
final prop = element.getElement('prop', namespace: 'DAV:')!;
return WebDavPropfind(prop: WebDavPropfindProp.fromXmlElement(prop));
}
List<XmlAttribute> _$WebDavPropfindToXmlAttributes(WebDavPropfind instance,
{Map<String, String?> namespaces = const {}}) {
final attributes = <XmlAttribute>[];
return attributes;
}
List<XmlNode> _$WebDavPropfindToXmlChildren(WebDavPropfind instance, {Map<String, String?> namespaces = const {}}) {
final children = <XmlNode>[];
final prop = instance.prop;
final propSerialized = prop;
final propConstructed = XmlElement(XmlName('prop', namespaces['DAV:']),
propSerialized.toXmlAttributes(namespaces: namespaces), propSerialized.toXmlChildren(namespaces: namespaces));
children.add(propConstructed);
return children;
}
XmlElement _$WebDavPropfindToXmlElement(WebDavPropfind instance, {Map<String, String?> namespaces = const {}}) {
return XmlElement(
XmlName('propfind', namespaces['DAV:']),
[...namespaces.toXmlAttributes(), ...instance.toXmlAttributes(namespaces: namespaces)],
instance.toXmlChildren(namespaces: namespaces));
}
mixin _$WebDavPropfindXmlSerializableMixin {
void buildXmlChildren(XmlBuilder builder, {Map<String, String> namespaces = const {}}) =>
_$WebDavPropfindBuildXmlChildren(this as WebDavPropfind, builder, namespaces: namespaces);
void buildXmlElement(XmlBuilder builder, {Map<String, String> namespaces = const {}}) =>
_$WebDavPropfindBuildXmlElement(this as WebDavPropfind, builder, namespaces: namespaces);
List<XmlAttribute> toXmlAttributes({Map<String, String?> namespaces = const {}}) =>
_$WebDavPropfindToXmlAttributes(this as WebDavPropfind, namespaces: namespaces);
List<XmlNode> toXmlChildren({Map<String, String?> namespaces = const {}}) =>
_$WebDavPropfindToXmlChildren(this as WebDavPropfind, namespaces: namespaces);
XmlElement toXmlElement({Map<String, String?> namespaces = const {}}) =>
_$WebDavPropfindToXmlElement(this as WebDavPropfind, namespaces: namespaces);
}
void _$WebDavOcFilterFilesBuildXmlChildren(WebDavOcFilterFiles instance, XmlBuilder builder,
{Map<String, String> namespaces = const {}}) {
final filterRules = instance.filterRules;
final filterRulesSerialized = filterRules;
builder.element('filter-rules', namespace: 'http://owncloud.org/ns', nest: () {
filterRulesSerialized.buildXmlChildren(builder, namespaces: namespaces);
});
final prop = instance.prop;
final propSerialized = prop;
builder.element('prop', namespace: 'DAV:', nest: () {
propSerialized.buildXmlChildren(builder, namespaces: namespaces);
});
}
void _$WebDavOcFilterFilesBuildXmlElement(WebDavOcFilterFiles instance, XmlBuilder builder,
{Map<String, String> namespaces = const {}}) {
builder.element('filter-files', namespace: 'http://owncloud.org/ns', namespaces: namespaces, nest: () {
instance.buildXmlChildren(builder, namespaces: namespaces);
});
}
WebDavOcFilterFiles _$WebDavOcFilterFilesFromXmlElement(XmlElement element) {
final filterRules = element.getElement('filter-rules', namespace: 'http://owncloud.org/ns')!;
final prop = element.getElement('prop', namespace: 'DAV:')!;
return WebDavOcFilterFiles(
filterRules: WebDavOcFilterRules.fromXmlElement(filterRules), prop: WebDavPropfindProp.fromXmlElement(prop));
}
List<XmlAttribute> _$WebDavOcFilterFilesToXmlAttributes(WebDavOcFilterFiles instance,
{Map<String, String?> namespaces = const {}}) {
final attributes = <XmlAttribute>[];
return attributes;
}
List<XmlNode> _$WebDavOcFilterFilesToXmlChildren(WebDavOcFilterFiles instance,
{Map<String, String?> namespaces = const {}}) {
final children = <XmlNode>[];
final filterRules = instance.filterRules;
final filterRulesSerialized = filterRules;
final filterRulesConstructed = XmlElement(
XmlName('filter-rules', namespaces['http://owncloud.org/ns']),
filterRulesSerialized.toXmlAttributes(namespaces: namespaces),
filterRulesSerialized.toXmlChildren(namespaces: namespaces));
children.add(filterRulesConstructed);
final prop = instance.prop;
final propSerialized = prop;
final propConstructed = XmlElement(XmlName('prop', namespaces['DAV:']),
propSerialized.toXmlAttributes(namespaces: namespaces), propSerialized.toXmlChildren(namespaces: namespaces));
children.add(propConstructed);
return children;
}
XmlElement _$WebDavOcFilterFilesToXmlElement(WebDavOcFilterFiles instance,
{Map<String, String?> namespaces = const {}}) {
return XmlElement(
XmlName('filter-files', namespaces['http://owncloud.org/ns']),
[...namespaces.toXmlAttributes(), ...instance.toXmlAttributes(namespaces: namespaces)],
instance.toXmlChildren(namespaces: namespaces));
}
mixin _$WebDavOcFilterFilesXmlSerializableMixin {
void buildXmlChildren(XmlBuilder builder, {Map<String, String> namespaces = const {}}) =>
_$WebDavOcFilterFilesBuildXmlChildren(this as WebDavOcFilterFiles, builder, namespaces: namespaces);
void buildXmlElement(XmlBuilder builder, {Map<String, String> namespaces = const {}}) =>
_$WebDavOcFilterFilesBuildXmlElement(this as WebDavOcFilterFiles, builder, namespaces: namespaces);
List<XmlAttribute> toXmlAttributes({Map<String, String?> namespaces = const {}}) =>
_$WebDavOcFilterFilesToXmlAttributes(this as WebDavOcFilterFiles, namespaces: namespaces);
List<XmlNode> toXmlChildren({Map<String, String?> namespaces = const {}}) =>
_$WebDavOcFilterFilesToXmlChildren(this as WebDavOcFilterFiles, namespaces: namespaces);
XmlElement toXmlElement({Map<String, String?> namespaces = const {}}) =>
_$WebDavOcFilterFilesToXmlElement(this as WebDavOcFilterFiles, namespaces: namespaces);
}

2
packages/nextcloud/pubspec.yaml

@ -12,6 +12,7 @@ dependencies:
json_annotation: ^4.7.0 json_annotation: ^4.7.0
version: ^3.0.2 version: ^3.0.2
xml: ^6.1.0 xml: ^6.1.0
xml_annotation: ^2.2.0
dev_dependencies: dev_dependencies:
build_runner: ^2.2.1 build_runner: ^2.2.1
@ -25,3 +26,4 @@ dev_dependencies:
ref: 0b2ee0d ref: 0b2ee0d
process_run: ^0.12.5+2 process_run: ^0.12.5+2
test: ^1.16.0 test: ^1.16.0
xml_serializable: ^2.2.2

295
packages/nextcloud/test/webdav.dart

@ -36,21 +36,23 @@ Future run(final DockerImage image) async {
}); });
test('List directory', () async { test('List directory', () async {
final files = await client.webdav.ls( final responses = (await client.webdav.ls(
'/', '/',
props: { prop: WebDavPropfindProp(
WebDavProps.ncHasPreview.name, nchaspreview: true,
WebDavProps.davContentType.name, davgetcontenttype: true,
WebDavProps.davLastModified.name, davgetlastmodified: true,
WebDavProps.ocSize.name, ocsize: true,
}, ),
); ))
expect(files, hasLength(8)); .responses;
final file = files.singleWhere((final f) => f.name == 'Nextcloud.png'); expect(responses, hasLength(9));
expect(file.hasPreview, isTrue); final props =
expect(file.mimeType, 'image/png'); responses.singleWhere((final response) => response.href!.endsWith('/Nextcloud.png')).propstats.first.prop;
expect(file.lastModified!.isBefore(DateTime.now()), isTrue); expect(props.nchaspreview, isTrue);
expect(file.size, 50598); expect(props.davgetcontenttype, 'image/png');
expect(webdavDateFormat.parseUtc(props.davgetlastmodified!).isBefore(DateTime.now()), isTrue);
expect(props.ocsize, 50598);
}); });
test('Create directory', () async { test('Create directory', () async {
@ -62,9 +64,9 @@ Future run(final DockerImage image) async {
final response = await client.webdav.mkdirs('test/bla'); final response = await client.webdav.mkdirs('test/bla');
expect(response!.statusCode, equals(201)); expect(response!.statusCode, equals(201));
final files = await client.webdav.ls('/test'); final responses = (await client.webdav.ls('/test')).responses;
expect(files, hasLength(1)); expect(responses, hasLength(2));
expect(files[0].path, '/test/bla/'); expect(responses[1].href, endsWith('/test/bla/'));
}); });
test('Upload files', () async { test('Upload files', () async {
@ -77,38 +79,57 @@ Future run(final DockerImage image) async {
response = await client.webdav.upload(txtBytes, 'test.txt'); response = await client.webdav.upload(txtBytes, 'test.txt');
expect(response.statusCode, equals(201)); expect(response.statusCode, equals(201));
final files = await client.webdav.ls( final responses = (await client.webdav.ls(
'/', '/',
props: { prop: WebDavPropfindProp(
WebDavProps.ocSize.name, ocsize: true,
}, ),
))
.responses;
expect(responses, hasLength(11));
expect(
responses.singleWhere((final response) => response.href!.endsWith('/test.png')).propstats.first.prop.ocsize,
pngBytes.lengthInBytes,
);
expect(
responses.singleWhere((final response) => response.href!.endsWith('/test.txt')).propstats.first.prop.ocsize,
txtBytes.lengthInBytes,
); );
expect(files, hasLength(10));
final pngFile = files.singleWhere((final f) => f.name == 'test.png');
final txtFile = files.singleWhere((final f) => f.name == 'test.txt');
expect(pngFile.size, pngBytes.lengthInBytes);
expect(txtFile.size, txtBytes.lengthInBytes);
}); });
test('Upload file with modified time', () async { test('Upload file', () async {
final lastModified = DateTime.fromMillisecondsSinceEpoch(DateTime.now().millisecondsSinceEpoch ~/ 1000 * 1000); final lastModified = DateTime.fromMillisecondsSinceEpoch(DateTime.now().millisecondsSinceEpoch ~/ 1000 * 1000);
final created = lastModified.subtract(const Duration(hours: 1));
final txtBytes = File('test/files/test.txt').readAsBytesSync(); final txtBytes = File('test/files/test.txt').readAsBytesSync();
final response = await client.webdav.upload( final response = await client.webdav.upload(
txtBytes, txtBytes,
'test.txt', 'test.txt',
lastModified: lastModified, lastModified: lastModified,
created: created,
); );
expect(response.statusCode, equals(201)); expect(response.statusCode, equals(201));
final files = await client.webdav.ls( final props = (await client.webdav.ls(
'/', '/',
props: { prop: WebDavPropfindProp(
WebDavProps.davLastModified.name, davgetlastmodified: true,
}, nccreationtime: true,
),
))
.responses
.singleWhere((final response) => response.href!.endsWith('/test.txt'))
.propstats
.first
.prop;
expect(
webdavDateFormat.parseUtc(props.davgetlastmodified!).millisecondsSinceEpoch,
lastModified.millisecondsSinceEpoch,
);
expect(
DateTime.fromMillisecondsSinceEpoch(props.nccreationtime! * 1000).millisecondsSinceEpoch,
created.millisecondsSinceEpoch,
); );
final txtFile = files.singleWhere((final f) => f.name == 'test.txt');
expect(txtFile.lastModified!.millisecondsSinceEpoch, lastModified.millisecondsSinceEpoch);
}); });
test('Copy file', () async { test('Copy file', () async {
@ -117,9 +138,9 @@ Future run(final DockerImage image) async {
'test.png', 'test.png',
); );
expect(response.statusCode, 201); expect(response.statusCode, 201);
final files = await client.webdav.ls('/'); final responses = (await client.webdav.ls('/')).responses;
expect(files.where((final f) => f.name == 'Nextcloud.png'), hasLength(1)); expect(responses.where((final response) => response.href!.endsWith('/Nextcloud.png')), hasLength(1));
expect(files.where((final f) => f.name == 'test.png'), hasLength(1)); expect(responses.where((final response) => response.href!.endsWith('/test.png')), hasLength(1));
}); });
test('Copy file (overwrite fail)', () async { test('Copy file (overwrite fail)', () async {
@ -150,9 +171,9 @@ Future run(final DockerImage image) async {
'test.png', 'test.png',
); );
expect(response.statusCode, 201); expect(response.statusCode, 201);
final files = await client.webdav.ls('/'); final responses = (await client.webdav.ls('/')).responses;
expect(files.where((final f) => f.name == 'Nextcloud.png'), hasLength(0)); expect(responses.where((final response) => response.href!.endsWith('/Nextcloud.png')), hasLength(0));
expect(files.where((final f) => f.name == 'test.png'), hasLength(1)); expect(responses.where((final response) => response.href!.endsWith('/test.png')), hasLength(1));
}); });
test('Move file (overwrite fail)', () async { test('Move file (overwrite fail)', () async {
@ -178,19 +199,69 @@ Future run(final DockerImage image) async {
}); });
test('Get file props', () async { test('Get file props', () async {
final file = await client.webdav.getProps( final props = (await client.webdav.ls(
'Nextcloud.png', 'Nextcloud.png',
props: { prop: WebDavPropfindProp(
WebDavProps.ncHasPreview.name, davgetlastmodified: true,
WebDavProps.davContentType.name, davgetetag: true,
WebDavProps.davLastModified.name, davgetcontenttype: true,
WebDavProps.ocSize.name, davgetcontentlength: true,
}, ocid: true,
); ocfileid: true,
expect(file.hasPreview, isTrue); ocfavorite: true,
expect(file.mimeType, 'image/png'); occommentshref: true,
expect(file.lastModified!.isBefore(DateTime.now()), isTrue); occommentscount: true,
expect(file.size, 50598); occommentsunread: true,
ocdownloadurl: true,
ocownerid: true,
ocownerdisplayname: true,
ocsize: true,
ocpermissions: true,
ncnote: true,
ncdatafingerprint: true,
nchaspreview: true,
ncmounttype: true,
ncisencrypted: true,
ncmetadataetag: true,
ncuploadtime: true,
nccreationtime: true,
ncrichworkspace: true,
ocssharepermissions: true,
ocmsharepermissions: true,
),
depth: 0,
))
.responses
.single
.propstats
.first
.prop;
expect(webdavDateFormat.parseUtc(props.davgetlastmodified!).isBefore(DateTime.now()), isTrue);
expect(props.davgetetag, isNotEmpty);
expect(props.davgetcontenttype, 'image/png');
expect(props.davgetcontentlength, 50598);
expect(props.ocid, isNotEmpty);
expect(props.ocfileid, isNotEmpty);
expect(props.ocfavorite, 0);
expect(props.occommentshref, isNotEmpty);
expect(props.occommentscount, 0);
expect(props.occommentsunread, 0);
expect(props.ocdownloadurl, isNull);
expect(props.ocownerid, 'user1');
expect(props.ocownerdisplayname, 'User One');
expect(props.ocsize, 50598);
expect(props.ocpermissions, 'RGDNVW');
expect(props.ncnote, isNull);
expect(props.ncdatafingerprint, isNull);
expect(props.nchaspreview, isTrue);
expect(props.ncmounttype, isNull);
expect(props.ncisencrypted, isNull);
expect(props.ncmetadataetag, isNull);
expect(props.ncuploadtime, 0);
expect(props.nccreationtime, 0);
expect(props.ncrichworkspace, isNull);
expect(props.ocssharepermissions, 19);
expect(json.decode(props.ocmsharepermissions!), ['share', 'read', 'write']);
}); });
test('Get directory props', () async { test('Get directory props', () async {
@ -198,90 +269,86 @@ Future run(final DockerImage image) async {
await client.webdav.mkdir('test'); await client.webdav.mkdir('test');
await client.webdav.upload(data, 'test/test.txt'); await client.webdav.upload(data, 'test/test.txt');
final file = await client.webdav.getProps( final props = (await client.webdav.ls(
'test', 'test',
props: { prop: WebDavPropfindProp(
WebDavProps.davResourceType.name, davgetcontenttype: true,
WebDavProps.davContentType.name, davgetlastmodified: true,
WebDavProps.davLastModified.name, ocsize: true,
WebDavProps.ocSize.name, ),
}, depth: 0,
); ))
expect(file.isDirectory, isTrue); .responses
expect(file.name, 'test'); .single
expect(file.mimeType, null); .propstats
expectDateInReasonableTimeRange(file.lastModified!, DateTime.now()); .first
expect(file.size, data.lengthInBytes); .prop;
expect(props.davgetcontenttype, isNull);
expectDateInReasonableTimeRange(webdavDateFormat.parseUtc(props.davgetlastmodified!), DateTime.now());
expect(props.ocsize, data.lengthInBytes);
}); });
test('Filter files', () async { test('Filter files', () async {
final response = await client.webdav.upload(Uint8List.fromList(utf8.encode('test')), 'test.txt'); final response = await client.webdav.upload(Uint8List.fromList(utf8.encode('test')), 'test.txt');
final id = response.headers['oc-fileid']!.first; final id = response.headers['oc-fileid']!.first;
await client.webdav.updateProps('test.txt', {WebDavProps.ocFavorite.name: '1'}); await client.webdav.updateProps(
'test.txt',
WebDavProp(
ocfavorite: 1,
),
);
final files = await client.webdav.filter( final responses = (await client.webdav.filter(
'/', '/',
{ WebDavOcFilterRules(
WebDavProps.ocFavorite.name: '1', ocfavorite: 1,
}, ),
props: { prop: WebDavPropfindProp(
WebDavProps.ocId.name, ocid: true,
WebDavProps.ocFavorite.name, ocfavorite: true,
}, ),
); ))
expect(files, hasLength(1)); .responses;
final file = files.singleWhere((final e) => e.name == 'test.txt'); expect(responses, hasLength(1));
expect(file.id, id); final props =
expect(file.favorite, isTrue); responses.singleWhere((final response) => response.href!.endsWith('/test.txt')).propstats.first.prop;
expect(props.ocid, id);
expect(props.ocfavorite, 1);
}); });
test('Set properties', () async { test('Set properties', () async {
final createdDate = DateTime.utc(1971, 2); final createdDate = DateTime.utc(1971, 2);
final createdEpoch = createdDate.millisecondsSinceEpoch / 1000; final createdEpoch = createdDate.millisecondsSinceEpoch ~/ 1000;
final uploadTime = DateTime.now(); final uploadTime = DateTime.now();
await client.webdav.upload(Uint8List.fromList(utf8.encode('test')), 'test.txt'); await client.webdav.upload(Uint8List.fromList(utf8.encode('test')), 'test.txt');
final updated = await client.webdav.updateProps('test.txt', { final updated = await client.webdav.updateProps(
WebDavProps.ocFavorite.name: '1',
WebDavProps.ncCreationTime.name: '$createdEpoch',
});
expect(updated, isTrue);
final file = await client.webdav.getProps(
'test.txt', 'test.txt',
props: { WebDavProp(
WebDavProps.ocFavorite.name, ocfavorite: 1,
WebDavProps.ncCreationTime.name, nccreationtime: createdEpoch,
WebDavProps.ncUploadTime.name, ),
},
); );
expect(file.favorite, isTrue);
expect(file.createdDate!.isAtSameMomentAs(createdDate), isTrue);
expectDateInReasonableTimeRange(file.uploadedDate!, uploadTime);
});
test('Set custom properties', () async {
client.webdav.registerNamespace('http://example.com/ns', 'test');
await client.webdav.upload(Uint8List.fromList(utf8.encode('test')), 'test.txt');
final updated = await client.webdav.updateProps('test.txt', {
'test:custom': 'test-custom-prop-value',
});
expect(updated, isTrue); expect(updated, isTrue);
final file = await client.webdav.getProps( final props = (await client.webdav.ls(
'test.txt', 'test.txt',
props: { prop: WebDavPropfindProp(
'test:custom', ocfavorite: true,
}, nccreationtime: true,
); ncuploadtime: true,
),
expect( depth: 0,
file.getProp('test:custom')!.text, ))
'test-custom-prop-value', .responses
); .single
.propstats
.first
.prop;
expect(props.ocfavorite, 1);
expect(DateTime.fromMillisecondsSinceEpoch(props.nccreationtime! * 1000).isAtSameMomentAs(createdDate), isTrue);
expectDateInReasonableTimeRange(DateTime.fromMillisecondsSinceEpoch(props.ncuploadtime! * 1000), uploadTime);
}); });
}); });
} }

1
tool/generate-nextcloud.sh

@ -62,6 +62,7 @@ jq \
( (
cd packages/nextcloud cd packages/nextcloud
rm -rf .dart_tool/build rm -rf .dart_tool/build
fvm dart run nextcloud:generate_props
fvm dart pub run build_runner build --delete-conflicting-outputs fvm dart pub run build_runner build --delete-conflicting-outputs
# For some reason we need to fix and format twice, otherwise not everything gets fixed # For some reason we need to fix and format twice, otherwise not everything gets fixed
fvm dart fix --apply lib/src/nextcloud.openapi.dart fvm dart fix --apply lib/src/nextcloud.openapi.dart

Loading…
Cancel
Save