diff --git a/packages/nextcloud/lib/src/webdav/client.dart b/packages/nextcloud/lib/src/webdav/client.dart index 0673454e..4c736d5a 100644 --- a/packages/nextcloud/lib/src/webdav/client.dart +++ b/packages/nextcloud/lib/src/webdav/client.dart @@ -76,8 +76,8 @@ class WebDavClient { .where((final part) => part.isNotEmpty) .join('/'); - /// returns the WebDAV capabilities of the server - Future status() async { + /// Gets the WebDAV capabilities of the server. + Future options() async { final response = await _send( 'OPTIONS', _constructPath(), @@ -85,14 +85,16 @@ class WebDavClient { ); final davCapabilities = response.headers['dav']?.cast().first ?? ''; final davSearchCapabilities = response.headers['dasl']?.cast().first ?? ''; - return WebDavStatus( + 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(), ); } - /// make a dir with [path] under current dir - Future mkdir( + /// Creates a collection at [path]. + /// + /// See http://www.webdav.org/specs/rfc2518.html#METHOD_MKCOL for more information. + Future mkcol( final String path, { final bool safe = true, }) async { @@ -110,32 +112,9 @@ class WebDavClient { ); } - /// just like mkdir -p - Future mkdirs( - final String path, { - 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] + /// Deletes the resource at [path]. + /// + /// See http://www.webdav.org/specs/rfc2518.html#METHOD_DELETE for more information. Future delete(final String path) => _send( 'DELETE', _constructPath(path), @@ -161,14 +140,18 @@ class WebDavClient { return headers.isNotEmpty ? headers : null; } - /// upload a new file with [localData] as content to [path] - Future upload( + /// 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 put( final Uint8List localData, final String path, { final DateTime? lastModified, final DateTime? created, }) => - uploadStream( + putStream( Stream.value(localData), path, lastModified: lastModified, @@ -176,8 +159,13 @@ class WebDavClient { contentLength: localData.lengthInBytes, ); - /// upload a new file with [localData] as content to [path] - Future 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 putStream( final Stream localData, final String path, { final DateTime? lastModified, @@ -196,11 +184,11 @@ class WebDavClient { ), ); - /// download [path] and store the response file contents to String - Future download(final String path) async => (await downloadStream(path)).bodyBytes; + /// Gets the content of the file at [path]. + Future get(final String path) async => (await getStream(path)).bodyBytes; - /// download [path] and store the response file contents to ByteStream - Future downloadStream(final String path) async => _send( + /// Gets the content of the file at [path]. + Future getStream(final String path) async => _send( 'GET', _constructPath(path), [200], @@ -209,11 +197,12 @@ class WebDavClient { Future _parseResponse(final HttpClientResponse response) async => 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. /// [depth] can be '0', '1' or 'infinity'. - Future ls( + /// See http://www.webdav.org/specs/rfc2518.html#METHOD_PROPFIND for more information. + Future propfind( final String path, { final WebDavPropfindProp? prop, final String? depth, @@ -238,7 +227,7 @@ class WebDavClient { ); if (response.statusCode == 301) { // coverage:ignore-start - return ls( + return propfind( response.headers['location']!.first, prop: prop, depth: depth, @@ -248,37 +237,38 @@ class WebDavClient { return _parseResponse(response); } - /// Runs the filter-files report with the given [filterRules] on the - /// [path]. + /// Runs the filter-files report with the [filterRules] on the resource at [path]. /// - /// Optionally populates the given [prop]s on the returned files. - Future filter( + /// Optionally populates the [prop]s on the returned files. + /// See https://github.com/owncloud/docs/issues/359 for more information. + Future report( final String path, final WebDavOcFilterRules filterRules, { final WebDavPropfindProp? prop, - }) async { - final response = await _send( - 'REPORT', - _constructPath(path), - [200, 207], - data: Stream.value( - Uint8List.fromList( - utf8.encode( - WebDavOcFilterFiles( - filterRules: filterRules, - prop: prop ?? WebDavPropfindProp(), // coverage:ignore-line - ).toXmlElement(namespaces: namespaces).toXmlString(), + }) async => + _parseResponse( + await _send( + 'REPORT', + _constructPath(path), + [200, 207], + data: Stream.value( + Uint8List.fromList( + utf8.encode( + WebDavOcFilterFiles( + filterRules: filterRules, + 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. - Future updateProps( + /// See http://www.webdav.org/specs/rfc2518.html#METHOD_PROPPATCH for more information. + Future proppatch( final String path, final WebDavProp prop, ) async { @@ -305,7 +295,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 move( final String sourcePath, 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 copy( final String sourcePath, final String destinationPath, { @@ -338,10 +334,10 @@ class WebDavClient { ); } -/// WebDAV server status. -class WebDavStatus { +/// WebDAV capabilities +class WebDavOptions { /// Creates a new WebDavStatus. - WebDavStatus( + WebDavOptions( this.capabilities, this.searchCapabilities, ); @@ -349,7 +345,6 @@ class WebDavStatus { /// DAV capabilities as advertised by the server in the 'dav' header. Set 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 searchCapabilities; } diff --git a/packages/nextcloud/test/webdav_test.dart b/packages/nextcloud/test/webdav_test.dart index e737a55d..3762c8b9 100644 --- a/packages/nextcloud/test/webdav_test.dart +++ b/packages/nextcloud/test/webdav_test.dart @@ -24,13 +24,13 @@ 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( nchaspreview: 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,30 +59,21 @@ 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( 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,7 +104,7 @@ void main() { ); expect(response.statusCode, equals(201)); - final props = (await client.webdav.ls( + final props = (await client.webdav.propfind( '/', prop: WebDavPropfindProp.fromBools( davgetlastmodified: 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,7 +205,7 @@ void main() { }); test('Get file props', () async { - final response = (await client.webdav.ls( + final response = (await client.webdav.propfind( 'Nextcloud.png', prop: WebDavPropfindProp.fromBools( davgetlastmodified: true, @@ -298,10 +289,10 @@ 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( davgetcontenttype: true, @@ -329,16 +320,16 @@ 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( ocfavorite: 1, ), ); - final responses = (await client.webdav.filter( + final responses = (await client.webdav.report( '/', WebDavOcFilterRules( ocfavorite: 1, @@ -361,9 +352,9 @@ 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( ocfavorite: 1, @@ -372,7 +363,7 @@ void main() { ); expect(updated, isTrue); - final props = (await client.webdav.ls( + final props = (await client.webdav.propfind( 'test.txt', prop: WebDavPropfindProp.fromBools( ocfavorite: true,