feat: 下载时若存在数据丢失风险则弹窗提示;服务端增加占用信息查看功能
This commit is contained in:
+73
-9
@@ -104,13 +104,13 @@ class _AppConfigDialogState extends State<AppConfigDialog> {
|
|||||||
Row(
|
Row(
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
||||||
children: [
|
children: [
|
||||||
FilledButton(
|
ElevatedButton(
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
Navigator.of(context).pop();
|
Navigator.of(context).pop();
|
||||||
},
|
},
|
||||||
child: Text('取消'),
|
child: Text('取消'),
|
||||||
),
|
),
|
||||||
ElevatedButton(
|
FilledButton(
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
var client = clientCtl.text.trim();
|
var client = clientCtl.text.trim();
|
||||||
var server = serverCtl.text.trim();
|
var server = serverCtl.text.trim();
|
||||||
@@ -223,12 +223,10 @@ class _AddSyncItemDialogState extends State<AddSyncItemDialog> {
|
|||||||
children: [
|
children: [
|
||||||
IconButton(
|
IconButton(
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
FilePicker.pickFiles(
|
FilePicker.pickFile().then(
|
||||||
allowMultiple: false,
|
|
||||||
).then(
|
|
||||||
(res) {
|
(res) {
|
||||||
if (res?.files.firstOrNull?.path?.isNotEmpty == true) {
|
if (res?.xFile.path?.isNotEmpty == true) {
|
||||||
pathCtl.text = res!.files.first.path!;
|
pathCtl.text = res!.xFile.path!;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
@@ -253,13 +251,13 @@ class _AddSyncItemDialogState extends State<AddSyncItemDialog> {
|
|||||||
Row(
|
Row(
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
||||||
children: [
|
children: [
|
||||||
FilledButton(
|
ElevatedButton(
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
Navigator.of(context).pop();
|
Navigator.of(context).pop();
|
||||||
},
|
},
|
||||||
child: Text('取消'),
|
child: Text('取消'),
|
||||||
),
|
),
|
||||||
ElevatedButton(
|
FilledButton(
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
var name = nameCtl.text.trim();
|
var name = nameCtl.text.trim();
|
||||||
var path = pathCtl.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(
|
IconButton(
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
loadCloudArchives();
|
loadCloudArchives();
|
||||||
|
loadLocalStatus();
|
||||||
},
|
},
|
||||||
icon: Icon(Icons.refresh_outlined),
|
icon: Icon(Icons.refresh_outlined),
|
||||||
),
|
),
|
||||||
@@ -210,9 +211,16 @@ class _MyHomePageState extends State<MyHomePage> {
|
|||||||
return InkWell(
|
return InkWell(
|
||||||
onTap: cloudModifiedTimeTs == null
|
onTap: cloudModifiedTimeTs == null
|
||||||
? null
|
? null
|
||||||
: () {
|
: () async {
|
||||||
if (syncItemConfig != null) {
|
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
|
onLongPress: cloudModifiedTimeTs == null
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import 'package:archive/archive_io.dart';
|
|||||||
import 'package:cloud_archiver/config.dart';
|
import 'package:cloud_archiver/config.dart';
|
||||||
import 'package:cloud_archiver/state.dart';
|
import 'package:cloud_archiver/state.dart';
|
||||||
import 'package:filesize/filesize.dart';
|
import 'package:filesize/filesize.dart';
|
||||||
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:path/path.dart';
|
import 'package:path/path.dart';
|
||||||
import 'package:path_provider/path_provider.dart';
|
import 'package:path_provider/path_provider.dart';
|
||||||
@@ -24,7 +25,7 @@ String totalSize() {
|
|||||||
Future<bool> connect(String server, String user, String auth) async {
|
Future<bool> connect(String server, String user, String auth) async {
|
||||||
var client = dav.newClient(
|
var client = dav.newClient(
|
||||||
server,
|
server,
|
||||||
debug: true,
|
debug: kDebugMode,
|
||||||
user: user,
|
user: user,
|
||||||
password: auth,
|
password: auth,
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
# https://dart.dev/tools/private-files
|
# https://dart.dev/tools/private-files
|
||||||
# Created by `dart pub`
|
# Created by `dart pub`
|
||||||
.dart_tool/
|
.dart_tool/
|
||||||
|
repo/
|
||||||
|
|||||||
+37
-2
@@ -2,6 +2,7 @@ import 'dart:io';
|
|||||||
|
|
||||||
import 'package:args/args.dart';
|
import 'package:args/args.dart';
|
||||||
import 'package:file/local.dart';
|
import 'package:file/local.dart';
|
||||||
|
import 'package:shelf/shelf.dart';
|
||||||
import 'package:shelf/shelf_io.dart';
|
import 'package:shelf/shelf_io.dart';
|
||||||
import 'package:shelf_dav/shelf_dav.dart';
|
import 'package:shelf_dav/shelf_dav.dart';
|
||||||
|
|
||||||
@@ -24,6 +25,7 @@ void main(List<String> args) async {
|
|||||||
if (!rootDir.existsSync()) {
|
if (!rootDir.existsSync()) {
|
||||||
rootDir.createSync(recursive: true);
|
rootDir.createSync(recursive: true);
|
||||||
}
|
}
|
||||||
|
var user = argResults.option('user')!;
|
||||||
final config = DAVConfig(
|
final config = DAVConfig(
|
||||||
root: rootDir,
|
root: rootDir,
|
||||||
prefix: '/dav',
|
prefix: '/dav',
|
||||||
@@ -33,7 +35,7 @@ void main(List<String> args) async {
|
|||||||
readOnly: false,
|
readOnly: false,
|
||||||
propertyStorageType: PropertyStorageType.file,
|
propertyStorageType: PropertyStorageType.file,
|
||||||
authenticationProvider: BasicAuthenticationProvider.plaintext(
|
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 ip = InternetAddress.anyIPv4;
|
||||||
|
|
||||||
final port = int.parse(argResults.option('port') ?? '8800');
|
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}');
|
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