From d53d6deb2e06c33aa07f90c410796df4d91df367 Mon Sep 17 00:00:00 2001 From: DebuggerX Date: Sun, 19 Jul 2026 00:21:57 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E4=B8=8B=E8=BD=BD=E6=97=B6=E8=8B=A5?= =?UTF-8?q?=E5=AD=98=E5=9C=A8=E6=95=B0=E6=8D=AE=E4=B8=A2=E5=A4=B1=E9=A3=8E?= =?UTF-8?q?=E9=99=A9=E5=88=99=E5=BC=B9=E7=AA=97=E6=8F=90=E7=A4=BA=EF=BC=9B?= =?UTF-8?q?=E6=9C=8D=E5=8A=A1=E7=AB=AF=E5=A2=9E=E5=8A=A0=E5=8D=A0=E7=94=A8?= =?UTF-8?q?=E4=BF=A1=E6=81=AF=E6=9F=A5=E7=9C=8B=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- client/lib/dialog.dart | 82 +++++++++++++++++++++++++++++++++++++----- client/lib/main.dart | 12 +++++-- client/lib/utils.dart | 3 +- server/.gitignore | 1 + server/bin/server.dart | 39 ++++++++++++++++++-- 5 files changed, 123 insertions(+), 14 deletions(-) diff --git a/client/lib/dialog.dart b/client/lib/dialog.dart index 6486063..b6fc7b5 100644 --- a/client/lib/dialog.dart +++ b/client/lib/dialog.dart @@ -104,13 +104,13 @@ class _AppConfigDialogState extends State { Row( mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [ - FilledButton( + ElevatedButton( onPressed: () { Navigator.of(context).pop(); }, child: Text('取消'), ), - ElevatedButton( + FilledButton( onPressed: () { var client = clientCtl.text.trim(); var server = serverCtl.text.trim(); @@ -223,12 +223,10 @@ class _AddSyncItemDialogState extends State { children: [ IconButton( onPressed: () { - FilePicker.pickFiles( - allowMultiple: false, - ).then( + FilePicker.pickFile().then( (res) { - if (res?.files.firstOrNull?.path?.isNotEmpty == true) { - pathCtl.text = res!.files.first.path!; + if (res?.xFile.path?.isNotEmpty == true) { + pathCtl.text = res!.xFile.path!; } }, ); @@ -253,13 +251,13 @@ class _AddSyncItemDialogState extends State { Row( mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [ - FilledButton( + ElevatedButton( onPressed: () { Navigator.of(context).pop(); }, child: Text('取消'), ), - ElevatedButton( + FilledButton( onPressed: () { var name = nameCtl.text.trim(); var path = pathCtl.text.trim(); @@ -284,3 +282,69 @@ class _AddSyncItemDialogState extends State { ); } } + +class UploadConfirmDialog extends StatelessWidget { + const UploadConfirmDialog({super.key}); + + static Future show(BuildContext context, [SyncItemConfig? config]) { + return showDialog( + context: context, + builder: (context) => Dialog( + child: UploadConfirmDialog(), + ), + ); + } + + @override + Widget build(BuildContext context) { + return Container( + width: 400, + height: 200, + padding: EdgeInsets.all(16), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(16), + color: Colors.white, + boxShadow: [ + BoxShadow( + color: Colors.black26, + offset: Offset(4, 4), + spreadRadius: 8, + blurRadius: 16, + ), + ], + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Text( + '确认下载并覆盖本地数据?', + style: TextStyle(fontSize: 24, fontWeight: FontWeight.bold), + ), + SizedBox(height: 26), + Text( + '本地资源修改时间似乎晚于云端\n确认覆盖可能造成数据丢失!', + textAlign: TextAlign.center, + ), + const Spacer(), + Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + ElevatedButton( + onPressed: () { + Navigator.of(context).pop(false); + }, + child: Text('取消'), + ), + FilledButton( + onPressed: () { + Navigator.of(context).pop(true); + }, + child: Text('确认'), + ), + ], + ), + ], + ), + ); + } +} diff --git a/client/lib/main.dart b/client/lib/main.dart index f9d6d11..690ea95 100644 --- a/client/lib/main.dart +++ b/client/lib/main.dart @@ -105,6 +105,7 @@ class _MyHomePageState extends State { IconButton( onPressed: () { loadCloudArchives(); + loadLocalStatus(); }, icon: Icon(Icons.refresh_outlined), ), @@ -210,9 +211,16 @@ class _MyHomePageState extends State { return InkWell( onTap: cloudModifiedTimeTs == null ? null - : () { + : () async { if (syncItemConfig != null) { - download(context, syncItemConfig, cloudArchives.sorted.first); + if (cloudModifiedTimeTs != null && + localModifiedTime != null && + localModifiedTime.millisecondsSinceEpoch > cloudModifiedTimeTs) { + if (await UploadConfirmDialog.show(context) != true) return; + } + if (context.mounted) { + download(context, syncItemConfig, cloudArchives.sorted.first); + } } }, onLongPress: cloudModifiedTimeTs == null diff --git a/client/lib/utils.dart b/client/lib/utils.dart index a007b10..f7da7dd 100644 --- a/client/lib/utils.dart +++ b/client/lib/utils.dart @@ -4,6 +4,7 @@ import 'package:archive/archive_io.dart'; import 'package:cloud_archiver/config.dart'; import 'package:cloud_archiver/state.dart'; import 'package:filesize/filesize.dart'; +import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:path/path.dart'; import 'package:path_provider/path_provider.dart'; @@ -24,7 +25,7 @@ String totalSize() { Future connect(String server, String user, String auth) async { var client = dav.newClient( server, - debug: true, + debug: kDebugMode, user: user, password: auth, ); diff --git a/server/.gitignore b/server/.gitignore index e4ce6a1..0325afb 100644 --- a/server/.gitignore +++ b/server/.gitignore @@ -1,3 +1,4 @@ # https://dart.dev/tools/private-files # Created by `dart pub` .dart_tool/ +repo/ diff --git a/server/bin/server.dart b/server/bin/server.dart index dd91087..8c20c86 100644 --- a/server/bin/server.dart +++ b/server/bin/server.dart @@ -2,6 +2,7 @@ import 'dart:io'; import 'package:args/args.dart'; import 'package:file/local.dart'; +import 'package:shelf/shelf.dart'; import 'package:shelf/shelf_io.dart'; import 'package:shelf_dav/shelf_dav.dart'; @@ -24,6 +25,7 @@ void main(List args) async { if (!rootDir.existsSync()) { rootDir.createSync(recursive: true); } + var user = argResults.option('user')!; final config = DAVConfig( root: rootDir, prefix: '/dav', @@ -33,7 +35,7 @@ void main(List args) async { readOnly: false, propertyStorageType: PropertyStorageType.file, authenticationProvider: BasicAuthenticationProvider.plaintext( - users: {argResults.option('user')!: argResults.option('auth')!}, + users: {user: argResults.option('auth')!}, ), ); @@ -42,6 +44,39 @@ void main(List args) async { final ip = InternetAddress.anyIPv4; final port = int.parse(argResults.option('port') ?? '8800'); - final server = await serve(dav.handler, ip, port); + final server = await serve( + (request) { + if (request.method == 'GET' && request.url.path == 'info/$user') { + return getInfo(rootDir); + } + + return dav.handler.call(request); + }, + ip, + port, + ); print('WebDAV server listening on port ${server.port}'); } + +Response getInfo(Directory dir) { + var du = Process.runSync('du', [ + '-h', + '--time', + dir.absolute.path, + ]).stdout.toString(); + var result = []; + for (var line in du.split('\n')) { + var parts = line.split(dir.absolute.path); + if (parts.length == 2) { + if (parts.last.trim().isEmpty) { + result.insert(0, '${parts.first}总占用\n'); + } else { + result.add('${parts.first}${Uri.decodeComponent(parts.last)}'); + } + } + } + return Response.ok( + result.join('\n'), + headers: {'content-type': 'text/plain'}, + ); +}