diff --git a/packages/nextcloud/lib/src/webdav/client.dart b/packages/nextcloud/lib/src/webdav/client.dart index 40c407f4..9a99ad7f 100644 --- a/packages/nextcloud/lib/src/webdav/client.dart +++ b/packages/nextcloud/lib/src/webdav/client.dart @@ -266,12 +266,15 @@ class WebDavClient { /// 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. /// See http://www.webdav.org/specs/rfc2518.html#METHOD_PROPPATCH for more information. Future proppatch( - final String path, - final WebDavProp prop, - ) async { + final String path, { + final WebDavProp? set, + final WebDavPropWithoutValues? remove, + }) async { final response = await _send( 'PROPPATCH', _constructPath(path), @@ -279,7 +282,10 @@ class WebDavClient { 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(), ), ), ), diff --git a/packages/nextcloud/lib/src/webdav/webdav.dart b/packages/nextcloud/lib/src/webdav/webdav.dart index 68ccc9b7..664cdb7d 100644 --- a/packages/nextcloud/lib/src/webdav/webdav.dart +++ b/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 { diff --git a/packages/nextcloud/lib/src/webdav/webdav.g.dart b/packages/nextcloud/lib/src/webdav/webdav.g.dart index 2e48e19e..85806c17 100644 --- a/packages/nextcloud/lib/src/webdav/webdav.g.dart +++ b/packages/nextcloud/lib/src/webdav/webdav.g.dart @@ -221,9 +221,18 @@ void _$WebDavPropertyupdateBuildXmlChildren(WebDavPropertyupdate instance, XmlBu {Map 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 _$WebDavPropertyupdateToXmlAttributes(WebDavPropertyupdate instance, @@ -249,9 +261,22 @@ List _$WebDavPropertyupdateToXmlChildren(WebDavPropertyupdate instance, final children = []; 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 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 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 _$WebDavRemoveToXmlAttributes(WebDavRemove instance, {Map namespaces = const {}}) { + final attributes = []; + return attributes; +} + +List _$WebDavRemoveToXmlChildren(WebDavRemove instance, {Map namespaces = const {}}) { + final children = []; + 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 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 namespaces = const {}}) => + _$WebDavRemoveBuildXmlChildren(this as WebDavRemove, builder, namespaces: namespaces); + + void buildXmlElement(XmlBuilder builder, {Map namespaces = const {}}) => + _$WebDavRemoveBuildXmlElement(this as WebDavRemove, builder, namespaces: namespaces); + + List toXmlAttributes({Map namespaces = const {}}) => + _$WebDavRemoveToXmlAttributes(this as WebDavRemove, namespaces: namespaces); + + List toXmlChildren({Map namespaces = const {}}) => + _$WebDavRemoveToXmlChildren(this as WebDavRemove, namespaces: namespaces); + + XmlElement toXmlElement({Map namespaces = const {}}) => + _$WebDavRemoveToXmlElement(this as WebDavRemove, namespaces: namespaces); +} + void _$WebDavPropfindBuildXmlChildren(WebDavPropfind instance, XmlBuilder builder, {Map namespaces = const {}}) { final prop = instance.prop; diff --git a/packages/nextcloud/test/webdav_test.dart b/packages/nextcloud/test/webdav_test.dart index 56f540e9..6c2f1b7a 100644 --- a/packages/nextcloud/test/webdav_test.dart +++ b/packages/nextcloud/test/webdav_test.dart @@ -324,7 +324,7 @@ void main() { final id = response.headers['oc-fileid']!.first; await client.webdav.proppatch( 'test.txt', - WebDavProp( + set: WebDavProp( ocfavorite: 1, ), ); @@ -356,7 +356,7 @@ void main() { final updated = await client.webdav.proppatch( 'test.txt', - WebDavProp( + set: WebDavProp( ocfavorite: 1, nccreationtime: createdEpoch, ), @@ -380,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); + }); }); }