feat: 下载时若存在数据丢失风险则弹窗提示;服务端增加占用信息查看功能
This commit is contained in:
+73
-9
@@ -104,13 +104,13 @@ class _AppConfigDialogState extends State<AppConfigDialog> {
|
||||
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<AddSyncItemDialog> {
|
||||
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<AddSyncItemDialog> {
|
||||
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<AddSyncItemDialog> {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class UploadConfirmDialog extends StatelessWidget {
|
||||
const UploadConfirmDialog({super.key});
|
||||
|
||||
static Future<bool?> 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('确认'),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
+10
-2
@@ -105,6 +105,7 @@ class _MyHomePageState extends State<MyHomePage> {
|
||||
IconButton(
|
||||
onPressed: () {
|
||||
loadCloudArchives();
|
||||
loadLocalStatus();
|
||||
},
|
||||
icon: Icon(Icons.refresh_outlined),
|
||||
),
|
||||
@@ -210,9 +211,16 @@ class _MyHomePageState extends State<MyHomePage> {
|
||||
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
|
||||
|
||||
@@ -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<bool> connect(String server, String user, String auth) async {
|
||||
var client = dav.newClient(
|
||||
server,
|
||||
debug: true,
|
||||
debug: kDebugMode,
|
||||
user: user,
|
||||
password: auth,
|
||||
);
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
# https://dart.dev/tools/private-files
|
||||
# Created by `dart pub`
|
||||
.dart_tool/
|
||||
repo/
|
||||
|
||||
+37
-2
@@ -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<String> 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<String> 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<String> 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 = <String>[];
|
||||
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'},
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user