Skip to content

Commit b85c1de

Browse files
Merge pull request #20 from OctagonalStar/feat/WebDAV_Sync
添加WebDav数据备份恢复
2 parents b556a05 + 449aa48 commit b85c1de

File tree

10 files changed

+551
-93
lines changed

10 files changed

+551
-93
lines changed

lib/funcs/sync.dart

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
import 'dart:convert';
2+
import 'dart:typed_data' show Uint8List;
3+
4+
import 'package:flutter/material.dart';
5+
import 'package:webdav_client/webdav_client.dart';
6+
7+
import 'package:arabic_learning/package_replacement/storage.dart' show SharedPreferences;
8+
9+
class WebDAV {
10+
String uri;
11+
String user;
12+
String password;
13+
WebDAV({required this.uri, required this.user, required this.password});
14+
15+
bool isReachable = false;
16+
bool isReadable = false;
17+
bool isWriteable = false;
18+
19+
late Client client;
20+
21+
static Future<List<dynamic>> test(String uri, String user, {String password = ''}) async {
22+
Client tempClient = newClient(
23+
uri,
24+
user: user,
25+
password: password
26+
);
27+
try{
28+
tempClient.setHeaders(
29+
{
30+
'accept-charset': 'utf-8',
31+
'Content-Type': 'text/xml',
32+
},
33+
);
34+
tempClient.setConnectTimeout(8000);
35+
tempClient.setSendTimeout(60000);
36+
tempClient.setReceiveTimeout(60000);
37+
} catch (e) {
38+
return [false, false, "base setting error: $e"];
39+
}
40+
try{
41+
await tempClient.ping(); // test for connection
42+
} catch (e) {
43+
return [false, false, "remote server didn't response: $e"];
44+
}
45+
try {
46+
await tempClient.readDir('/'); // test for read
47+
} catch (e) {
48+
return [true, false, 'no read access: $e'];
49+
}
50+
try{
51+
await tempClient.write("TestFile", Uint8List(64)); // test for write
52+
await tempClient.remove("TestFile");
53+
} catch (e) {
54+
return [true, false, 'no write access: $e'];
55+
}
56+
return [true, true, 'ok'];
57+
}
58+
59+
Future<void> connect() async {
60+
try{
61+
client = newClient(
62+
uri,
63+
user: user,
64+
password: password
65+
);
66+
client.setHeaders({'accept-charset': 'utf-8'});
67+
client.setConnectTimeout(8000);
68+
client.setSendTimeout(8000);
69+
client.setReceiveTimeout(8000);
70+
await client.ping(); // test for connection
71+
isReachable = true;
72+
await client.readDir(''); // test for read
73+
isReadable = true;
74+
await client.write("TestFile", Uint8List(64)); // test for write
75+
isWriteable = true;
76+
await client.remove("TestFile");
77+
} catch (e) {
78+
debugPrint(e.toString());
79+
}
80+
}
81+
82+
Future<bool> upload(SharedPreferences pref,{bool force = false}) async {
83+
if(isWriteable || force) {
84+
await client.write("arabic_learning.bak", utf8.encode(jsonEncode(pref.export())));
85+
return true;
86+
}
87+
return false;
88+
}
89+
90+
Future<bool> download(SharedPreferences pref,{bool force = false}) async {
91+
if(isReadable || force) {
92+
Map<String, dynamic> file = jsonDecode(utf8.decode(await client.read("arabic_learning.bak")));
93+
pref.recovery(file);
94+
return true;
95+
}
96+
return false;
97+
}
98+
}

lib/package_replacement/storage.dart

Lines changed: 24 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,9 @@ class SharedPreferences {
77
late bool type; // true: shpr ; false: indexDB
88
late idb.IdbFactory idbFactory;
99
late idb.Database db;
10-
Map<String, dynamic> dbCache = {}; // 使用缓存避免异步加载
1110
late shpr.SharedPreferences prefs;
11+
static const List<String> usedKeys = ["settingData", "wordData", "fsrsData"]; // ! change this whenever add new setting key !
12+
Map<String, dynamic> dbCache = {}; // 使用缓存避免异步加载
1213
static Future<SharedPreferences> getInstance() async {
1314
SharedPreferences rt = SharedPreferences();
1415
if(kIsWeb) {
@@ -24,9 +25,9 @@ class SharedPreferences {
2425
);
2526
var txn = rt.db.transaction("data", "readonly");
2627
var store = txn.objectStore("data");
27-
rt.dbCache["settingData"] = await store.getObject("settingData");
28-
rt.dbCache["wordData"] = await store.getObject("wordData");
29-
rt.dbCache["fsrsData"] = await store.getObject("fsrsData");
28+
for(String keyName in usedKeys) {
29+
rt.dbCache[keyName] = await store.getObject(keyName);
30+
}
3031
rt.type = false;
3132
} catch (e) {
3233
// print("FallBack to shpr $e");
@@ -82,4 +83,23 @@ class SharedPreferences {
8283
return false;
8384
}
8485
}
86+
87+
Map<String, dynamic> export() {
88+
if(type) {
89+
Map<String, dynamic> overall = {};
90+
for(String keyName in usedKeys){
91+
overall[keyName] = prefs.getString(keyName);
92+
}
93+
return overall;
94+
} else {
95+
return dbCache;
96+
}
97+
}
98+
99+
void recovery(Map<String, dynamic> backup) {
100+
if(!type) dbCache = {}; // create a new instance
101+
for(String keyName in usedKeys) {
102+
setString(keyName, backup[keyName]);
103+
}
104+
}
85105
}

lib/pages/setting_page.dart

Lines changed: 44 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import 'package:arabic_learning/sub_pages_builder/setting_pages/data_download_pa
55
import 'package:arabic_learning/sub_pages_builder/setting_pages/item_widget.dart';
66
import 'package:arabic_learning/sub_pages_builder/setting_pages/model_download_page.dart' show ModelDownload;
77
import 'package:arabic_learning/sub_pages_builder/setting_pages/questions_setting_page.dart' show QuestionsSettingLeadingPage;
8+
import 'package:arabic_learning/sub_pages_builder/setting_pages/sync_page.dart' show DataSyncPage;
89
import 'package:arabic_learning/vars/global.dart';
910
import 'package:flutter/foundation.dart' show kIsWeb;
1011
import 'package:file_picker/file_picker.dart';
@@ -22,23 +23,36 @@ class SettingPage extends StatefulWidget {
2223
class _SettingPage extends State<SettingPage> {
2324
@override
2425
Widget build(BuildContext context) {
25-
final mediaQuery = MediaQuery.of(context);
2626
return Consumer<Global>(
2727
builder: (context, value, child) {
2828
var setting = value.settingData;
2929
return ListView(
3030
children: [
31-
settingItem(context, mediaQuery, regularSetting(mediaQuery, context, setting), "常规设置"),
32-
settingItem(context, mediaQuery, dataSetting(mediaQuery, context, setting), "学习设置", withPadding: false),
33-
settingItem(context, mediaQuery, audioSetting(mediaQuery, context, setting), "音频设置", withPadding: false),
34-
settingItem(context, mediaQuery, aboutSetting(mediaQuery, context, setting), "关于", withPadding: false),
31+
SettingItem(
32+
title: "常规设置",
33+
padding: EdgeInsets.all(8.0),
34+
children: regularSetting(context, setting),
35+
),
36+
SettingItem(
37+
title: "学习设置",
38+
children: dataSetting(context, setting),
39+
),
40+
SettingItem(
41+
title: "音频设置",
42+
children: audioSetting(context, setting),
43+
),
44+
SettingItem(
45+
title: "关于",
46+
children: aboutSetting(context, setting),
47+
),
3548
],
3649
);
3750
},
3851
);
3952
}
4053

41-
List<Widget> regularSetting(MediaQueryData mediaQuery, BuildContext context, Map<String, dynamic> setting) {
54+
List<Widget> regularSetting(BuildContext context, Map<String, dynamic> setting) {
55+
MediaQueryData mediaQuery = MediaQuery.of(context);
4256
return [
4357
Row(
4458
children: [
@@ -117,7 +131,8 @@ class _SettingPage extends State<SettingPage> {
117131
];
118132
}
119133

120-
List<Widget> dataSetting(MediaQueryData mediaQuery, BuildContext context, Map<String, dynamic> setting) {
134+
List<Widget> dataSetting(BuildContext context, Map<String, dynamic> setting) {
135+
MediaQueryData mediaQuery = MediaQuery.of(context);
121136
return [
122137
Column(
123138
children: [
@@ -268,11 +283,29 @@ class _SettingPage extends State<SettingPage> {
268283
Icon(Icons.arrow_forward_ios)
269284
],
270285
)
271-
)
286+
),
287+
ElevatedButton(
288+
style: ElevatedButton.styleFrom(
289+
minimumSize: Size.fromHeight(mediaQuery.size.height * 0.08),
290+
backgroundColor: Theme.of(context).colorScheme.onPrimary.withAlpha(150),
291+
shape: RoundedRectangleBorder(borderRadius: BorderRadiusGeometry.vertical(bottom: Radius.circular(25.0)))
292+
),
293+
onPressed: (){
294+
Navigator.of(context).push(MaterialPageRoute(builder: (context) => DataSyncPage()));
295+
},
296+
child: Row(
297+
children: [
298+
Icon(Icons.sync),
299+
Expanded(child: Text("数据备份及同步")),
300+
Icon(Icons.arrow_forward_ios)
301+
],
302+
)
303+
),
272304
];
273305
}
274306

275-
List<Widget> audioSetting(MediaQueryData mediaQuery, BuildContext context, Map<String, dynamic> setting) {
307+
List<Widget> audioSetting(BuildContext context, Map<String, dynamic> setting) {
308+
MediaQueryData mediaQuery = MediaQuery.of(context);
276309
var set = context.read<Global>().settingData;
277310
return [
278311
Column(
@@ -374,7 +407,8 @@ class _SettingPage extends State<SettingPage> {
374407
];
375408
}
376409

377-
List<Widget> aboutSetting(MediaQueryData mediaQuery, BuildContext context, Map<String, dynamic> setting) {
410+
List<Widget> aboutSetting(BuildContext context, Map<String, dynamic> setting) {
411+
MediaQueryData mediaQuery = MediaQuery.of(context);
378412
return [
379413
ElevatedButton(
380414
style: ElevatedButton.styleFrom(

lib/sub_pages_builder/learning_pages/learning_pages_build.dart

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import 'dart:convert';
21
import 'dart:math';
32

43
import 'package:arabic_learning/funcs/ui.dart';

lib/sub_pages_builder/setting_pages/data_download_page.dart

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@ class DownloadPage extends StatelessWidget {
1212
const DownloadPage({super.key});
1313
@override
1414
Widget build(BuildContext context) {
15-
final mediaQuery = MediaQuery.of(context);
1615
return Scaffold(
1716
appBar: AppBar(
1817
title: Text("下载在线词库"),
@@ -27,7 +26,11 @@ class DownloadPage extends StatelessWidget {
2726
}
2827
return ListView(
2928
children: [
30-
settingItem(context, mediaQuery, snapshot.data!, "来自 Github @${StaticsVar.onlineDictOwner} 学长的词库 (在此表示感谢)")
29+
SettingItem(
30+
title: "来自 Github @${StaticsVar.onlineDictOwner} 学长的词库 (在此表示感谢)",
31+
padding: EdgeInsets.all(8.0),
32+
children: snapshot.data!,
33+
)
3134
],
3235
);
3336
}

0 commit comments

Comments
 (0)