simple_webdav_client 0.2.0 copy "simple_webdav_client: ^0.2.0" to clipboard
simple_webdav_client: ^0.2.0 copied to clipboard

A Dart WebDAV client with full RFC 4918 support, including file operations, property management, and locking (LOCK/UNLOCK). Ideal for these needing complete WebDAV functionality.

example/main.dart

// Copyright (c) 2024 Fries_I23
//
// This software is released under the MIT License.
// https://opensource.org/licenses/MIT
//
// Output (Example):
// -----------------------------------
// [Step1]: put new file at http://localhost/new-file.txt
// WebDavStdResponseResult{
//   // http://localhost/new-file.txt
//   WebDavStdResource{
//     path:http://localhost/new-file.txt | status:201,
//     props(0):
//   }
// }
//
//
// [Step2]: set custom prop on http://localhost/new-file.txt
// WebDavStdResponseResult{
//   // /new-file.txt
//   WebDavStdResource{
//     path:/new-file.txt | status:207,
//     props(1):
//       [custom:]author: WebDavStdResourceProp<String>{name:author,ns:custom:,status:200,value:},
//   }
// }
//
//
// [Step3]: get prop names from http://localhost/new-file.txt
// WebDavStdResponseResult{
//   // /new-file.txt
//   WebDavStdResource{
//     path:/new-file.txt | status:207,
//     props(9):
//       [custom:]author: WebDavStdResourceProp<String>{name:author,ns:custom:,status:200,value:},
//       [dav:]resourcetype: WebDavStdResourceProp<ResourceTypes>{name:resourcetype,ns:dav:,status:200,value:()},
//       [dav:]creationdate: WebDavStdResourceProp<DateTime>{name:creationdate,ns:dav:,status:200,value:null},
//       [dav:]getcontentlength: WebDavStdResourceProp<num>{name:getcontentlength,ns:dav:,status:200,value:null},
//       [dav:]getlastmodified: WebDavStdResourceProp<DateTime>{name:getlastmodified,ns:dav:,status:200,value:null},
//       [dav:]getetag: WebDavStdResourceProp<String>{name:getetag,ns:dav:,status:200,value:},
//       [dav:]supportedlock: WebDavStdResourceProp<List<LockEntry>>{name:supportedlock,ns:dav:,status:200,value:[]},
//       [dav:]lockdiscovery: WebDavStdResourceProp<List<ActiveLock<Null>>>{name:lockdiscovery,ns:dav:,status:200,value:[]},
//       [dav:]getcontenttype: WebDavStdResourceProp<ContentType>{name:getcontenttype,ns:dav:,status:200,value:null},
//   }
// }
//
//
// [Step4]: get props attributes from http://localhost/new-file.txt
// WebDavStdResponseResult{
//   // /new-file.txt
//   WebDavStdResource{
//     path:/new-file.txt | status:207,
//     props(9):
//       [custom:]author: WebDavStdResourceProp<String>{name:author,ns:custom:,status:200,value:zigzag},
//       [dav:]resourcetype: WebDavStdResourceProp<ResourceTypes>{name:resourcetype,ns:dav:,status:200,value:()},
//       [dav:]creationdate: WebDavStdResourceProp<DateTime>{name:creationdate,ns:dav:,status:200,value:2024-09-14 07:15:01.000Z},
//       [dav:]getcontentlength: WebDavStdResourceProp<num>{name:getcontentlength,ns:dav:,status:200,value:0},
//       [dav:]getlastmodified: WebDavStdResourceProp<DateTime>{name:getlastmodified,ns:dav:,status:200,value:2024-09-14 07:15:01.000Z},
//       [dav:]getetag: WebDavStdResourceProp<String>{name:getetag,ns:dav:,status:200,value:"0-6220f191f7916"},
//       [dav:]supportedlock: WebDavStdResourceProp<List<LockEntry>>{name:supportedlock,ns:dav:,status:200,value:[Instance of 'LockEntry', Instance of 'LockEntry']},
//       [dav:]lockdiscovery: WebDavStdResourceProp<List<ActiveLock<Null>>>{name:lockdiscovery,ns:dav:,status:200,value:[]},
//       [dav:]getcontenttype: WebDavStdResourceProp<ContentType>{name:getcontenttype,ns:dav:,status:200,value:text/plain},
//   }
// }
//
//
// [Step5]: make new dir http://localhost/path/
// WebDavStdResponseResult{
//   // http://localhost/path/
//   WebDavStdResource{
//     path:http://localhost/path/ | status:201,
//     props(0):
//   }
// }
//
//
// [Step6]: copy http://localhost/new-file.txt to http://localhost/path/new-file-copyed.txt
// WebDavStdResponseResult{
//   // http://localhost/new-file.txt
//   WebDavStdResource{
//     path:http://localhost/new-file.txt | status:201,
//     props(0):
//   }
// }
// WebDavStdResponseResult{
//   // http://localhost/new-file.txt
//   WebDavStdResource{
//     path:http://localhost/new-file.txt | status:201,
//     props(0):
//   }
// }
//
//
// [Step7]: remove custom prop on http://localhost/new-file.txt
// WebDavStdResponseResult{
//   // /new-file.txt
//   WebDavStdResource{
//     path:/new-file.txt | status:207,
//     props(1):
//       [custom:]author: WebDavStdResourceProp<String>{name:author,ns:custom:,status:200,value:},
//   }
// }
//
//
// [Step8]: get props attributes from http://localhost/new-file.txt
// WebDavStdResponseResult{
//   // /new-file.txt
//   WebDavStdResource{
//     path:/new-file.txt | status:207,
//     props(3):
//       [dav:]getlastmodified: WebDavStdResourceProp<DateTime>{name:getlastmodified,ns:dav:,status:200,value:2024-09-14 07:15:01.000Z},
//       [dav:]getetag: WebDavStdResourceProp<String>{name:getetag,ns:dav:,status:200,value:"0-6220f191f7916"},
//       [custom:]author: WebDavStdResourceProp<String>{name:author,ns:custom:,status:404,value:},
//   }
// }
//
//
// [Step9]: move http://localhost/path/new-file-copyed.txt to http://localhost/path/new-file-moved.txt
// WebDavStdResponseResult{
//   // http://localhost/path/new-file-copyed.txt
//   WebDavStdResource{
//     path:http://localhost/path/new-file-copyed.txt | status:201,
//     props(0):
//   }
// }
//
//
// [Step10]: get props attributes from http://localhost/new-file.txt
// WebDavStdResponseResult{
//   // /path/new-file-moved.txt
//   WebDavStdResource{
//     path:/path/new-file-moved.txt | status:207,
//     props(3):
//       [custom:]author: WebDavStdResourceProp<String>{name:author,ns:custom:,status:200,value:zigzag},
//       [dav:]getlastmodified: WebDavStdResourceProp<DateTime>{name:getlastmodified,ns:dav:,status:200,value:2024-09-14 07:15:01.000Z},
//       [dav:]getetag: WebDavStdResourceProp<String>{name:getetag,ns:dav:,status:200,value:"0-6220f1920bcc2"},
//   }
// }
//
//
// [Step11]: delete dir http://localhost/path/
// WebDavStdResponseResult{
//   // http://localhost/path/
//   WebDavStdResource{
//     path:http://localhost/path/ | status:204,
//     props(0):
//   }
// }
// WebDavStdResponseResult{
//   // http://localhost/path/
//   WebDavStdResource{
//     path:http://localhost/path/ | status:404,
//     props(0):
//   }
// }
//
//
// [Step12]: lock file http://localhost/new-file.txt
// WebDavStdResponseResult{
//   // http://localhost/new-file.txt
//   WebDavStdResource{
//     path:http://localhost/new-file.txt | status:200,
//     props(1):
//       [dav:]lockdiscovery: WebDavStdResourceProp<List<ActiveLock<Null>>>{name:lockdiscovery,ns:dav:,status:200,value:[Instance of 'ActiveLock<Null>']},
//   }
// }
//
//
// [Step13]: delete file http://localhost/new-file.txt
// WebDavStdResponseResult{
//   // http://localhost/new-file.txt
//   WebDavStdResource{
//     path:http://localhost/new-file.txt | status:423,
//     props(0):
//   }
// }
// WebDavStdResponseResult{
//   // http://localhost/new-file.txt
//   WebDavStdResource{
//     path:http://localhost/new-file.txt | status:200,
//     props(0):
//   }
// }
//
//
// [Step13]: unlock file http://localhost/new-file.txt
// WebDavStdResponseResult{
//   // http://localhost/new-file.txt
//   WebDavStdResource{
//     path:http://localhost/new-file.txt | status:204,
//     props(0):
//   }
// }
//
//
// [Step15]: delete file http://localhost/new-file.txt
// WebDavStdResponseResult{
//   // http://localhost/new-file.txt
//   WebDavStdResource{
//     path:http://localhost/new-file.txt | status:204,
//     props(0):
//   }
// }
// WebDavStdResponseResult{
//   // http://localhost/new-file.txt
//   WebDavStdResource{
//     path:http://localhost/new-file.txt | status:404,
//     props(0):
//   }
// }
//
// Exited.
//

import 'package:simple_webdav_client/client.dart';
import 'package:simple_webdav_client/dav.dart';
import 'package:simple_webdav_client/utils.dart';
import 'package:universal_io/io.dart';

BaseRespResultParser generateParser() {
  final propParsers = Map.of(kStdPropParserManager);
  propParsers[(name: "author", ns: "CUSTOM:")] = const StringPropParser();

  final propstatParser = BasePropstatElementParser(
      parserManger: WebDavResposneDataParserManger(parsers: propParsers),
      statusParser: const BaseHttpStatusElementParser(),
      errorParser: const BaseErrorElementParser());
  final responseParser = BaseResponseElementParser(
      hrefParser: const BaseHrefElementParser(),
      statusParser: const BaseHttpStatusElementParser(),
      propstatParser: propstatParser,
      errorParser: const BaseErrorElementParser(),
      locationParser: const BaseHrefElementParser());
  final multistatParser =
      BaseMultistatusElementParser(responseParser: responseParser);

  final parsers = Map.of(kStdElementParserManager);
  parsers[(name: WebDavElementNames.multistatus, ns: kDavNamespaceUrlStr)] =
      multistatParser;

  return BaseRespResultParser(
      singleResDecoder: kStdResponseResultParser.singleResDecoder,
      multiResDecoder: BaseRespMultiResultParser(
          parserManger: WebDavResposneDataParserManger(parsers: parsers)));
}

void main() async {
  final Uri newFilePath;
  final Uri newDirPath;
  final WebDavStdClient client;

  BaseRespResultParser parser;

  client = WebDavClient.std();
  client.addCredentials(Uri.parse('http://localhost'), "WebDAV",
      HttpClientDigestCredentials("admin", "123456"));
  parser = generateParser();

  newFilePath = Uri.parse("http://localhost/new-file.txt");
  final dispatcher = client.dispatch(newFilePath, responseResultParser: parser);
  print('[Step1]: put new file at $newFilePath');
  await dispatcher
      .create(data: "test data")
      .then((request) => request.close())
      .then((response) => response.parse())
      .then((result) => print(result?.toDebugString()));

  print('\n');
  print('[Step2]: set custom prop on $newFilePath');
  await dispatcher
      .updateProps(operations: [
        ProppatchRequestProp.set(
            name: "author",
            namespace: "CUSTOM:",
            value: ProppatchRequestPropBaseValue("zigzag"))
      ])
      .then((request) => request.close())
      .then((response) => response.parse())
      .then((result) => print(result?.toDebugString()));

  print('\n');
  print('[Step3]: get prop names from $newFilePath');
  await dispatcher
      .findPropNames()
      .then((request) => request.close())
      .then((response) => response.parse())
      .then((result) => print(result?.toDebugString()));

  print('\n');
  print('[Step4]: get props attributes from $newFilePath');
  await dispatcher
      .findAllProps(includes: [PropfindRequestProp('author', 'CUSTOM:')])
      .then((request) => request.close())
      .then((response) => response.parse())
      .then((result) => print(result?.toDebugString()));

  newDirPath = Uri.parse("http://localhost/path/");
  print('\n');
  print('[Step5]: make new dir $newDirPath');
  await client
      .dispatch(newDirPath)
      .createDir()
      .then((request) => request.close())
      .then((response) => response.parse())
      .then((result) => print(result?.toDebugString()));

  print('\n');
  final copyedFilePath = Uri.parse("http://localhost/path/new-file-copyed.txt");
  print('[Step6]: copy $newFilePath to $copyedFilePath');
  await client
      .dispatch(newFilePath)
      .copy(to: copyedFilePath)
      .then((request) => request.close())
      .then((response) => response.parse())
      .then((result) => print(result?.toDebugString()));
  await client
      .dispatch(newFilePath)
      .copy(to: Uri.parse("http://localhost/path/new-file-copyed-bak.txt"))
      .then((request) => request.close())
      .then((response) => response.parse())
      .then((result) => print(result?.toDebugString()));

  print('\n');
  print('[Step7]: remove custom prop on $newFilePath');
  await dispatcher
      .updateProps(operations: [
        ProppatchRequestProp.remove(name: "author", namespace: "CUSTOM:")
      ])
      .then((request) => request.close())
      .then((response) => response.parse())
      .then((result) => print(result?.toDebugString()));

  print('\n');
  print('[Step8]: get props attributes from $newFilePath');
  await dispatcher
      .findProps(props: [
        PropfindRequestProp('author', 'CUSTOM:'),
        PropfindRequestProp.dav("getlastmodified"),
        PropfindRequestProp.dav("getetag")
      ])
      .then((request) => request.close())
      .then((response) => response.parse())
      .then((result) => print(result?.toDebugString()));

  print('\n');
  final movedFilePath = Uri.parse("http://localhost/path/new-file-moved.txt");
  print('[Step9]: move $copyedFilePath to $movedFilePath');
  await client
      .dispatch(copyedFilePath)
      .move(to: movedFilePath)
      .then((request) => request.close())
      .then((response) => response.parse())
      .then((result) => print(result?.toDebugString()));

  print('\n');
  print('[Step10]: get props attributes from $newFilePath');
  await client
      .dispatch(movedFilePath, responseResultParser: parser)
      .findProps(props: [
        PropfindRequestProp('author', 'CUSTOM:'),
        PropfindRequestProp.dav("getlastmodified"),
        PropfindRequestProp.dav("getetag")
      ])
      .then((request) => request.close())
      .then((response) => response.parse())
      .then((result) => print(result?.toDebugString()));

  print('\n');
  print('[Step11]: delete dir $newDirPath');
  await client
      .dispatch(newDirPath)
      .deleteDir()
      .then((request) => request.close())
      .then((response) => response.parse())
      .then((result) => print(result?.toDebugString()));
  await client
      .dispatch(newDirPath)
      .get()
      .then((request) => request.close())
      .then((response) => response.parse())
      .then((result) => print(result?.toDebugString()));

  print('\n');
  print('[Step12]: lock file $newFilePath');

  Uri? lockToken;
  await dispatcher
      .createLock(info: LockInfo(lockScope: LockScope.exclusive))
      .then((request) => request.close())
      .then((response) => response.parse())
      .then((result) {
    print(result?.toDebugString());
    lockToken = result?.firstOrNull?.props
        .whereType<WebDavStdResourceProp<LockDiscovery>>()
        .firstOrNull
        ?.value
        ?.firstOrNull
        ?.lockToken;
  });

  print('\n');
  print('[Step13]: delete file $newFilePath');
  await client
      .dispatch(newFilePath)
      .delete()
      .then((request) => request.close())
      .then((response) => response.parse())
      .then((result) => print(result?.toDebugString()));
  await client
      .dispatch(newFilePath)
      .get()
      .then((request) => request.close())
      .then((response) => response.parse())
      .then((result) => print(result?.toDebugString()));

  if (lockToken != null) {
    print('\n');
    print('[Step13]: unlock file $newFilePath');
    await client
        .dispatch(newFilePath)
        .unlock(token: lockToken!)
        .then((request) => request.close())
        .then((response) => response.parse())
        .then((result) => print(result?.toDebugString()));
  }

  print('\n');
  print('[Step15]: delete file $newFilePath');
  await client
      .dispatch(newFilePath)
      .delete()
      .then((request) => request.close())
      .then((response) => response.parse())
      .then((result) => print(result?.toDebugString()));
  await client
      .dispatch(newFilePath)
      .get()
      .then((request) => request.close())
      .then((response) => response.parse())
      .then((result) => print(result?.toDebugString()));
}
1
likes
160
pub points
45%
popularity

Publisher

unverified uploader

A Dart WebDAV client with full RFC 4918 support, including file operations, property management, and locking (LOCK/UNLOCK). Ideal for these needing complete WebDAV functionality.

Repository (GitHub)
View/report issues

Topics

#webdav #webdav-client #rfc4918

Documentation

API reference

Funding

Consider supporting this project:

www.buymeacoffee.com

License

MIT (license)

Dependencies

universal_io, xml

More

Packages that depend on simple_webdav_client