sync data using webdav

This commit is contained in:
2024-11-10 10:38:46 +08:00
parent a9a22ace14
commit 4f4411fcc3
17 changed files with 542 additions and 49 deletions

View File

@@ -179,7 +179,13 @@
"Move To First": "移动到最前", "Move To First": "移动到最前",
"Cancel": "取消", "Cancel": "取消",
"Paused": "已暂停", "Paused": "已暂停",
"Pause": "暂停" "Pause": "暂停",
"Operation": "操作",
"Upload": "上传",
"Saved": "已保存",
"Sync Data": "同步数据",
"Syncing Data": "正在同步数据",
"Data Sync": "数据同步"
}, },
"zh_TW": { "zh_TW": {
"Home": "首頁", "Home": "首頁",
@@ -361,6 +367,12 @@
"Move To First": "移動到最前", "Move To First": "移動到最前",
"Cancel": "取消", "Cancel": "取消",
"Paused": "已暫停", "Paused": "已暫停",
"Pause": "暫停" "Pause": "暫停",
"Operation": "操作",
"Upload": "上傳",
"Saved": "已保存",
"Sync Data": "同步數據",
"Syncing Data": "正在同步數據",
"Data Sync": "數據同步"
} }
} }

View File

@@ -114,6 +114,8 @@ class _Settings with ChangeNotifier {
'enableLongPressToZoom': true, 'enableLongPressToZoom': true,
'checkUpdateOnStart': true, 'checkUpdateOnStart': true,
'limitImageWidth': true, 'limitImageWidth': true,
'webdav': [], // empty means not configured
'dataVersion': 0,
}; };
operator [](String key) { operator [](String key) {

View File

@@ -1,5 +1,6 @@
import 'dart:convert'; import 'dart:convert';
import 'package:flutter/foundation.dart';
import 'package:sqlite3/sqlite3.dart'; import 'package:sqlite3/sqlite3.dart';
import 'package:venera/foundation/appdata.dart'; import 'package:venera/foundation/appdata.dart';
import 'package:venera/foundation/image_provider/local_favorite_image.dart'; import 'package:venera/foundation/image_provider/local_favorite_image.dart';
@@ -148,7 +149,7 @@ class FavoriteItemWithFolderInfo extends FavoriteItem {
); );
} }
class LocalFavoritesManager { class LocalFavoritesManager with ChangeNotifier {
factory LocalFavoritesManager() => factory LocalFavoritesManager() =>
cache ?? (cache = LocalFavoritesManager._create()); cache ?? (cache = LocalFavoritesManager._create());
@@ -233,6 +234,7 @@ class LocalFavoritesManager {
values (?, ?); values (?, ?);
""", [folder, order[folder]]); """, [folder, order[folder]]);
} }
notifyListeners();
} }
int count(String folderName) { int count(String folderName) {
@@ -272,6 +274,7 @@ class LocalFavoritesManager {
set tags = '$tag,' || tags set tags = '$tag,' || tags
where id == ? where id == ?
""", [id]); """, [id]);
notifyListeners();
} }
List<FavoriteItemWithFolderInfo> allComics() { List<FavoriteItemWithFolderInfo> allComics() {
@@ -324,6 +327,7 @@ class LocalFavoritesManager {
primary key (id, type) primary key (id, type)
); );
"""); """);
notifyListeners();
return name; return name;
} }
@@ -386,6 +390,7 @@ class LocalFavoritesManager {
values (?, ?, ?, ?, ?, ?, ?, ?); values (?, ?, ?, ?, ?, ?, ?, ?);
""", [...params, minValue(folder) - 1]); """, [...params, minValue(folder) - 1]);
} }
notifyListeners();
} }
/// delete a folder /// delete a folder
@@ -394,6 +399,7 @@ class LocalFavoritesManager {
_db.execute(""" _db.execute("""
drop table "$name"; drop table "$name";
"""); """);
notifyListeners();
} }
void deleteComic(String folder, FavoriteItem comic) { void deleteComic(String folder, FavoriteItem comic) {
@@ -408,6 +414,7 @@ class LocalFavoritesManager {
delete from "$folder" delete from "$folder"
where id == ? and type == ?; where id == ? and type == ?;
""", [id, type.value]); """, [id, type.value]);
notifyListeners();
} }
Future<void> clearAll() async { Future<void> clearAll() async {
@@ -425,6 +432,7 @@ class LocalFavoritesManager {
for (int i = 0; i < newFolder.length; i++) { for (int i = 0; i < newFolder.length; i++) {
addComic(folder, newFolder[i], i); addComic(folder, newFolder[i], i);
} }
notifyListeners();
} }
void rename(String before, String after) { void rename(String before, String after) {
@@ -438,6 +446,7 @@ class LocalFavoritesManager {
ALTER TABLE "$before" ALTER TABLE "$before"
RENAME TO "$after"; RENAME TO "$after";
"""); """);
notifyListeners();
} }
void onReadEnd(String id, ComicType type) async { void onReadEnd(String id, ComicType type) async {
@@ -475,6 +484,7 @@ class LocalFavoritesManager {
""", [newTime, id]); """, [newTime, id]);
} }
} }
notifyListeners();
} }
List<FavoriteItemWithFolderInfo> search(String keyword) { List<FavoriteItemWithFolderInfo> search(String keyword) {
@@ -521,6 +531,7 @@ class LocalFavoritesManager {
set tags = ? set tags = ?
where id == ?; where id == ?;
""", [tags.join(","), id]); """, [tags.join(","), id]);
notifyListeners();
} }
final _cachedFavoritedIds = <String, bool>{}; final _cachedFavoritedIds = <String, bool>{};
@@ -560,6 +571,7 @@ class LocalFavoritesManager {
comic.id, comic.id,
comic.type.value comic.type.value
]); ]);
notifyListeners();
} }
String folderToJson(String folder) { String folderToJson(String folder) {

View File

@@ -82,11 +82,12 @@ class Log {
addLog(LogLevel.warning, title, content); addLog(LogLevel.warning, title, content);
} }
static error(String title, String content, [Object? stackTrace]) { static error(String title, Object content, [Object? stackTrace]) {
var info = content.toString();
if(stackTrace != null) { if(stackTrace != null) {
content += "\n${stackTrace.toString()}"; info += "\n${stackTrace.toString()}";
} }
addLog(LogLevel.error, title, content); addLog(LogLevel.error, title, info);
} }
static void clear() => _logs.clear(); static void clear() => _logs.clear();

View File

@@ -108,11 +108,11 @@ class AppDio with DioMixin {
AppDio([BaseOptions? options]) { AppDio([BaseOptions? options]) {
this.options = options ?? BaseOptions(); this.options = options ?? BaseOptions();
interceptors.add(MyLogInterceptor());
httpClientAdapter = RHttpAdapter(const rhttp.ClientSettings()); httpClientAdapter = RHttpAdapter(const rhttp.ClientSettings());
interceptors.add(CookieManagerSql(SingleInstanceCookieJar.instance!)); interceptors.add(CookieManagerSql(SingleInstanceCookieJar.instance!));
interceptors.add(NetworkCacheManager()); interceptors.add(NetworkCacheManager());
interceptors.add(CloudflareInterceptor()); interceptors.add(CloudflareInterceptor());
interceptors.add(MyLogInterceptor());
} }
static HttpClient createHttpClient() { static HttpClient createHttpClient() {
@@ -211,7 +211,7 @@ class AppDio with DioMixin {
class RHttpAdapter implements HttpClientAdapter { class RHttpAdapter implements HttpClientAdapter {
rhttp.ClientSettings settings; rhttp.ClientSettings settings;
RHttpAdapter(this.settings) { RHttpAdapter([this.settings = const rhttp.ClientSettings()]) {
settings = settings.copyWith( settings = settings.copyWith(
redirectSettings: const rhttp.RedirectSettings.limited(5), redirectSettings: const rhttp.RedirectSettings.limited(5),
timeoutSettings: const rhttp.TimeoutSettings( timeoutSettings: const rhttp.TimeoutSettings(
@@ -232,12 +232,6 @@ class RHttpAdapter implements HttpClientAdapter {
Stream<Uint8List>? requestStream, Stream<Uint8List>? requestStream,
Future<void>? cancelFuture, Future<void>? cancelFuture,
) async { ) async {
Log.info(
"Network",
"${options.method} ${options.uri}\n"
"Headers: ${options.headers}\n"
"Data: ${options.data}\n",
);
var res = await rhttp.Rhttp.request( var res = await rhttp.Rhttp.request(
method: switch (options.method) { method: switch (options.method) {
'GET' => rhttp.HttpMethod.get, 'GET' => rhttp.HttpMethod.get,

View File

@@ -1,5 +1,6 @@
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:sliver_tools/sliver_tools.dart';
import 'package:venera/components/components.dart'; import 'package:venera/components/components.dart';
import 'package:venera/foundation/app.dart'; import 'package:venera/foundation/app.dart';
import 'package:venera/foundation/comic_source/comic_source.dart'; import 'package:venera/foundation/comic_source/comic_source.dart';
@@ -17,6 +18,7 @@ import 'package:venera/pages/downloading_page.dart';
import 'package:venera/pages/history_page.dart'; import 'package:venera/pages/history_page.dart';
import 'package:venera/pages/search_page.dart'; import 'package:venera/pages/search_page.dart';
import 'package:venera/utils/cbz.dart'; import 'package:venera/utils/cbz.dart';
import 'package:venera/utils/data_sync.dart';
import 'package:venera/utils/ext.dart'; import 'package:venera/utils/ext.dart';
import 'package:venera/utils/io.dart'; import 'package:venera/utils/io.dart';
import 'package:venera/utils/translations.dart'; import 'package:venera/utils/translations.dart';
@@ -32,6 +34,7 @@ class HomePage extends StatelessWidget {
slivers: [ slivers: [
SliverPadding(padding: EdgeInsets.only(top: context.padding.top)), SliverPadding(padding: EdgeInsets.only(top: context.padding.top)),
const _SearchBar(), const _SearchBar(),
const _SyncDataWidget(),
const _History(), const _History(),
const _Local(), const _Local(),
const _ComicSourceWidget(), const _ComicSourceWidget(),
@@ -77,6 +80,97 @@ class _SearchBar extends StatelessWidget {
} }
} }
class _SyncDataWidget extends StatefulWidget {
const _SyncDataWidget();
@override
State<_SyncDataWidget> createState() => _SyncDataWidgetState();
}
class _SyncDataWidgetState extends State<_SyncDataWidget> {
@override
void initState() {
super.initState();
DataSync().addListener(update);
}
void update() {
if(mounted) {
setState(() {});
}
}
@override
void dispose() {
super.dispose();
DataSync().removeListener(update);
}
@override
Widget build(BuildContext context) {
Widget child;
if(!DataSync().isEnabled) {
child = const SliverPadding(padding: EdgeInsets.zero);
} else if (DataSync().isUploading || DataSync().isDownloading) {
child = SliverToBoxAdapter(
child: Container(
margin: const EdgeInsets.symmetric(horizontal: 8, vertical: 8),
decoration: BoxDecoration(
border: Border.all(
color: Theme.of(context).colorScheme.primary,
),
borderRadius: BorderRadius.circular(8),
),
child: ListTile(
leading: const Icon(Icons.sync),
title: Text('Syncing Data'.tl),
trailing: const CircularProgressIndicator(strokeWidth: 2)
.fixWidth(18)
.fixHeight(18),
),
),
);
} else {
child = SliverToBoxAdapter(
child: Container(
margin: const EdgeInsets.symmetric(horizontal: 8, vertical: 8),
decoration: BoxDecoration(
border: Border.all(
color: Theme.of(context).colorScheme.outlineVariant,
),
borderRadius: BorderRadius.circular(8),
),
child: ListTile(
leading: const Icon(Icons.sync),
title: Text('Sync Data'.tl),
trailing: Row(
mainAxisSize: MainAxisSize.min,
children: [
IconButton(
icon: const Icon(Icons.cloud_upload_outlined),
onPressed: () async {
DataSync().uploadData();
}
),
IconButton(
icon: const Icon(Icons.cloud_download_outlined),
onPressed: () async {
DataSync().downloadData();
}
),
],
),
),
),
);
}
return SliverAnimatedPaintExtent(
duration: const Duration(milliseconds: 200),
child: child,
);
}
}
class _History extends StatefulWidget { class _History extends StatefulWidget {
const _History(); const _History();
@@ -529,7 +623,9 @@ class _ImportComicsWidgetState extends State<_ImportComicsWidget> {
await xFile!.saveTo(cache); await xFile!.saveTo(cache);
var comic = await CBZ.import(File(cache)); var comic = await CBZ.import(File(cache));
if (selectedFolder != null) { if (selectedFolder != null) {
LocalFavoritesManager().addComic(selectedFolder!, FavoriteItem( LocalFavoritesManager().addComic(
selectedFolder!,
FavoriteItem(
id: comic.id, id: comic.id,
name: comic.title, name: comic.title,
coverPath: comic.cover, coverPath: comic.cover,
@@ -610,7 +706,9 @@ class _ImportComicsWidgetState extends State<_ImportComicsWidget> {
for (var comic in comics.values) { for (var comic in comics.values) {
LocalManager().add(comic, LocalManager().findValidId(ComicType.local)); LocalManager().add(comic, LocalManager().findValidId(ComicType.local));
if (selectedFolder != null) { if (selectedFolder != null) {
LocalFavoritesManager().addComic(selectedFolder!, FavoriteItem( LocalFavoritesManager().addComic(
selectedFolder!,
FavoriteItem(
id: comic.id, id: comic.id,
name: comic.title, name: comic.title,
coverPath: comic.cover, coverPath: comic.cover,

View File

@@ -78,8 +78,7 @@ class _AppSettingsState extends State<AppSettings> {
appdata.settings['cacheSize'] = int.parse(value); appdata.settings['cacheSize'] = int.parse(value);
appdata.saveData(); appdata.saveData();
setState(() {}); setState(() {});
CacheManager() CacheManager().setLimitSize(appdata.settings['cacheSize']);
.setLimitSize(appdata.settings['cacheSize']);
return null; return null;
}, },
); );
@@ -106,8 +105,7 @@ class _AppSettingsState extends State<AppSettings> {
await file.saveTo(cacheFile.path); await file.saveTo(cacheFile.path);
try { try {
await importAppData(cacheFile); await importAppData(cacheFile);
} } catch (e, s) {
catch(e, s) {
Log.error("Import data", e.toString(), s); Log.error("Import data", e.toString(), s);
context.showMessage(message: "Failed to import data".tl); context.showMessage(message: "Failed to import data".tl);
} }
@@ -116,6 +114,13 @@ class _AppSettingsState extends State<AppSettings> {
}, },
actionTitle: 'Import'.tl, actionTitle: 'Import'.tl,
).toSliver(), ).toSliver(),
_CallbackSetting(
title: "Data Sync".tl,
callback: () async {
showPopUpWidget(context, const _WebdavSetting());
},
actionTitle: 'Set'.tl,
).toSliver(),
_SettingPartTitle( _SettingPartTitle(
title: "Log".tl, title: "Log".tl,
icon: Icons.error_outline, icon: Icons.error_outline,
@@ -271,3 +276,129 @@ class _LogsPageState extends State<LogsPage> {
saveFile(data: utf8.encode(log), filename: 'log.txt'); saveFile(data: utf8.encode(log), filename: 'log.txt');
} }
} }
class _WebdavSetting extends StatefulWidget {
const _WebdavSetting();
@override
State<_WebdavSetting> createState() => _WebdavSettingState();
}
class _WebdavSettingState extends State<_WebdavSetting> {
String url = "";
String user = "";
String pass = "";
bool isTesting = false;
bool upload = true;
@override
void initState() {
super.initState();
if (appdata.settings['webdav'] is! List) {
appdata.settings['webdav'] = [];
}
var configs = appdata.settings['webdav'] as List;
if (configs.whereType<String>().length != 3) {
return;
}
url = configs[0];
user = configs[1];
pass = configs[2];
}
@override
Widget build(BuildContext context) {
return PopUpWidgetScaffold(
title: "Webdav",
body: SingleChildScrollView(
child: Column(
children: [
const SizedBox(height: 12),
TextField(
decoration: const InputDecoration(
labelText: "URL",
border: OutlineInputBorder(),
),
controller: TextEditingController(text: url),
onChanged: (value) => url = value,
),
const SizedBox(height: 12),
TextField(
decoration: InputDecoration(
labelText: "Username".tl,
border: const OutlineInputBorder(),
),
controller: TextEditingController(text: user),
onChanged: (value) => user = value,
),
const SizedBox(height: 12),
TextField(
decoration: InputDecoration(
labelText: "Password".tl,
border: const OutlineInputBorder(),
),
controller: TextEditingController(text: pass),
onChanged: (value) => pass = value,
),
const SizedBox(height: 12),
Row(
children: [
Text("Operation".tl),
Radio<bool>(
groupValue: upload,
value: true,
onChanged: (value) {
setState(() {
upload = value!;
});
},
),
Text("Upload".tl),
Radio<bool>(
groupValue: upload,
value: false,
onChanged: (value) {
setState(() {
upload = value!;
});
},
),
Text("Download".tl),
],
),
const SizedBox(height: 16),
Center(
child: Button.filled(
isLoading: isTesting,
onPressed: () async {
var oldConfig = appdata.settings['webdav'];
appdata.settings['webdav'] = [url, user, pass];
setState(() {
isTesting = true;
});
var testResult = upload
? await DataSync().uploadData()
: await DataSync().downloadData();
if (testResult.error) {
setState(() {
isTesting = false;
});
appdata.settings['webdav'] = oldConfig;
context.showMessage(message: testResult.errorMessage!);
return;
}
appdata.saveData();
context.showMessage(message: "Saved".tl);
App.rootPop();
},
child: Text("Continue".tl),
),
)
],
).paddingHorizontal(16),
),
);
}
}

View File

@@ -15,6 +15,7 @@ import 'package:venera/foundation/local.dart';
import 'package:venera/foundation/log.dart'; import 'package:venera/foundation/log.dart';
import 'package:venera/network/app_dio.dart'; import 'package:venera/network/app_dio.dart';
import 'package:venera/utils/data.dart'; import 'package:venera/utils/data.dart';
import 'package:venera/utils/data_sync.dart';
import 'package:venera/utils/io.dart'; import 'package:venera/utils/io.dart';
import 'package:venera/utils/translations.dart'; import 'package:venera/utils/translations.dart';
import 'package:yaml/yaml.dart'; import 'package:yaml/yaml.dart';

View File

@@ -1,3 +1,4 @@
import 'dart:convert';
import 'dart:isolate'; import 'dart:isolate';
import 'package:venera/foundation/app.dart'; import 'package:venera/foundation/app.dart';
@@ -35,7 +36,7 @@ Future<File> exportAppData() async {
return cacheFile; return cacheFile;
} }
Future<void> importAppData(File file) async { Future<void> importAppData(File file, [bool checkVersion = false]) async {
var cacheDirPath = FilePath.join(App.cachePath, 'temp_data'); var cacheDirPath = FilePath.join(App.cachePath, 'temp_data');
var cacheDir = Directory(cacheDirPath); var cacheDir = Directory(cacheDirPath);
await Isolate.run(() { await Isolate.run(() {
@@ -44,14 +45,21 @@ Future<void> importAppData(File file) async {
var historyFile = cacheDir.joinFile("history.db"); var historyFile = cacheDir.joinFile("history.db");
var localFavoriteFile = cacheDir.joinFile("local_favorite.db"); var localFavoriteFile = cacheDir.joinFile("local_favorite.db");
var appdataFile = cacheDir.joinFile("appdata.json"); var appdataFile = cacheDir.joinFile("appdata.json");
if(checkVersion && appdataFile.existsSync()) {
var data = jsonDecode(await appdataFile.readAsString());
var version = data["settings"]["dataVersion"];
if(version is int && version <= appdata.settings["dataVersion"]) {
return;
}
}
if(await historyFile.exists()) { if(await historyFile.exists()) {
HistoryManager().close(); HistoryManager().close();
await historyFile.copy(FilePath.join(App.dataPath, "history.db")); historyFile.copySync(FilePath.join(App.dataPath, "history.db"));
HistoryManager().init(); HistoryManager().init();
} }
if(await localFavoriteFile.exists()) { if(await localFavoriteFile.exists()) {
LocalFavoritesManager().close(); LocalFavoritesManager().close();
await localFavoriteFile.copy(FilePath.join(App.dataPath, "local_favorite.db")); localFavoriteFile.copySync(FilePath.join(App.dataPath, "local_favorite.db"));
LocalFavoritesManager().init(); LocalFavoritesManager().init();
} }
if(await appdataFile.exists()) { if(await appdataFile.exists()) {

205
lib/utils/data_sync.dart Normal file
View File

@@ -0,0 +1,205 @@
import 'package:dio/io.dart';
import 'package:flutter/foundation.dart';
import 'package:venera/foundation/app.dart';
import 'package:venera/foundation/appdata.dart';
import 'package:venera/foundation/comic_source/comic_source.dart';
import 'package:venera/foundation/favorites.dart';
import 'package:venera/foundation/history.dart';
import 'package:venera/foundation/log.dart';
import 'package:venera/foundation/res.dart';
import 'package:venera/network/app_dio.dart';
import 'package:venera/utils/data.dart';
import 'package:venera/utils/ext.dart';
import 'package:webdav_client/webdav_client.dart' hide File;
import 'io.dart';
class DataSync with ChangeNotifier {
DataSync._() {
if (isEnabled) {
downloadData();
}
HistoryManager().addListener(onDataChanged);
LocalFavoritesManager().addListener(onDataChanged);
ComicSource.addListener(onDataChanged);
}
void onDataChanged() {
if (isEnabled) {
uploadData();
}
}
static DataSync? instance;
factory DataSync() => instance ?? (instance = DataSync._());
bool isDownloading = false;
bool isUploading = false;
bool haveWaitingTask = false;
bool get isEnabled {
var config = appdata.settings['webdav'];
return config is List && config.isNotEmpty;
}
List<String>? _validateConfig() {
var config = appdata.settings['webdav'];
if (config is! List || (config.isNotEmpty && config.length != 3)) {
return null;
}
if (config.whereType<String>().length != 3) {
return null;
}
return List.from(config);
}
Future<Res<bool>> uploadData() async {
if (haveWaitingTask) return const Res(true);
while (isDownloading || isUploading) {
haveWaitingTask = true;
await Future.delayed(const Duration(milliseconds: 100));
}
haveWaitingTask = false;
isUploading = true;
notifyListeners();
try {
var config = _validateConfig();
if (config == null) {
return const Res.error('Invalid WebDAV configuration');
}
if (config.isEmpty) {
return const Res(true);
}
String url = config[0];
String user = config[1];
String pass = config[2];
var proxy = await AppDio.getProxy();
var client = newClient(
url,
user: user,
password: pass,
adapter: IOHttpClientAdapter(
createHttpClient: () {
return HttpClient()
..findProxy = (uri) => proxy == null ? "DIRECT" : "PROXY $proxy";
},
),
);
try {
await client.ping();
} catch (e) {
Log.error("Upload Data", 'Failed to connect to WebDAV server');
return const Res.error('Failed to connect to WebDAV server');
}
try {
appdata.settings['dataVersion']++;
await appdata.saveData();
var data = await exportAppData();
var time =
(DateTime.now().millisecondsSinceEpoch ~/ 86400000).toString();
var filename = time;
filename += '-';
filename += appdata.settings['dataVersion'].toString();
filename += '.venera';
var files = await client.readDir('/');
files = files.where((e) => e.name!.endsWith('.venera')).toList();
var old = files.firstWhereOrNull( (e) => e.name!.startsWith("$time-"));
if (old != null) {
await client.remove(old.name!);
}
if (files.length >= 10) {
files.sort((a, b) => a.name!.compareTo(b.name!));
await client.remove(files.first.name!);
}
await client.write(filename, await data.readAsBytes());
Log.info("Upload Data", "Data uploaded successfully");
return const Res(true);
} catch (e, s) {
Log.error("Upload Data", e, s);
return Res.error(e.toString());
}
} finally {
isUploading = false;
notifyListeners();
}
}
Future<Res<bool>> downloadData() async {
if (haveWaitingTask) return const Res(true);
while (isDownloading || isUploading) {
haveWaitingTask = true;
await Future.delayed(const Duration(milliseconds: 100));
}
haveWaitingTask = false;
isDownloading = true;
notifyListeners();
try {
var config = _validateConfig();
if (config == null) {
return const Res.error('Invalid WebDAV configuration');
}
if (config.isEmpty) {
return const Res(true);
}
String url = config[0];
String user = config[1];
String pass = config[2];
var proxy = await AppDio.getProxy();
var client = newClient(
url,
user: user,
password: pass,
adapter: IOHttpClientAdapter(
createHttpClient: () {
return HttpClient()
..findProxy = (uri) => proxy == null ? "DIRECT" : "PROXY $proxy";
},
),
);
try {
await client.ping();
} catch (e) {
Log.error("Data Sync", 'Failed to connect to WebDAV server');
return const Res.error('Failed to connect to WebDAV server');
}
try {
var files = await client.readDir('/');
files.sort((a, b) => b.name!.compareTo(a.name!));
var file = files.firstWhereOrNull((e) => e.name!.endsWith('.venera'));
var version =
file!.name!.split('-').elementAtOrNull(1)?.split('.').first;
if (version != null && int.tryParse(version) != null) {
var currentVersion = appdata.settings['dataVersion'];
if (currentVersion != null && int.parse(version) <= currentVersion) {
Log.info("Data Sync", 'No new data to download');
return const Res(true);
}
}
Log.info("Data Sync", "Downloading data from WebDAV server");
var localFile = File(FilePath.join(App.cachePath, file.name!));
await client.read2File(file.name!, localFile.path);
await importAppData(localFile, true);
await localFile.delete();
Log.info("Data Sync", "Data downloaded successfully");
return const Res(true);
} catch (e, s) {
Log.error("Data Sync", e, s);
return Res.error(e.toString());
}
} finally {
isDownloading = false;
notifyListeners();
}
}
}

View File

@@ -10,7 +10,7 @@
#include <file_selector_linux/file_selector_plugin.h> #include <file_selector_linux/file_selector_plugin.h>
#include <flutter_qjs/flutter_qjs_plugin.h> #include <flutter_qjs/flutter_qjs_plugin.h>
#include <gtk/gtk_plugin.h> #include <gtk/gtk_plugin.h>
#include <screen_retriever/screen_retriever_plugin.h> #include <screen_retriever_linux/screen_retriever_linux_plugin.h>
#include <sqlite3_flutter_libs/sqlite3_flutter_libs_plugin.h> #include <sqlite3_flutter_libs/sqlite3_flutter_libs_plugin.h>
#include <url_launcher_linux/url_launcher_plugin.h> #include <url_launcher_linux/url_launcher_plugin.h>
#include <window_manager/window_manager_plugin.h> #include <window_manager/window_manager_plugin.h>
@@ -28,9 +28,9 @@ void fl_register_plugins(FlPluginRegistry* registry) {
g_autoptr(FlPluginRegistrar) gtk_registrar = g_autoptr(FlPluginRegistrar) gtk_registrar =
fl_plugin_registry_get_registrar_for_plugin(registry, "GtkPlugin"); fl_plugin_registry_get_registrar_for_plugin(registry, "GtkPlugin");
gtk_plugin_register_with_registrar(gtk_registrar); gtk_plugin_register_with_registrar(gtk_registrar);
g_autoptr(FlPluginRegistrar) screen_retriever_registrar = g_autoptr(FlPluginRegistrar) screen_retriever_linux_registrar =
fl_plugin_registry_get_registrar_for_plugin(registry, "ScreenRetrieverPlugin"); fl_plugin_registry_get_registrar_for_plugin(registry, "ScreenRetrieverLinuxPlugin");
screen_retriever_plugin_register_with_registrar(screen_retriever_registrar); screen_retriever_linux_plugin_register_with_registrar(screen_retriever_linux_registrar);
g_autoptr(FlPluginRegistrar) sqlite3_flutter_libs_registrar = g_autoptr(FlPluginRegistrar) sqlite3_flutter_libs_registrar =
fl_plugin_registry_get_registrar_for_plugin(registry, "Sqlite3FlutterLibsPlugin"); fl_plugin_registry_get_registrar_for_plugin(registry, "Sqlite3FlutterLibsPlugin");
sqlite3_flutter_libs_plugin_register_with_registrar(sqlite3_flutter_libs_registrar); sqlite3_flutter_libs_plugin_register_with_registrar(sqlite3_flutter_libs_registrar);

View File

@@ -7,7 +7,7 @@ list(APPEND FLUTTER_PLUGIN_LIST
file_selector_linux file_selector_linux
flutter_qjs flutter_qjs
gtk gtk
screen_retriever screen_retriever_linux
sqlite3_flutter_libs sqlite3_flutter_libs
url_launcher_linux url_launcher_linux
window_manager window_manager

View File

@@ -10,7 +10,7 @@ import desktop_webview_window
import file_selector_macos import file_selector_macos
import flutter_inappwebview_macos import flutter_inappwebview_macos
import path_provider_foundation import path_provider_foundation
import screen_retriever import screen_retriever_macos
import share_plus import share_plus
import sqlite3_flutter_libs import sqlite3_flutter_libs
import url_launcher_macos import url_launcher_macos
@@ -22,7 +22,7 @@ func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
FileSelectorPlugin.register(with: registry.registrar(forPlugin: "FileSelectorPlugin")) FileSelectorPlugin.register(with: registry.registrar(forPlugin: "FileSelectorPlugin"))
InAppWebViewFlutterPlugin.register(with: registry.registrar(forPlugin: "InAppWebViewFlutterPlugin")) InAppWebViewFlutterPlugin.register(with: registry.registrar(forPlugin: "InAppWebViewFlutterPlugin"))
PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin")) PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin"))
ScreenRetrieverPlugin.register(with: registry.registrar(forPlugin: "ScreenRetrieverPlugin")) ScreenRetrieverMacosPlugin.register(with: registry.registrar(forPlugin: "ScreenRetrieverMacosPlugin"))
SharePlusMacosPlugin.register(with: registry.registrar(forPlugin: "SharePlusMacosPlugin")) SharePlusMacosPlugin.register(with: registry.registrar(forPlugin: "SharePlusMacosPlugin"))
Sqlite3FlutterLibsPlugin.register(with: registry.registrar(forPlugin: "Sqlite3FlutterLibsPlugin")) Sqlite3FlutterLibsPlugin.register(with: registry.registrar(forPlugin: "Sqlite3FlutterLibsPlugin"))
UrlLauncherPlugin.register(with: registry.registrar(forPlugin: "UrlLauncherPlugin")) UrlLauncherPlugin.register(with: registry.registrar(forPlugin: "UrlLauncherPlugin"))

View File

@@ -593,6 +593,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.3.0" version: "2.3.0"
petitparser:
dependency: transitive
description:
name: petitparser
sha256: c15605cd28af66339f8eb6fbe0e541bfe2d1b72d5825efc6598f3e0a31b9ad27
url: "https://pub.dev"
source: hosted
version: "6.0.2"
photo_view: photo_view:
dependency: "direct main" dependency: "direct main"
description: description:
@@ -888,6 +896,15 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.1.0" version: "1.1.0"
webdav_client:
dependency: "direct main"
description:
path: "."
ref: "285f87f15bccd2d5d5ff443761348c6ee47b98d1"
resolved-ref: "285f87f15bccd2d5d5ff443761348c6ee47b98d1"
url: "https://github.com/wgh136/webdav_client"
source: git
version: "1.2.2"
win32: win32:
dependency: transitive dependency: transitive
description: description:
@@ -912,6 +929,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.0.4" version: "1.0.4"
xml:
dependency: transitive
description:
name: xml
sha256: b015a8ad1c488f66851d762d3090a21c600e479dc75e68328c52774040cf9226
url: "https://pub.dev"
source: hosted
version: "6.5.0"
yaml: yaml:
dependency: "direct main" dependency: "direct main"
description: description:

View File

@@ -59,6 +59,10 @@ dependencies:
url: https://github.com/venera-app/lodepng_flutter url: https://github.com/venera-app/lodepng_flutter
ref: d1c96cd6503103b3270dfe2f320d4a1c93780f53 ref: d1c96cd6503103b3270dfe2f320d4a1c93780f53
rhttp: 0.9.1 rhttp: 0.9.1
webdav_client:
git:
url: https://github.com/wgh136/webdav_client
ref: 285f87f15bccd2d5d5ff443761348c6ee47b98d1
dev_dependencies: dev_dependencies:
flutter_test: flutter_test:

View File

@@ -11,7 +11,7 @@
#include <file_selector_windows/file_selector_windows.h> #include <file_selector_windows/file_selector_windows.h>
#include <flutter_inappwebview_windows/flutter_inappwebview_windows_plugin_c_api.h> #include <flutter_inappwebview_windows/flutter_inappwebview_windows_plugin_c_api.h>
#include <flutter_qjs/flutter_qjs_plugin.h> #include <flutter_qjs/flutter_qjs_plugin.h>
#include <screen_retriever/screen_retriever_plugin.h> #include <screen_retriever_windows/screen_retriever_windows_plugin_c_api.h>
#include <share_plus/share_plus_windows_plugin_c_api.h> #include <share_plus/share_plus_windows_plugin_c_api.h>
#include <sqlite3_flutter_libs/sqlite3_flutter_libs_plugin.h> #include <sqlite3_flutter_libs/sqlite3_flutter_libs_plugin.h>
#include <url_launcher_windows/url_launcher_windows.h> #include <url_launcher_windows/url_launcher_windows.h>
@@ -28,8 +28,8 @@ void RegisterPlugins(flutter::PluginRegistry* registry) {
registry->GetRegistrarForPlugin("FlutterInappwebviewWindowsPluginCApi")); registry->GetRegistrarForPlugin("FlutterInappwebviewWindowsPluginCApi"));
FlutterQjsPluginRegisterWithRegistrar( FlutterQjsPluginRegisterWithRegistrar(
registry->GetRegistrarForPlugin("FlutterQjsPlugin")); registry->GetRegistrarForPlugin("FlutterQjsPlugin"));
ScreenRetrieverPluginRegisterWithRegistrar( ScreenRetrieverWindowsPluginCApiRegisterWithRegistrar(
registry->GetRegistrarForPlugin("ScreenRetrieverPlugin")); registry->GetRegistrarForPlugin("ScreenRetrieverWindowsPluginCApi"));
SharePlusWindowsPluginCApiRegisterWithRegistrar( SharePlusWindowsPluginCApiRegisterWithRegistrar(
registry->GetRegistrarForPlugin("SharePlusWindowsPluginCApi")); registry->GetRegistrarForPlugin("SharePlusWindowsPluginCApi"));
Sqlite3FlutterLibsPluginRegisterWithRegistrar( Sqlite3FlutterLibsPluginRegisterWithRegistrar(

View File

@@ -8,7 +8,7 @@ list(APPEND FLUTTER_PLUGIN_LIST
file_selector_windows file_selector_windows
flutter_inappwebview_windows flutter_inappwebview_windows
flutter_qjs flutter_qjs
screen_retriever screen_retriever_windows
share_plus share_plus
sqlite3_flutter_libs sqlite3_flutter_libs
url_launcher_windows url_launcher_windows