Browse Source

Merge pull request #520 from provokateurin/refactor/webdav-methods

Refactor/webdav methods
pull/522/head
Kate 1 year ago committed by GitHub
parent
commit
0c6c44d1fb
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 11
      packages/neon/neon_files/lib/blocs/browser.dart
  2. 11
      packages/neon/neon_files/lib/blocs/files.dart
  3. 2
      packages/neon/neon_files/lib/utils/download_task.dart
  4. 2
      packages/neon/neon_files/lib/utils/upload_task.dart
  5. 2
      packages/nextcloud/bin/generate_props.dart
  6. 283
      packages/nextcloud/lib/src/webdav/client.dart
  7. 9
      packages/nextcloud/lib/src/webdav/props.dart
  8. 27
      packages/nextcloud/lib/src/webdav/props.g.dart
  9. 27
      packages/nextcloud/lib/src/webdav/webdav.dart
  10. 105
      packages/nextcloud/lib/src/webdav/webdav.g.dart
  11. 141
      packages/nextcloud/test/webdav_test.dart

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

@ -44,9 +44,9 @@ class FilesBrowserBloc extends InteractiveBloc implements FilesBrowserBlocEvents
client.id,
'files-${path.value.join('/')}',
files,
() async => client.webdav.ls(
() async => client.webdav.propfind(
path.value.join('/'),
prop: WebDavPropfindProp.fromBools(
prop: WebDavPropWithoutValues.fromBools(
davgetcontenttype: true,
davgetetag: true,
davgetlastmodified: true,
@ -69,11 +69,6 @@ class FilesBrowserBloc extends InteractiveBloc implements FilesBrowserBlocEvents
@override
void createFolder(final List<String> path) {
wrapAction(
() async => client.webdav.mkdir(
path.join('/'),
safe: false,
),
);
wrapAction(() async => client.webdav.mkcol(path.join('/')));
}
}

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

@ -65,7 +65,12 @@ class FilesBloc extends InteractiveBloc implements FilesBlocEvents, FilesBlocSta
@override
void addFavorite(final List<String> path) {
wrapAction(() async => client.webdav.updateProps(path.join('/'), WebDavProp(ocfavorite: 1)));
wrapAction(
() async => client.webdav.proppatch(
path.join('/'),
set: WebDavProp(ocfavorite: 1),
),
);
}
@override
@ -119,9 +124,9 @@ class FilesBloc extends InteractiveBloc implements FilesBlocEvents, FilesBlocSta
@override
void removeFavorite(final List<String> path) {
wrapAction(
() async => client.webdav.updateProps(
() async => client.webdav.proppatch(
path.join('/'),
WebDavProp(ocfavorite: 0),
set: WebDavProp(ocfavorite: 0),
),
);
}

2
packages/neon/neon_files/lib/utils/download_task.dart

@ -13,7 +13,7 @@ class DownloadTask {
Future execute(final NextcloudClient client, final IOSink sink) async {
final completer = Completer();
final response = await client.webdav.downloadStream(path.join('/'));
final response = await client.webdav.getStream(path.join('/'));
var downloaded = 0;
response.listen((final chunk) async {

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

@ -16,7 +16,7 @@ class UploadTask {
Future execute(final NextcloudClient client, final Stream<List<int>> stream) async {
var uploaded = 0;
await client.webdav.uploadStream(
await client.webdav.putStream(
stream.map((final chunk) {
uploaded += chunk.length;
_streamController.add((uploaded / size * 100).toInt());

2
packages/nextcloud/bin/generate_props.dart

@ -29,7 +29,7 @@ void main() {
"part 'props.g.dart';",
'',
...generateClass(
'WebDavPropfindProp',
'WebDavPropWithoutValues',
'prop',
'namespaceDav',
findProps,

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

@ -20,8 +20,7 @@ class WebDavClient {
Future<HttpClientResponse> _send(
final String method,
final String url,
final List<int> expectedCodes, {
final String url, {
final Stream<Uint8List>? data,
final Map<String, String>? headers,
}) async {
@ -29,7 +28,6 @@ class WebDavClient {
method,
Uri.parse(url),
)
..followRedirects = false
..persistentConnection = true;
for (final header in {
HttpHeaders.contentTypeHeader: 'application/xml',
@ -46,7 +44,7 @@ class WebDavClient {
final response = await request.close();
if (!expectedCodes.contains(response.statusCode)) {
if (response.statusCode > 299) {
throw DynamiteApiException(
response.statusCode,
response.responseHeaders,
@ -76,73 +74,10 @@ class WebDavClient {
.where((final part) => part.isNotEmpty)
.join('/');
/// returns the WebDAV capabilities of the server
Future<WebDavStatus> status() async {
final response = await _send(
'OPTIONS',
_constructPath(),
[200],
);
final davCapabilities = response.headers['dav']?.cast<String>().first ?? '';
final davSearchCapabilities = response.headers['dasl']?.cast<String>().first ?? '';
return WebDavStatus(
davCapabilities.split(',').map((final e) => e.trim()).where((final e) => e.isNotEmpty).toSet(),
davSearchCapabilities.split(',').map((final e) => e.trim()).where((final e) => e.isNotEmpty).toSet(),
);
}
/// make a dir with [remotePath] under current dir
Future<HttpClientResponse> mkdir(
final String remotePath, {
final bool safe = true,
}) async {
final expectedCodes = [
201,
if (safe) ...[
301,
405,
],
];
return _send(
'MKCOL',
_constructPath(remotePath),
expectedCodes,
);
}
/// just like mkdir -p
Future<HttpClientResponse?> mkdirs(
final String remotePath, {
final bool safe = true,
}) async {
final dirs = remotePath.trim().split('/')..removeWhere((final value) => value == '');
if (dirs.isEmpty) {
return null;
}
if (remotePath.trim().startsWith('/')) {
dirs[0] = '/${dirs[0]}'; // coverage:ignore-line
}
final prevPath = StringBuffer();
late HttpClientResponse response;
for (final dir in dirs) {
response = await mkdir(
'$prevPath/$dir',
safe: safe,
);
prevPath.write('/$dir');
}
return response;
}
/// remove dir with given [path]
Future<HttpClientResponse> delete(final String path) => _send(
'DELETE',
_constructPath(path),
[204],
);
Future<WebDavMultistatus> _parseResponse(final HttpClientResponse response) async =>
WebDavMultistatus.fromXmlElement(xml.XmlDocument.parse(await response.body).rootElement);
Map<String, String>? _generateUploadHeaders({
Map<String, String>? _getUploadHeaders({
required final DateTime? lastModified,
required final DateTime? created,
required final int? contentLength,
@ -161,135 +96,168 @@ class WebDavClient {
return headers.isNotEmpty ? headers : null;
}
/// upload a new file with [localData] as content to [remotePath]
Future<HttpClientResponse> upload(
/// Gets the WebDAV capabilities of the server.
Future<WebDavOptions> options() async {
final response = await _send(
'OPTIONS',
_constructPath(),
);
final davCapabilities = response.headers['dav']?.cast<String>().first ?? '';
final davSearchCapabilities = response.headers['dasl']?.cast<String>().first ?? '';
return WebDavOptions(
davCapabilities.split(',').map((final e) => e.trim()).where((final e) => e.isNotEmpty).toSet(),
davSearchCapabilities.split(',').map((final e) => e.trim()).where((final e) => e.isNotEmpty).toSet(),
);
}
/// Creates a collection at [path].
///
/// See http://www.webdav.org/specs/rfc2518.html#METHOD_MKCOL for more information.
Future<HttpClientResponse> mkcol(final String path) async => _send(
'MKCOL',
_constructPath(path),
);
/// Deletes the resource at [path].
///
/// See http://www.webdav.org/specs/rfc2518.html#METHOD_DELETE for more information.
Future<HttpClientResponse> delete(final String path) => _send(
'DELETE',
_constructPath(path),
);
/// Puts a new file at [path] with [localData] as content.
///
/// [lastModified] sets the date when the file was last modified on the server.
/// [created] sets the date when the file was created on the server.
/// See http://www.webdav.org/specs/rfc2518.html#METHOD_PUT for more information.
Future<HttpClientResponse> put(
final Uint8List localData,
final String remotePath, {
final String path, {
final DateTime? lastModified,
final DateTime? created,
}) =>
uploadStream(
putStream(
Stream.value(localData),
remotePath,
path,
lastModified: lastModified,
created: created,
contentLength: localData.lengthInBytes,
);
/// upload a new file with [localData] as content to [remotePath]
Future<HttpClientResponse> uploadStream(
/// Puts a new file at [path] with [localData] as content.
///
/// [lastModified] sets the date when the file was last modified on the server.
/// [created] sets the date when the file was created on the server.
/// [contentLength] sets the length of the [localData] that is uploaded.
/// See http://www.webdav.org/specs/rfc2518.html#METHOD_PUT for more information.
Future<HttpClientResponse> putStream(
final Stream<Uint8List> localData,
final String remotePath, {
final String path, {
final DateTime? lastModified,
final DateTime? created,
final int? contentLength,
}) async =>
_send(
'PUT',
_constructPath(remotePath),
[200, 201, 204],
_constructPath(path),
data: localData,
headers: _generateUploadHeaders(
headers: _getUploadHeaders(
lastModified: lastModified,
created: created,
contentLength: contentLength,
),
);
/// download [remotePath] and store the response file contents to String
Future<Uint8List> download(final String remotePath) async => (await downloadStream(remotePath)).bodyBytes;
/// Gets the content of the file at [path].
Future<Uint8List> get(final String path) async => (await getStream(path)).bodyBytes;
/// download [remotePath] and store the response file contents to ByteStream
Future<HttpClientResponse> downloadStream(final String remotePath) async => _send(
/// Gets the content of the file at [path].
Future<HttpClientResponse> getStream(final String path) async => _send(
'GET',
_constructPath(remotePath),
[200],
_constructPath(path),
);
Future<WebDavMultistatus> _parseResponse(final HttpClientResponse response) async =>
WebDavMultistatus.fromXmlElement(xml.XmlDocument.parse(await response.body).rootElement);
/// list the directories and files under given [remotePath].
/// Retrieves the props for the resource at [path].
///
/// Optionally populates the given [prop]s on the returned files.
/// [depth] can be '0', '1' or 'infinity'.
Future<WebDavMultistatus> ls(
final String remotePath, {
final WebDavPropfindProp? prop,
/// See http://www.webdav.org/specs/rfc2518.html#METHOD_PROPFIND for more information.
Future<WebDavMultistatus> propfind(
final String path, {
final WebDavPropWithoutValues? prop,
final String? depth,
}) async {
assert(depth == null || ['0', '1', 'infinity'].contains(depth), 'Depth has to be 0, 1 or infinity');
final response = await _send(
'PROPFIND',
_constructPath(remotePath),
[207, 301],
data: Stream.value(
Uint8List.fromList(
utf8.encode(
WebDavPropfind(prop: prop ?? WebDavPropfindProp()).toXmlElement(namespaces: namespaces).toXmlString(),
return _parseResponse(
await _send(
'PROPFIND',
_constructPath(path),
data: Stream.value(
Uint8List.fromList(
utf8.encode(
WebDavPropfind(prop: prop ?? WebDavPropWithoutValues())
.toXmlElement(namespaces: namespaces)
.toXmlString(),
),
),
),
),
headers: {
if (depth != null) ...{
'Depth': depth,
headers: {
if (depth != null) ...{
'Depth': depth,
},
},
},
),
);
if (response.statusCode == 301) {
// coverage:ignore-start
return ls(
response.headers['location']!.first,
prop: prop,
depth: depth,
);
// coverage:ignore-end
}
return _parseResponse(response);
}
/// Runs the filter-files report with the given [filterRules] on the
/// [remotePath].
/// Runs the filter-files report with the [filterRules] on the resource at [path].
///
/// Optionally populates the given [prop]s on the returned files.
Future<WebDavMultistatus> filter(
final String remotePath,
/// Optionally populates the [prop]s on the returned files.
/// See https://github.com/owncloud/docs/issues/359 for more information.
Future<WebDavMultistatus> report(
final String path,
final WebDavOcFilterRules filterRules, {
final WebDavPropfindProp? prop,
}) async {
final response = await _send(
'REPORT',
_constructPath(remotePath),
[200, 207],
data: Stream.value(
Uint8List.fromList(
utf8.encode(
WebDavOcFilterFiles(
filterRules: filterRules,
prop: prop ?? WebDavPropfindProp(), // coverage:ignore-line
).toXmlElement(namespaces: namespaces).toXmlString(),
final WebDavPropWithoutValues? prop,
}) async =>
_parseResponse(
await _send(
'REPORT',
_constructPath(path),
data: Stream.value(
Uint8List.fromList(
utf8.encode(
WebDavOcFilterFiles(
filterRules: filterRules,
prop: prop ?? WebDavPropWithoutValues(), // coverage:ignore-line
).toXmlElement(namespaces: namespaces).toXmlString(),
),
),
),
),
),
);
return _parseResponse(response);
}
);
/// Update (string) properties of the given [remotePath].
/// Updates the props of the resource at [path].
///
/// The props in [set] will be updated.
/// The props in [remove] will be removed.
/// Returns true if the update was successful.
Future<bool> updateProps(
final String remotePath,
final WebDavProp prop,
) async {
/// See http://www.webdav.org/specs/rfc2518.html#METHOD_PROPPATCH for more information.
Future<bool> proppatch(
final String path, {
final WebDavProp? set,
final WebDavPropWithoutValues? remove,
}) async {
final response = await _send(
'PROPPATCH',
_constructPath(remotePath),
[200, 207],
_constructPath(path),
data: Stream.value(
Uint8List.fromList(
utf8.encode(
WebDavPropertyupdate(set: WebDavSet(prop: prop)).toXmlElement(namespaces: namespaces).toXmlString(),
WebDavPropertyupdate(
set: set != null ? WebDavSet(prop: set) : null,
remove: remove != null ? WebDavRemove(prop: remove) : null,
).toXmlElement(namespaces: namespaces).toXmlString(),
),
),
),
@ -305,7 +273,10 @@ class WebDavClient {
return true;
}
/// Move a file from [sourcePath] to [destinationPath]
/// Moves the resource from [sourcePath] to [destinationPath].
///
/// If [overwrite] is set any existing resource will be replaced.
/// See http://www.webdav.org/specs/rfc2518.html#METHOD_MOVE for more information.
Future<HttpClientResponse> move(
final String sourcePath,
final String destinationPath, {
@ -314,14 +285,16 @@ class WebDavClient {
_send(
'MOVE',
_constructPath(sourcePath),
[200, 201, 204],
headers: {
'Destination': _constructPath(destinationPath),
'Overwrite': overwrite ? 'T' : 'F',
},
);
/// Copy a file from [sourcePath] to [destinationPath]
/// Copies the resource from [sourcePath] to [destinationPath].
///
/// If [overwrite] is set any existing resource will be replaced.
/// See http://www.webdav.org/specs/rfc2518.html#METHOD_COPY for more information.
Future<HttpClientResponse> copy(
final String sourcePath,
final String destinationPath, {
@ -330,7 +303,6 @@ class WebDavClient {
_send(
'COPY',
_constructPath(sourcePath),
[200, 201, 204],
headers: {
'Destination': _constructPath(destinationPath),
'Overwrite': overwrite ? 'T' : 'F',
@ -338,10 +310,10 @@ class WebDavClient {
);
}
/// WebDAV server status.
class WebDavStatus {
/// WebDAV capabilities
class WebDavOptions {
/// Creates a new WebDavStatus.
WebDavStatus(
WebDavOptions(
this.capabilities,
this.searchCapabilities,
);
@ -349,7 +321,6 @@ class WebDavStatus {
/// DAV capabilities as advertised by the server in the 'dav' header.
Set<String> capabilities;
/// DAV search and locating capabilities as advertised by the server in the
/// 'dasl' header.
/// DAV search and locating capabilities as advertised by the server in the 'dasl' header.
Set<String> searchCapabilities;
}

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

@ -7,8 +7,8 @@ part 'props.g.dart';
@annotation.XmlSerializable(createMixin: true)
@annotation.XmlRootElement(name: 'prop', namespace: namespaceDav)
class WebDavPropfindProp with _$WebDavPropfindPropXmlSerializableMixin {
WebDavPropfindProp({
class WebDavPropWithoutValues with _$WebDavPropWithoutValuesXmlSerializableMixin {
WebDavPropWithoutValues({
this.davgetlastmodified,
this.davgetetag,
this.davgetcontenttype,
@ -38,7 +38,7 @@ class WebDavPropfindProp with _$WebDavPropfindPropXmlSerializableMixin {
this.ocmsharepermissions,
});
WebDavPropfindProp.fromBools({
WebDavPropWithoutValues.fromBools({
final bool davgetlastmodified = false,
final bool davgetetag = false,
final bool davgetcontenttype = false,
@ -94,7 +94,8 @@ class WebDavPropfindProp with _$WebDavPropfindPropXmlSerializableMixin {
ocssharepermissions = ocssharepermissions ? [null] : null,
ocmsharepermissions = ocmsharepermissions ? [null] : null;
factory WebDavPropfindProp.fromXmlElement(final XmlElement element) => _$WebDavPropfindPropFromXmlElement(element);
factory WebDavPropWithoutValues.fromXmlElement(final XmlElement element) =>
_$WebDavPropWithoutValuesFromXmlElement(element);
@annotation.XmlElement(
name: 'getlastmodified',

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

@ -6,7 +6,7 @@ part of 'props.dart';
// XmlSerializableGenerator
// **************************************************************************
void _$WebDavPropfindPropBuildXmlChildren(WebDavPropfindProp instance, XmlBuilder builder,
void _$WebDavPropWithoutValuesBuildXmlChildren(WebDavPropWithoutValues instance, XmlBuilder builder,
{Map<String, String> namespaces = const {}}) {
final davgetlastmodified = instance.davgetlastmodified;
final davgetlastmodifiedSerialized = davgetlastmodified;
@ -308,14 +308,14 @@ void _$WebDavPropfindPropBuildXmlChildren(WebDavPropfindProp instance, XmlBuilde
}
}
void _$WebDavPropfindPropBuildXmlElement(WebDavPropfindProp instance, XmlBuilder builder,
void _$WebDavPropWithoutValuesBuildXmlElement(WebDavPropWithoutValues instance, XmlBuilder builder,
{Map<String, String> namespaces = const {}}) {
builder.element('prop', namespace: 'DAV:', namespaces: namespaces, nest: () {
instance.buildXmlChildren(builder, namespaces: namespaces);
});
}
WebDavPropfindProp _$WebDavPropfindPropFromXmlElement(XmlElement element) {
WebDavPropWithoutValues _$WebDavPropWithoutValuesFromXmlElement(XmlElement element) {
final davgetlastmodified =
element.getElements('getlastmodified', namespace: 'DAV:')?.map((e) => e.getText()).whereType<String>();
final davgetetag = element.getElements('getetag', namespace: 'DAV:')?.map((e) => e.getText()).whereType<String>();
@ -401,7 +401,7 @@ WebDavPropfindProp _$WebDavPropfindPropFromXmlElement(XmlElement element) {
.getElements('share-permissions', namespace: 'http://open-cloud-mesh.org/ns')
?.map((e) => e.getText())
.whereType<String>();
return WebDavPropfindProp(
return WebDavPropWithoutValues(
davgetlastmodified: davgetlastmodified?.toList(),
davgetetag: davgetetag?.toList(),
davgetcontenttype: davgetcontenttype?.toList(),
@ -431,13 +431,13 @@ WebDavPropfindProp _$WebDavPropfindPropFromXmlElement(XmlElement element) {
ocmsharepermissions: ocmsharepermissions?.toList());
}
List<XmlAttribute> _$WebDavPropfindPropToXmlAttributes(WebDavPropfindProp instance,
List<XmlAttribute> _$WebDavPropWithoutValuesToXmlAttributes(WebDavPropWithoutValues instance,
{Map<String, String?> namespaces = const {}}) {
final attributes = <XmlAttribute>[];
return attributes;
}
List<XmlNode> _$WebDavPropfindPropToXmlChildren(WebDavPropfindProp instance,
List<XmlNode> _$WebDavPropWithoutValuesToXmlChildren(WebDavPropWithoutValues instance,
{Map<String, String?> namespaces = const {}}) {
final children = <XmlNode>[];
final davgetlastmodified = instance.davgetlastmodified;
@ -638,28 +638,29 @@ List<XmlNode> _$WebDavPropfindPropToXmlChildren(WebDavPropfindProp instance,
return children;
}
XmlElement _$WebDavPropfindPropToXmlElement(WebDavPropfindProp instance, {Map<String, String?> namespaces = const {}}) {
XmlElement _$WebDavPropWithoutValuesToXmlElement(WebDavPropWithoutValues instance,
{Map<String, String?> namespaces = const {}}) {
return XmlElement(
XmlName('prop', namespaces['DAV:']),
[...namespaces.toXmlAttributes(), ...instance.toXmlAttributes(namespaces: namespaces)],
instance.toXmlChildren(namespaces: namespaces));
}
mixin _$WebDavPropfindPropXmlSerializableMixin {
mixin _$WebDavPropWithoutValuesXmlSerializableMixin {
void buildXmlChildren(XmlBuilder builder, {Map<String, String> namespaces = const {}}) =>
_$WebDavPropfindPropBuildXmlChildren(this as WebDavPropfindProp, builder, namespaces: namespaces);
_$WebDavPropWithoutValuesBuildXmlChildren(this as WebDavPropWithoutValues, builder, namespaces: namespaces);
void buildXmlElement(XmlBuilder builder, {Map<String, String> namespaces = const {}}) =>
_$WebDavPropfindPropBuildXmlElement(this as WebDavPropfindProp, builder, namespaces: namespaces);
_$WebDavPropWithoutValuesBuildXmlElement(this as WebDavPropWithoutValues, builder, namespaces: namespaces);
List<XmlAttribute> toXmlAttributes({Map<String, String?> namespaces = const {}}) =>
_$WebDavPropfindPropToXmlAttributes(this as WebDavPropfindProp, namespaces: namespaces);
_$WebDavPropWithoutValuesToXmlAttributes(this as WebDavPropWithoutValues, namespaces: namespaces);
List<XmlNode> toXmlChildren({Map<String, String?> namespaces = const {}}) =>
_$WebDavPropfindPropToXmlChildren(this as WebDavPropfindProp, namespaces: namespaces);
_$WebDavPropWithoutValuesToXmlChildren(this as WebDavPropWithoutValues, namespaces: namespaces);
XmlElement toXmlElement({Map<String, String?> namespaces = const {}}) =>
_$WebDavPropfindPropToXmlElement(this as WebDavPropfindProp, namespaces: namespaces);
_$WebDavPropWithoutValuesToXmlElement(this as WebDavPropWithoutValues, namespaces: namespaces);
}
void _$WebDavPropBuildXmlChildren(WebDavProp instance, XmlBuilder builder,

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

@ -76,11 +76,15 @@ class WebDavPropstat with _$WebDavPropstatXmlSerializableMixin {
@annotation.XmlRootElement(name: 'propertyupdate', namespace: namespaceDav)
class WebDavPropertyupdate with _$WebDavPropertyupdateXmlSerializableMixin {
WebDavPropertyupdate({
required this.set,
this.set,
this.remove,
});
@annotation.XmlElement(name: 'set', namespace: namespaceDav)
final WebDavSet set;
@annotation.XmlElement(name: 'set', namespace: namespaceDav, includeIfNull: false)
final WebDavSet? set;
@annotation.XmlElement(name: 'remove', namespace: namespaceDav, includeIfNull: false)
final WebDavRemove? remove;
}
@annotation.XmlSerializable(createMixin: true)
@ -96,6 +100,19 @@ class WebDavSet with _$WebDavSetXmlSerializableMixin {
final WebDavProp prop; // coverage:ignore-line
}
@annotation.XmlSerializable(createMixin: true)
@annotation.XmlRootElement(name: 'remove', namespace: namespaceDav)
class WebDavRemove with _$WebDavRemoveXmlSerializableMixin {
WebDavRemove({
required this.prop,
});
factory WebDavRemove.fromXmlElement(final XmlElement element) => _$WebDavRemoveFromXmlElement(element);
@annotation.XmlElement(name: 'prop', namespace: namespaceDav)
final WebDavPropWithoutValues prop; // coverage:ignore-line
}
@annotation.XmlSerializable(createMixin: true)
@annotation.XmlRootElement(name: 'propfind', namespace: namespaceDav)
class WebDavPropfind with _$WebDavPropfindXmlSerializableMixin {
@ -104,7 +121,7 @@ class WebDavPropfind with _$WebDavPropfindXmlSerializableMixin {
});
@annotation.XmlElement(name: 'prop', namespace: namespaceDav)
final WebDavPropfindProp prop;
final WebDavPropWithoutValues prop;
}
@annotation.XmlSerializable(createMixin: true)
@ -119,7 +136,7 @@ class WebDavOcFilterFiles with _$WebDavOcFilterFilesXmlSerializableMixin {
final WebDavOcFilterRules filterRules;
@annotation.XmlElement(name: 'prop', namespace: namespaceDav)
final WebDavPropfindProp prop;
final WebDavPropWithoutValues prop;
}
@annotation.XmlSerializable(createMixin: true)

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

@ -221,9 +221,18 @@ void _$WebDavPropertyupdateBuildXmlChildren(WebDavPropertyupdate instance, XmlBu
{Map<String, String> namespaces = const {}}) {
final set = instance.set;
final setSerialized = set;
builder.element('set', namespace: 'DAV:', nest: () {
setSerialized.buildXmlChildren(builder, namespaces: namespaces);
});
if (setSerialized != null) {
builder.element('set', namespace: 'DAV:', nest: () {
setSerialized.buildXmlChildren(builder, namespaces: namespaces);
});
}
final remove = instance.remove;
final removeSerialized = remove;
if (removeSerialized != null) {
builder.element('remove', namespace: 'DAV:', nest: () {
removeSerialized.buildXmlChildren(builder, namespaces: namespaces);
});
}
}
void _$WebDavPropertyupdateBuildXmlElement(WebDavPropertyupdate instance, XmlBuilder builder,
@ -234,8 +243,11 @@ void _$WebDavPropertyupdateBuildXmlElement(WebDavPropertyupdate instance, XmlBui
}
WebDavPropertyupdate _$WebDavPropertyupdateFromXmlElement(XmlElement element) {
final set = element.getElement('set', namespace: 'DAV:')!;
return WebDavPropertyupdate(set: WebDavSet.fromXmlElement(set));
final set = element.getElement('set', namespace: 'DAV:');
final remove = element.getElement('remove', namespace: 'DAV:');
return WebDavPropertyupdate(
set: set != null ? WebDavSet.fromXmlElement(set) : null,
remove: remove != null ? WebDavRemove.fromXmlElement(remove) : null);
}
List<XmlAttribute> _$WebDavPropertyupdateToXmlAttributes(WebDavPropertyupdate instance,
@ -249,9 +261,22 @@ List<XmlNode> _$WebDavPropertyupdateToXmlChildren(WebDavPropertyupdate instance,
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);
final setConstructed = setSerialized != null
? XmlElement(XmlName('set', namespaces['DAV:']), setSerialized.toXmlAttributes(namespaces: namespaces),
setSerialized.toXmlChildren(namespaces: namespaces))
: null;
if (setConstructed != null) {
children.add(setConstructed);
}
final remove = instance.remove;
final removeSerialized = remove;
final removeConstructed = removeSerialized != null
? XmlElement(XmlName('remove', namespaces['DAV:']), removeSerialized.toXmlAttributes(namespaces: namespaces),
removeSerialized.toXmlChildren(namespaces: namespaces))
: null;
if (removeConstructed != null) {
children.add(removeConstructed);
}
return children;
}
@ -338,6 +363,66 @@ mixin _$WebDavSetXmlSerializableMixin {
_$WebDavSetToXmlElement(this as WebDavSet, namespaces: namespaces);
}
void _$WebDavRemoveBuildXmlChildren(WebDavRemove 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 _$WebDavRemoveBuildXmlElement(WebDavRemove instance, XmlBuilder builder,
{Map<String, String> namespaces = const {}}) {
builder.element('remove', namespace: 'DAV:', namespaces: namespaces, nest: () {
instance.buildXmlChildren(builder, namespaces: namespaces);
});
}
WebDavRemove _$WebDavRemoveFromXmlElement(XmlElement element) {
final prop = element.getElement('prop', namespace: 'DAV:')!;
return WebDavRemove(prop: WebDavPropWithoutValues.fromXmlElement(prop));
}
List<XmlAttribute> _$WebDavRemoveToXmlAttributes(WebDavRemove instance, {Map<String, String?> namespaces = const {}}) {
final attributes = <XmlAttribute>[];
return attributes;
}
List<XmlNode> _$WebDavRemoveToXmlChildren(WebDavRemove 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 _$WebDavRemoveToXmlElement(WebDavRemove instance, {Map<String, String?> namespaces = const {}}) {
return XmlElement(
XmlName('remove', namespaces['DAV:']),
[...namespaces.toXmlAttributes(), ...instance.toXmlAttributes(namespaces: namespaces)],
instance.toXmlChildren(namespaces: namespaces));
}
mixin _$WebDavRemoveXmlSerializableMixin {
void buildXmlChildren(XmlBuilder builder, {Map<String, String> namespaces = const {}}) =>
_$WebDavRemoveBuildXmlChildren(this as WebDavRemove, builder, namespaces: namespaces);
void buildXmlElement(XmlBuilder builder, {Map<String, String> namespaces = const {}}) =>
_$WebDavRemoveBuildXmlElement(this as WebDavRemove, builder, namespaces: namespaces);
List<XmlAttribute> toXmlAttributes({Map<String, String?> namespaces = const {}}) =>
_$WebDavRemoveToXmlAttributes(this as WebDavRemove, namespaces: namespaces);
List<XmlNode> toXmlChildren({Map<String, String?> namespaces = const {}}) =>
_$WebDavRemoveToXmlChildren(this as WebDavRemove, namespaces: namespaces);
XmlElement toXmlElement({Map<String, String?> namespaces = const {}}) =>
_$WebDavRemoveToXmlElement(this as WebDavRemove, namespaces: namespaces);
}
void _$WebDavPropfindBuildXmlChildren(WebDavPropfind instance, XmlBuilder builder,
{Map<String, String> namespaces = const {}}) {
final prop = instance.prop;
@ -356,7 +441,7 @@ void _$WebDavPropfindBuildXmlElement(WebDavPropfind instance, XmlBuilder builder
WebDavPropfind _$WebDavPropfindFromXmlElement(XmlElement element) {
final prop = element.getElement('prop', namespace: 'DAV:')!;
return WebDavPropfind(prop: WebDavPropfindProp.fromXmlElement(prop));
return WebDavPropfind(prop: WebDavPropWithoutValues.fromXmlElement(prop));
}
List<XmlAttribute> _$WebDavPropfindToXmlAttributes(WebDavPropfind instance,
@ -424,7 +509,7 @@ 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));
filterRules: WebDavOcFilterRules.fromXmlElement(filterRules), prop: WebDavPropWithoutValues.fromXmlElement(prop));
}
List<XmlAttribute> _$WebDavOcFilterFilesToXmlAttributes(WebDavOcFilterFiles instance,

141
packages/nextcloud/test/webdav_test.dart

@ -24,15 +24,15 @@ void main() {
tearDown(() => container.destroy());
test('Get status', () async {
final status = await client.webdav.status();
expect(status.capabilities, containsAll(['1', '3']));
expect(status.searchCapabilities, hasLength(0));
final options = await client.webdav.options();
expect(options.capabilities, containsAll(['1', '3']));
expect(options.searchCapabilities, hasLength(0));
});
test('List directory', () async {
final responses = (await client.webdav.ls(
final responses = (await client.webdav.propfind(
'/',
prop: WebDavPropfindProp.fromBools(
prop: WebDavPropWithoutValues.fromBools(
nchaspreview: true,
davgetcontenttype: true,
davgetlastmodified: true,
@ -50,7 +50,7 @@ void main() {
});
test('List directory recursively', () async {
final responses = (await client.webdav.ls(
final responses = (await client.webdav.propfind(
'/',
depth: 'infinity',
))
@ -59,32 +59,23 @@ void main() {
});
test('Create directory', () async {
final response = await client.webdav.mkdir('test');
final response = await client.webdav.mkcol('test');
expect(response.statusCode, equals(201));
});
test('Create directory recursively', () async {
final response = await client.webdav.mkdirs('test/bla');
expect(response!.statusCode, equals(201));
final responses = (await client.webdav.ls('/test')).responses;
expect(responses, hasLength(2));
expect(responses[1].href, endsWith('/test/bla/'));
});
test('Upload files', () async {
final pngBytes = File('test/files/test.png').readAsBytesSync();
final txtBytes = File('test/files/test.txt').readAsBytesSync();
var response = await client.webdav.upload(pngBytes, 'test.png');
var response = await client.webdav.put(pngBytes, 'test.png');
expect(response.statusCode, equals(201));
response = await client.webdav.upload(txtBytes, 'test.txt');
response = await client.webdav.put(txtBytes, 'test.txt');
expect(response.statusCode, equals(201));
final responses = (await client.webdav.ls(
final responses = (await client.webdav.propfind(
'/',
prop: WebDavPropfindProp.fromBools(
prop: WebDavPropWithoutValues.fromBools(
ocsize: true,
),
))
@ -105,7 +96,7 @@ void main() {
final created = lastModified.subtract(const Duration(hours: 1));
final txtBytes = File('test/files/test.txt').readAsBytesSync();
final response = await client.webdav.upload(
final response = await client.webdav.put(
txtBytes,
'test.txt',
lastModified: lastModified,
@ -113,9 +104,9 @@ void main() {
);
expect(response.statusCode, equals(201));
final props = (await client.webdav.ls(
final props = (await client.webdav.propfind(
'/',
prop: WebDavPropfindProp.fromBools(
prop: WebDavPropWithoutValues.fromBools(
davgetlastmodified: true,
nccreationtime: true,
),
@ -136,14 +127,14 @@ void main() {
});
test('Download file', () async {
final response = await client.webdav.download('Nextcloud.png');
final response = await client.webdav.get('Nextcloud.png');
expect(response, isNotEmpty);
});
test('Delete file', () async {
final response = await client.webdav.delete('Nextcloud.png');
expect(response.statusCode, 204);
final responses = (await client.webdav.ls('/')).responses;
final responses = (await client.webdav.propfind('/')).responses;
expect(responses.where((final response) => response.href!.endsWith('/Nextcloud.png')), hasLength(0));
});
@ -153,14 +144,14 @@ void main() {
'test.png',
);
expect(response.statusCode, 201);
final responses = (await client.webdav.ls('/')).responses;
final responses = (await client.webdav.propfind('/')).responses;
expect(responses.where((final response) => response.href!.endsWith('/Nextcloud.png')), hasLength(1));
expect(responses.where((final response) => response.href!.endsWith('/test.png')), hasLength(1));
});
test('Copy file (overwrite fail)', () async {
await client.webdav.upload(Uint8List.fromList(utf8.encode('1')), '1.txt');
await client.webdav.upload(Uint8List.fromList(utf8.encode('2')), '2.txt');
await client.webdav.put(Uint8List.fromList(utf8.encode('1')), '1.txt');
await client.webdav.put(Uint8List.fromList(utf8.encode('2')), '2.txt');
expect(
() => client.webdav.copy('1.txt', '2.txt'),
@ -169,8 +160,8 @@ void main() {
});
test('Copy file (overwrite success)', () async {
await client.webdav.upload(Uint8List.fromList(utf8.encode('1')), '1.txt');
await client.webdav.upload(Uint8List.fromList(utf8.encode('2')), '2.txt');
await client.webdav.put(Uint8List.fromList(utf8.encode('1')), '1.txt');
await client.webdav.put(Uint8List.fromList(utf8.encode('2')), '2.txt');
final response = await client.webdav.copy(
'1.txt',
@ -186,14 +177,14 @@ void main() {
'test.png',
);
expect(response.statusCode, 201);
final responses = (await client.webdav.ls('/')).responses;
final responses = (await client.webdav.propfind('/')).responses;
expect(responses.where((final response) => response.href!.endsWith('/Nextcloud.png')), hasLength(0));
expect(responses.where((final response) => response.href!.endsWith('/test.png')), hasLength(1));
});
test('Move file (overwrite fail)', () async {
await client.webdav.upload(Uint8List.fromList(utf8.encode('1')), '1.txt');
await client.webdav.upload(Uint8List.fromList(utf8.encode('2')), '2.txt');
await client.webdav.put(Uint8List.fromList(utf8.encode('1')), '1.txt');
await client.webdav.put(Uint8List.fromList(utf8.encode('2')), '2.txt');
expect(
() => client.webdav.move('1.txt', '2.txt'),
@ -202,8 +193,8 @@ void main() {
});
test('Move file (overwrite success)', () async {
await client.webdav.upload(Uint8List.fromList(utf8.encode('1')), '1.txt');
await client.webdav.upload(Uint8List.fromList(utf8.encode('2')), '2.txt');
await client.webdav.put(Uint8List.fromList(utf8.encode('1')), '1.txt');
await client.webdav.put(Uint8List.fromList(utf8.encode('2')), '2.txt');
final response = await client.webdav.move(
'1.txt',
@ -214,9 +205,9 @@ void main() {
});
test('Get file props', () async {
final response = (await client.webdav.ls(
final response = (await client.webdav.propfind(
'Nextcloud.png',
prop: WebDavPropfindProp.fromBools(
prop: WebDavPropWithoutValues.fromBools(
davgetlastmodified: true,
davgetetag: true,
davgetcontenttype: true,
@ -298,12 +289,12 @@ void main() {
test('Get directory props', () async {
final data = Uint8List.fromList(utf8.encode('test'));
await client.webdav.mkdir('test');
await client.webdav.upload(data, 'test/test.txt');
await client.webdav.mkcol('test');
await client.webdav.put(data, 'test/test.txt');
final response = (await client.webdav.ls(
final response = (await client.webdav.propfind(
'test',
prop: WebDavPropfindProp.fromBools(
prop: WebDavPropWithoutValues.fromBools(
davgetcontenttype: true,
davgetlastmodified: true,
davresourcetype: true,
@ -329,21 +320,21 @@ void main() {
});
test('Filter files', () async {
final response = await client.webdav.upload(Uint8List.fromList(utf8.encode('test')), 'test.txt');
final response = await client.webdav.put(Uint8List.fromList(utf8.encode('test')), 'test.txt');
final id = response.headers['oc-fileid']!.first;
await client.webdav.updateProps(
await client.webdav.proppatch(
'test.txt',
WebDavProp(
set: WebDavProp(
ocfavorite: 1,
),
);
final responses = (await client.webdav.filter(
final responses = (await client.webdav.report(
'/',
WebDavOcFilterRules(
ocfavorite: 1,
),
prop: WebDavPropfindProp.fromBools(
prop: WebDavPropWithoutValues.fromBools(
ocid: true,
ocfavorite: true,
),
@ -361,20 +352,20 @@ void main() {
final createdEpoch = createdDate.millisecondsSinceEpoch ~/ 1000;
final uploadTime = DateTime.now();
await client.webdav.upload(Uint8List.fromList(utf8.encode('test')), 'test.txt');
await client.webdav.put(Uint8List.fromList(utf8.encode('test')), 'test.txt');
final updated = await client.webdav.updateProps(
final updated = await client.webdav.proppatch(
'test.txt',
WebDavProp(
set: WebDavProp(
ocfavorite: 1,
nccreationtime: createdEpoch,
),
);
expect(updated, isTrue);
final props = (await client.webdav.ls(
final props = (await client.webdav.propfind(
'test.txt',
prop: WebDavPropfindProp.fromBools(
prop: WebDavPropWithoutValues.fromBools(
ocfavorite: true,
nccreationtime: true,
ncuploadtime: true,
@ -389,5 +380,53 @@ void main() {
expect(DateTime.fromMillisecondsSinceEpoch(props.nccreationtime! * 1000).isAtSameMomentAs(createdDate), isTrue);
expectDateInReasonableTimeRange(DateTime.fromMillisecondsSinceEpoch(props.ncuploadtime! * 1000), uploadTime);
});
test('Remove properties', () async {
await client.webdav.put(Uint8List.fromList(utf8.encode('test')), 'test.txt');
var updated = await client.webdav.proppatch(
'test.txt',
set: WebDavProp(
ocfavorite: 1,
),
);
expect(updated, isTrue);
var props = (await client.webdav.propfind(
'test.txt',
prop: WebDavPropWithoutValues.fromBools(
ocfavorite: true,
nccreationtime: true,
ncuploadtime: true,
),
))
.responses
.single
.propstats
.first
.prop;
expect(props.ocfavorite, 1);
updated = await client.webdav.proppatch(
'test.txt',
remove: WebDavPropWithoutValues.fromBools(
ocfavorite: true,
),
);
expect(updated, isFalse);
props = (await client.webdav.propfind(
'test.txt',
prop: WebDavPropWithoutValues.fromBools(
ocfavorite: true,
),
))
.responses
.single
.propstats
.first
.prop;
expect(props.ocfavorite, 0);
});
});
}

Loading…
Cancel
Save