Browse Source

refactor(nextcloud): Rename WebDAV methods to their HTTP methods and add documentation

pull/520/head
jld3103 1 year ago
parent
commit
6acd512cef
No known key found for this signature in database
GPG Key ID: 9062417B9E8EB7B3
  1. 139
      packages/nextcloud/lib/src/webdav/client.dart
  2. 75
      packages/nextcloud/test/webdav_test.dart

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

@ -76,8 +76,8 @@ class WebDavClient {
.where((final part) => part.isNotEmpty) .where((final part) => part.isNotEmpty)
.join('/'); .join('/');
/// returns the WebDAV capabilities of the server /// Gets the WebDAV capabilities of the server.
Future<WebDavStatus> status() async { Future<WebDavOptions> options() async {
final response = await _send( final response = await _send(
'OPTIONS', 'OPTIONS',
_constructPath(), _constructPath(),
@ -85,14 +85,16 @@ class WebDavClient {
); );
final davCapabilities = response.headers['dav']?.cast<String>().first ?? ''; final davCapabilities = response.headers['dav']?.cast<String>().first ?? '';
final davSearchCapabilities = response.headers['dasl']?.cast<String>().first ?? ''; final davSearchCapabilities = response.headers['dasl']?.cast<String>().first ?? '';
return WebDavStatus( return WebDavOptions(
davCapabilities.split(',').map((final e) => e.trim()).where((final e) => e.isNotEmpty).toSet(), 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(), davSearchCapabilities.split(',').map((final e) => e.trim()).where((final e) => e.isNotEmpty).toSet(),
); );
} }
/// make a dir with [path] under current dir /// Creates a collection at [path].
Future<HttpClientResponse> mkdir( ///
/// See http://www.webdav.org/specs/rfc2518.html#METHOD_MKCOL for more information.
Future<HttpClientResponse> mkcol(
final String path, { final String path, {
final bool safe = true, final bool safe = true,
}) async { }) async {
@ -110,32 +112,9 @@ class WebDavClient {
); );
} }
/// just like mkdir -p /// Deletes the resource at [path].
Future<HttpClientResponse?> mkdirs( ///
final String path, { /// See http://www.webdav.org/specs/rfc2518.html#METHOD_DELETE for more information.
final bool safe = true,
}) async {
final dirs = path.trim().split('/')..removeWhere((final value) => value == '');
if (dirs.isEmpty) {
return null;
}
if (path.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( Future<HttpClientResponse> delete(final String path) => _send(
'DELETE', 'DELETE',
_constructPath(path), _constructPath(path),
@ -161,14 +140,18 @@ class WebDavClient {
return headers.isNotEmpty ? headers : null; return headers.isNotEmpty ? headers : null;
} }
/// upload a new file with [localData] as content to [path] /// Puts a new file at [path] with [localData] as content.
Future<HttpClientResponse> upload( ///
/// [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 Uint8List localData,
final String path, { final String path, {
final DateTime? lastModified, final DateTime? lastModified,
final DateTime? created, final DateTime? created,
}) => }) =>
uploadStream( putStream(
Stream.value(localData), Stream.value(localData),
path, path,
lastModified: lastModified, lastModified: lastModified,
@ -176,8 +159,13 @@ class WebDavClient {
contentLength: localData.lengthInBytes, contentLength: localData.lengthInBytes,
); );
/// upload a new file with [localData] as content to [path] /// Puts a new file at [path] with [localData] as content.
Future<HttpClientResponse> uploadStream( ///
/// [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 Stream<Uint8List> localData,
final String path, { final String path, {
final DateTime? lastModified, final DateTime? lastModified,
@ -196,11 +184,11 @@ class WebDavClient {
), ),
); );
/// download [path] and store the response file contents to String /// Gets the content of the file at [path].
Future<Uint8List> download(final String path) async => (await downloadStream(path)).bodyBytes; Future<Uint8List> get(final String path) async => (await getStream(path)).bodyBytes;
/// download [path] and store the response file contents to ByteStream /// Gets the content of the file at [path].
Future<HttpClientResponse> downloadStream(final String path) async => _send( Future<HttpClientResponse> getStream(final String path) async => _send(
'GET', 'GET',
_constructPath(path), _constructPath(path),
[200], [200],
@ -209,11 +197,12 @@ class WebDavClient {
Future<WebDavMultistatus> _parseResponse(final HttpClientResponse response) async => Future<WebDavMultistatus> _parseResponse(final HttpClientResponse response) async =>
WebDavMultistatus.fromXmlElement(xml.XmlDocument.parse(await response.body).rootElement); WebDavMultistatus.fromXmlElement(xml.XmlDocument.parse(await response.body).rootElement);
/// list the directories and files under given [path]. /// Retrieves the props for the resource at [path].
/// ///
/// Optionally populates the given [prop]s on the returned files. /// Optionally populates the given [prop]s on the returned files.
/// [depth] can be '0', '1' or 'infinity'. /// [depth] can be '0', '1' or 'infinity'.
Future<WebDavMultistatus> ls( /// See http://www.webdav.org/specs/rfc2518.html#METHOD_PROPFIND for more information.
Future<WebDavMultistatus> propfind(
final String path, { final String path, {
final WebDavPropfindProp? prop, final WebDavPropfindProp? prop,
final String? depth, final String? depth,
@ -238,7 +227,7 @@ class WebDavClient {
); );
if (response.statusCode == 301) { if (response.statusCode == 301) {
// coverage:ignore-start // coverage:ignore-start
return ls( return propfind(
response.headers['location']!.first, response.headers['location']!.first,
prop: prop, prop: prop,
depth: depth, depth: depth,
@ -248,37 +237,38 @@ class WebDavClient {
return _parseResponse(response); return _parseResponse(response);
} }
/// Runs the filter-files report with the given [filterRules] on the /// Runs the filter-files report with the [filterRules] on the resource at [path].
/// [path].
/// ///
/// Optionally populates the given [prop]s on the returned files. /// Optionally populates the [prop]s on the returned files.
Future<WebDavMultistatus> filter( /// See https://github.com/owncloud/docs/issues/359 for more information.
Future<WebDavMultistatus> report(
final String path, final String path,
final WebDavOcFilterRules filterRules, { final WebDavOcFilterRules filterRules, {
final WebDavPropfindProp? prop, final WebDavPropfindProp? prop,
}) async { }) async =>
final response = await _send( _parseResponse(
'REPORT', await _send(
_constructPath(path), 'REPORT',
[200, 207], _constructPath(path),
data: Stream.value( [200, 207],
Uint8List.fromList( data: Stream.value(
utf8.encode( Uint8List.fromList(
WebDavOcFilterFiles( utf8.encode(
filterRules: filterRules, WebDavOcFilterFiles(
prop: prop ?? WebDavPropfindProp(), // coverage:ignore-line filterRules: filterRules,
).toXmlElement(namespaces: namespaces).toXmlString(), prop: prop ?? WebDavPropfindProp(), // coverage:ignore-line
).toXmlElement(namespaces: namespaces).toXmlString(),
),
),
), ),
), ),
), );
);
return _parseResponse(response);
}
/// Update (string) properties of the given [path]. /// Updates the props of the resource at [path].
/// ///
/// Returns true if the update was successful. /// Returns true if the update was successful.
Future<bool> updateProps( /// See http://www.webdav.org/specs/rfc2518.html#METHOD_PROPPATCH for more information.
Future<bool> proppatch(
final String path, final String path,
final WebDavProp prop, final WebDavProp prop,
) async { ) async {
@ -305,7 +295,10 @@ class WebDavClient {
return true; 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( Future<HttpClientResponse> move(
final String sourcePath, final String sourcePath,
final String destinationPath, { final String destinationPath, {
@ -321,7 +314,10 @@ class WebDavClient {
}, },
); );
/// 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( Future<HttpClientResponse> copy(
final String sourcePath, final String sourcePath,
final String destinationPath, { final String destinationPath, {
@ -338,10 +334,10 @@ class WebDavClient {
); );
} }
/// WebDAV server status. /// WebDAV capabilities
class WebDavStatus { class WebDavOptions {
/// Creates a new WebDavStatus. /// Creates a new WebDavStatus.
WebDavStatus( WebDavOptions(
this.capabilities, this.capabilities,
this.searchCapabilities, this.searchCapabilities,
); );
@ -349,7 +345,6 @@ class WebDavStatus {
/// DAV capabilities as advertised by the server in the 'dav' header. /// DAV capabilities as advertised by the server in the 'dav' header.
Set<String> capabilities; Set<String> capabilities;
/// DAV search and locating capabilities as advertised by the server in the /// DAV search and locating capabilities as advertised by the server in the 'dasl' header.
/// 'dasl' header.
Set<String> searchCapabilities; Set<String> searchCapabilities;
} }

75
packages/nextcloud/test/webdav_test.dart

@ -24,13 +24,13 @@ void main() {
tearDown(() => container.destroy()); tearDown(() => container.destroy());
test('Get status', () async { test('Get status', () async {
final status = await client.webdav.status(); final options = await client.webdav.options();
expect(status.capabilities, containsAll(['1', '3'])); expect(options.capabilities, containsAll(['1', '3']));
expect(status.searchCapabilities, hasLength(0)); expect(options.searchCapabilities, hasLength(0));
}); });
test('List directory', () async { test('List directory', () async {
final responses = (await client.webdav.ls( final responses = (await client.webdav.propfind(
'/', '/',
prop: WebDavPropfindProp.fromBools( prop: WebDavPropfindProp.fromBools(
nchaspreview: true, nchaspreview: true,
@ -50,7 +50,7 @@ void main() {
}); });
test('List directory recursively', () async { test('List directory recursively', () async {
final responses = (await client.webdav.ls( final responses = (await client.webdav.propfind(
'/', '/',
depth: 'infinity', depth: 'infinity',
)) ))
@ -59,30 +59,21 @@ void main() {
}); });
test('Create directory', () async { test('Create directory', () async {
final response = await client.webdav.mkdir('test'); final response = await client.webdav.mkcol('test');
expect(response.statusCode, equals(201)); 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 { test('Upload files', () async {
final pngBytes = File('test/files/test.png').readAsBytesSync(); final pngBytes = File('test/files/test.png').readAsBytesSync();
final txtBytes = File('test/files/test.txt').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)); 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)); expect(response.statusCode, equals(201));
final responses = (await client.webdav.ls( final responses = (await client.webdav.propfind(
'/', '/',
prop: WebDavPropfindProp.fromBools( prop: WebDavPropfindProp.fromBools(
ocsize: true, ocsize: true,
@ -105,7 +96,7 @@ void main() {
final created = lastModified.subtract(const Duration(hours: 1)); 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.put(
txtBytes, txtBytes,
'test.txt', 'test.txt',
lastModified: lastModified, lastModified: lastModified,
@ -113,7 +104,7 @@ void main() {
); );
expect(response.statusCode, equals(201)); expect(response.statusCode, equals(201));
final props = (await client.webdav.ls( final props = (await client.webdav.propfind(
'/', '/',
prop: WebDavPropfindProp.fromBools( prop: WebDavPropfindProp.fromBools(
davgetlastmodified: true, davgetlastmodified: true,
@ -136,14 +127,14 @@ void main() {
}); });
test('Download file', () async { test('Download file', () async {
final response = await client.webdav.download('Nextcloud.png'); final response = await client.webdav.get('Nextcloud.png');
expect(response, isNotEmpty); expect(response, isNotEmpty);
}); });
test('Delete file', () async { test('Delete file', () async {
final response = await client.webdav.delete('Nextcloud.png'); final response = await client.webdav.delete('Nextcloud.png');
expect(response.statusCode, 204); 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)); expect(responses.where((final response) => response.href!.endsWith('/Nextcloud.png')), hasLength(0));
}); });
@ -153,14 +144,14 @@ void main() {
'test.png', 'test.png',
); );
expect(response.statusCode, 201); 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('/Nextcloud.png')), hasLength(1));
expect(responses.where((final response) => response.href!.endsWith('/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 {
await client.webdav.upload(Uint8List.fromList(utf8.encode('1')), '1.txt'); await client.webdav.put(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('2')), '2.txt');
expect( expect(
() => client.webdav.copy('1.txt', '2.txt'), () => client.webdav.copy('1.txt', '2.txt'),
@ -169,8 +160,8 @@ void main() {
}); });
test('Copy file (overwrite success)', () async { test('Copy file (overwrite success)', () async {
await client.webdav.upload(Uint8List.fromList(utf8.encode('1')), '1.txt'); await client.webdav.put(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('2')), '2.txt');
final response = await client.webdav.copy( final response = await client.webdav.copy(
'1.txt', '1.txt',
@ -186,14 +177,14 @@ void main() {
'test.png', 'test.png',
); );
expect(response.statusCode, 201); 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('/Nextcloud.png')), hasLength(0));
expect(responses.where((final response) => response.href!.endsWith('/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 {
await client.webdav.upload(Uint8List.fromList(utf8.encode('1')), '1.txt'); await client.webdav.put(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('2')), '2.txt');
expect( expect(
() => client.webdav.move('1.txt', '2.txt'), () => client.webdav.move('1.txt', '2.txt'),
@ -202,8 +193,8 @@ void main() {
}); });
test('Move file (overwrite success)', () async { test('Move file (overwrite success)', () async {
await client.webdav.upload(Uint8List.fromList(utf8.encode('1')), '1.txt'); await client.webdav.put(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('2')), '2.txt');
final response = await client.webdav.move( final response = await client.webdav.move(
'1.txt', '1.txt',
@ -214,7 +205,7 @@ void main() {
}); });
test('Get file props', () async { test('Get file props', () async {
final response = (await client.webdav.ls( final response = (await client.webdav.propfind(
'Nextcloud.png', 'Nextcloud.png',
prop: WebDavPropfindProp.fromBools( prop: WebDavPropfindProp.fromBools(
davgetlastmodified: true, davgetlastmodified: true,
@ -298,10 +289,10 @@ void main() {
test('Get directory props', () async { test('Get directory props', () async {
final data = Uint8List.fromList(utf8.encode('test')); final data = Uint8List.fromList(utf8.encode('test'));
await client.webdav.mkdir('test'); await client.webdav.mkcol('test');
await client.webdav.upload(data, 'test/test.txt'); await client.webdav.put(data, 'test/test.txt');
final response = (await client.webdav.ls( final response = (await client.webdav.propfind(
'test', 'test',
prop: WebDavPropfindProp.fromBools( prop: WebDavPropfindProp.fromBools(
davgetcontenttype: true, davgetcontenttype: true,
@ -329,16 +320,16 @@ void main() {
}); });
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.put(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( await client.webdav.proppatch(
'test.txt', 'test.txt',
WebDavProp( WebDavProp(
ocfavorite: 1, ocfavorite: 1,
), ),
); );
final responses = (await client.webdav.filter( final responses = (await client.webdav.report(
'/', '/',
WebDavOcFilterRules( WebDavOcFilterRules(
ocfavorite: 1, ocfavorite: 1,
@ -361,9 +352,9 @@ void main() {
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.put(Uint8List.fromList(utf8.encode('test')), 'test.txt');
final updated = await client.webdav.updateProps( final updated = await client.webdav.proppatch(
'test.txt', 'test.txt',
WebDavProp( WebDavProp(
ocfavorite: 1, ocfavorite: 1,
@ -372,7 +363,7 @@ void main() {
); );
expect(updated, isTrue); expect(updated, isTrue);
final props = (await client.webdav.ls( final props = (await client.webdav.propfind(
'test.txt', 'test.txt',
prop: WebDavPropfindProp.fromBools( prop: WebDavPropfindProp.fromBools(
ocfavorite: true, ocfavorite: true,

Loading…
Cancel
Save