Flutter WebDAV客户端插件simple_webdav_client的使用

发布于 1周前 作者 zlyuanteng 来自 Flutter

Flutter WebDAV 客户端插件 simple_webdav_client 的使用

simple_webdav_client 是一个带有完整 RFC 4918 支持的 Dart WebDAV 客户端。它包括文件操作(PUT/GET/CREATE/DELETE/MKCOL/MOVE/COPY)、属性管理(PROPFIND/PROPUPDATE)和锁定(LOCK/UNLOCK)。适用于需要完整 WebDAV 功能的用户。

客户端的 API 调用风格类似于 HttpClient,并且更容易与异步编程一起工作。有关更多示例,请参阅 这里

特性

已实现以下方法:

  • ✅ PROPFIND
  • ✅ PROPPATCH
  • ✅ MKCOL/PUT
  • ✅ GET/HEAD/POST
  • ✅ DELETE
  • ✅ COPY
  • ✅ MOVE
  • ✅ LOCK
  • ✅ UNLOCK

PROPFIND

用于检索由请求 URI 标识的资源上定义的属性。更多信息见 RFC4918#9.1

// openUrl
client.openUrl(
    method: WebDavMethod.propfind,
    url: url,
    // Use [PropfindPropRequestParam] to specify the properties to be requested,
    // [PropfindAllRequestParam] to retrieve as many live properties as possible,
    // and [PropfindNameRequestParam] to retrieve all property names.
    param: PropfindPropRequestParam(...),
);

// api
client.dispatch(url).findProps(...);
client.dispatch(url).findAllProps(...);
client.dispatch(url).findPropNames(...);

PROPPATCH

用于设置和/或删除由请求 URI 标识的资源上定义的属性。更多信息见 RFC4918#9.2

// openUrl
client.openUrl(
    method: WebDavMethod.propfind,
    url: url,
    param: ProppatchRequestParam(...),
);

// api
client.dispatch(url).updateProps(...);

MKCOL/PUT

在由请求 URI 指定的位置创建新的(集合)资源。更多信息见 RFC4918#9.3 - MKCOLRFC4918#9.7 - PUT

// openUrl
client.openUrl(
    method: WebDavMethod.propfind,
    url: url,
    // mkcol: MkcolRequestParam
    // put: PutRequestParam
    param: MkcolRequestParam(...),
);

// api
client.dispatch(url).create(...);
client.dispatch(url).createDir(...);

GET/HEAD/POST

RFC7231 中定义的 HTTP 方法相同。

// openUrl
client.openUrl(
    method: WebDavMethod.propfind,
    url: url,
    // get: GetRequestParam
    // head: HeadRequestParam
    // post: PostRequestParam
    param: GetRequestParam(...),
);

// api: get
client.dispatch(url).get(...);

DELETE

删除由请求 URI 标识的资源。更多信息见 RFC4918#9.6

// openUrl
client.openUrl(
    method: WebDavMethod.propfind,
    url: url,
    param: DeleteRequestParam(...),
);

// api
client.dispatch(url).delete(...);
client.dispatch(url).deleteDir(...);

COPY

创建源资源的副本。更多信息见 RFC4918#9.8

// openUrl
client.openUrl(
    method: WebDavMethod.propfind,
    url: url,
    param: CopyRequestParam(...),
);

// api
client.dispatch(url).copy(...);
client.dispatch(url).copyDir(...);

MOVE

将资源移动到新位置。更多信息见 RFC4918#9.9

// openUrl
client.openUrl(
    method: WebDavMethod.propfind,
    url: url,
    param: MoveRequestParam(...),
);

// api
client.dispatch(url).move(...);
client.dispatch(url).moveDir(...);

LOCK

用于请求对资源的锁定。更多信息见 RFC4918#9.10

// openUrl
client.openUrl(
    method: WebDavMethod.propfind,
    url: url,
    param: LockRequestParam(...),
);

// api
client.dispatch(url).createLock(...);
client.dispatch(url).createDirLock(...);
client.dispatch(url).renewLock(...);

UNLOCK

移除由 Lock-Token 中的令牌标识的锁。更多信息见 RFC4918#9.11

// openUrl
client.openUrl(
    method: WebDavMethod.propfind,
    url: url,
    param: UnlockRequestParam(...),
);

// api
client.dispatch(url).unlock(...);

使用示例

检查测试用例在目录中:

示例 1 - 完整请求
WebDavClient.std().dispatch(newFilePath)
    .findAllProps(includes: [PropfindRequestProp('author', 'CUSTOM:')])
    .then((request) => request.close())
    .then((response) => response.parse())
    .then((result) => print(result?.toDebugString()));
示例 2 - 带有自定义解析器的请求
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)));
}

final parser = generateParser();

// openUrl
client.openUrl(
    method: WebDavMethod.propfind,
    url: url,
    param: ...,
    responseResultParser: parser
);
// dispatch
WebDavClient.std().dispatch(newFilePath, responseResultParser: parser);

捐赠

Buy Me A Coffee Alipay WeChat

ETH BTC

许可证

MIT License

Copyright (c) 2024 Fries_I23

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

示例代码

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()));
}

更多关于Flutter WebDAV客户端插件simple_webdav_client的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html

1 回复

更多关于Flutter WebDAV客户端插件simple_webdav_client的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html


当然,下面是一个关于如何使用Flutter中的simple_webdav_client插件的示例代码。这个插件允许你与WebDAV服务器进行交互。以下代码展示了如何设置客户端、列出目录内容以及上传文件。

首先,确保你已经在pubspec.yaml文件中添加了simple_webdav_client依赖:

dependencies:
  flutter:
    sdk: flutter
  simple_webdav_client: ^最新版本号  # 请替换为实际的最新版本号

然后,运行flutter pub get来安装依赖。

接下来是示例代码:

import 'package:flutter/material.dart';
import 'package:simple_webdav_client/simple_webdav_client.dart';

void main() {
  runApp(MyApp());
}

class MyApp extends StatefulWidget {
  @override
  _MyAppState createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
  final String webDavUrl = 'http://your-webdav-server-url'; // 替换为你的WebDAV服务器URL
  final String username = 'your-username'; // 替换为你的用户名
  final String password = 'your-password'; // 替换为你的密码
  WebDavClient? _client;

  @override
  void initState() {
    super.initState();
    initWebDavClient();
  }

  Future<void> initWebDavClient() async {
    _client = WebDavClient(baseUrl: webDavUrl, username: username, password: password);
    // 测试连接,列出根目录内容
    listFiles();
  }

  Future<void> listFiles() async {
    try {
      var response = await _client!.listDirectory('/');
      print('Directory content:');
      for (var file in response.files) {
        print(file.name);
      }
      for (var directory in response.directories) {
        print(directory.name);
      }
    } catch (e) {
      print('Error listing files: $e');
    }
  }

  Future<void> uploadFile() async {
    String localFilePath = 'path/to/your/local/file.txt'; // 替换为本地文件路径
    String remoteFilePath = '/remote/path/file.txt'; // 替换为远程路径

    try {
      var response = await _client!.uploadFile(localFilePath, remoteFilePath);
      print('File uploaded successfully: ${response.statusCode}');
    } catch (e) {
      print('Error uploading file: $e');
    }
  }

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          title: Text('WebDAV Client Demo'),
        ),
        body: Center(
          child: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: <Widget>[
              ElevatedButton(
                onPressed: listFiles,
                child: Text('List Files'),
              ),
              SizedBox(height: 20),
              ElevatedButton(
                onPressed: uploadFile,
                child: Text('Upload File'),
              ),
            ],
          ),
        ),
      ),
    );
  }
}

解释

  1. 依赖安装:首先,在pubspec.yaml中添加simple_webdav_client依赖。
  2. 初始化客户端:在initState方法中初始化WebDavClient实例,并传入WebDAV服务器的URL、用户名和密码。
  3. 列出目录内容listFiles方法使用listDirectory函数列出根目录的内容,并打印文件名和目录名。
  4. 上传文件uploadFile方法使用uploadFile函数上传本地文件到指定的远程路径。
  5. UI界面:创建了一个简单的Flutter应用,包含两个按钮,一个用于列出文件,另一个用于上传文件。

注意事项

  • 请确保你使用的WebDAV服务器URL、用户名和密码是正确的。
  • 上传文件时,确保本地文件路径和远程文件路径都是有效的。
  • 根据你的需求,可能需要处理更多的错误情况和边界情况。

希望这个示例能帮助你理解如何使用simple_webdav_client插件与WebDAV服务器进行交互。

回到顶部