diff --git a/.github/workflows/Release.yml b/.github/workflows/Release.yml index 31082dc..6d51e6d 100644 --- a/.github/workflows/Release.yml +++ b/.github/workflows/Release.yml @@ -4,8 +4,8 @@ on: workflow_dispatch: env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - RUST_TOOLCHAIN: nightly-2023-10-31 + GH_TOKEN: ${{ secrets.GH_TOKEN }} + RUST_TOOLCHAIN: nightly-2024-04-15 NDK_VERSION: 25.2.9519653 jobs: @@ -16,6 +16,7 @@ jobs: needs: - check_release - build_release_assets + - community_notify steps: - run: exit 0 @@ -324,3 +325,36 @@ jobs: cd ci cargo run --bin upload-asset + community_notify: + name: community notify + runs-on: ubuntu-latest + needs: + - build_release_assets + env: + TG_BOT_TOKEN: ${{ secrets.TG_BOT_TOKEN }} + TG_CHAT_IDS: ${{ secrets.TG_CHAT_IDS }} + DISCORD_BOT_TOKEN: ${{ secrets.DISCORD_BOT_TOKEN }} + DISCORD_CHAT_IDS: ${{ secrets.DISCORD_CHAT_IDS }} + steps: + + - name: Cargo cache + uses: actions/cache@v3 + with: + path: | + ~/.cargo/bin/ + ~/.cargo/registry/index/ + ~/.cargo/registry/cache/ + ~/.cargo/git/db/ + key: ${{ runner.os }}-cargo- + + - name: Install rust toolchain + uses: actions-rs/toolchain@v1 + with: + toolchain: ${{ env.RUST_TOOLCHAIN }} + + - name: Checkout + uses: actions/checkout@v3 + + - run: | + cd ci + cargo run --bin community-notify diff --git a/android/build.gradle b/android/build.gradle index 58a8c74..713d7f6 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -26,6 +26,6 @@ subprojects { project.evaluationDependsOn(':app') } -task clean(type: Delete) { +tasks.register("clean", Delete) { delete rootProject.buildDir } diff --git a/ci/src/check_asset/main.rs b/ci/src/check_asset/main.rs index 3e2f5a6..42a843d 100644 --- a/ci/src/check_asset/main.rs +++ b/ci/src/check_asset/main.rs @@ -9,9 +9,9 @@ const UA: &str = "niuhuan jasmine ci"; #[tokio::main] async fn main() -> Result<()> { - let gh_token = std::env::var("GITHUB_TOKEN")?; + let gh_token = std::env::var("GH_TOKEN")?; if gh_token.is_empty() { - panic!("Please set GITHUB_TOKEN"); + panic!("Please set GH_TOKEN"); } let flutter_version = std::env::var("flutter_version")?; if flutter_version.is_empty() { diff --git a/ci/src/check_release/main.rs b/ci/src/check_release/main.rs index 75af5d1..db35573 100644 --- a/ci/src/check_release/main.rs +++ b/ci/src/check_release/main.rs @@ -10,9 +10,9 @@ const MAIN_BRANCH: &str = "master"; #[tokio::main] async fn main() -> Result<()> { // get ghToken - let gh_token = std::env::var("GITHUB_TOKEN")?; + let gh_token = std::env::var("GH_TOKEN")?; if gh_token.is_empty() { - panic!("Please set GITHUB_TOKEN"); + panic!("Please set GH_TOKEN"); } let vs_code_txt = tokio::fs::read_to_string("version.code.txt").await?; diff --git a/ci/src/upload_asset/main.rs b/ci/src/upload_asset/main.rs index 6e8f497..79e03ad 100644 --- a/ci/src/upload_asset/main.rs +++ b/ci/src/upload_asset/main.rs @@ -10,7 +10,7 @@ const UA: &str = "niuhuan jasmine ci"; #[tokio::main] async fn main() -> Result<()> { // get ghToken - let gh_token = std::env::var("GITHUB_TOKEN")?; + let gh_token = std::env::var("GH_TOKEN")?; let target = std::env::var("TARGET")?; let flutter_version = std::env::var("flutter_version")?; diff --git a/ci/version.code.txt b/ci/version.code.txt index d4f6e2c..cea0f99 100644 --- a/ci/version.code.txt +++ b/ci/version.code.txt @@ -1 +1 @@ -v1.6.2 +v1.6.6 \ No newline at end of file diff --git a/ci/version.info.txt b/ci/version.info.txt index d4f6e2c..8b94980 100644 --- a/ci/version.info.txt +++ b/ci/version.info.txt @@ -1 +1,3 @@ -v1.6.2 +v1.6.1 + +- [x] ♻️ Network diff --git a/ios/Podfile.lock b/ios/Podfile.lock index ac9b764..c7d96a5 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -34,7 +34,7 @@ PODS: - DKImagePickerController/PhotoGallery - Flutter - Flutter (1.0.0) - - permission_handler_apple (9.0.4): + - permission_handler_apple (9.1.1): - Flutter - SDWebImage (5.13.4): - SDWebImage/Core (= 5.13.4) @@ -71,11 +71,11 @@ SPEC CHECKSUMS: DKPhotoGallery: fdfad5125a9fdda9cc57df834d49df790dbb4179 file_picker: 817ab1d8cd2da9d2da412a417162deee3500fc95 Flutter: f04841e97a9d0b0a8025694d0796dd46242b2854 - permission_handler_apple: 44366e37eaf29454a1e7b1b7d736c2cceaeb17ce + permission_handler_apple: e76247795d700c14ea09e3a2d8855d41ee80a2e6 SDWebImage: e5cc87bf736e60f49592f307bdf9e157189298a3 SwiftyGif: 6c3eafd0ce693cad58bb63d2b2fb9bacb8552780 url_launcher_ios: ae1517e5e344f5544fb090b079e11f399dfbe4d2 PODFILE CHECKSUM: ef19549a9bc3046e7bb7d2fab4d021637c0c58a3 -COCOAPODS: 1.11.3 +COCOAPODS: 1.12.1 diff --git a/lib/assets/ic_launcher.png b/lib/assets/ic_launcher.png new file mode 100644 index 0000000..41aabb5 Binary files /dev/null and b/lib/assets/ic_launcher.png differ diff --git a/lib/basic/entities.dart b/lib/basic/entities.dart index ab5b7bf..0049bbf 100644 --- a/lib/basic/entities.dart +++ b/lib/basic/entities.dart @@ -685,7 +685,9 @@ class SelfInfo { expPercent = json['expPercent']; badges = List.castFrom(json['badges']); albumFavoritesMax = json['album_favorites_max']; - favoriteList = List.from(json['favorite_list']).map((e)=>FavoriteFolder.fromJson(e)).toList(); + favoriteList = List.from(json['favorite_list']) + .map((e) => FavoriteFolder.fromJson(e)) + .toList(); } Map toJson() { @@ -708,7 +710,7 @@ class SelfInfo { _data['expPercent'] = expPercent; _data['badges'] = badges; _data['album_favorites_max'] = albumFavoritesMax; - _data['favorite_list'] = favoriteList.map((e)=>e.toJson()).toList(); + _data['favorite_list'] = favoriteList.map((e) => e.toJson()).toList(); return _data; } } @@ -1211,7 +1213,7 @@ ComicBasic albumToSimple(AlbumResponse album) { description: album.description, name: album.name, author: album.author.join(" / "), - image: album.images[0] ?? '', + image: album.images.isEmpty ? '' : album.images[0] ?? '', ); } diff --git a/lib/basic/methods.dart b/lib/basic/methods.dart index cbf2f70..36868a4 100644 --- a/lib/basic/methods.dart +++ b/lib/basic/methods.dart @@ -454,10 +454,15 @@ class Methods { } Future ping(String idx) async { - print("PING $idx"); + print("PING API $idx"); return int.parse(await _invoke("ping_server", idx)); } + Future pingCdn(String idx) async { + print("PING CDN $idx"); + return int.parse(await _invoke("ping_cdn", idx)); + } + Future mkdirs(String path) { return _invoke("mkdirs", path); } diff --git a/lib/configs/network_api_host.dart b/lib/configs/network_api_host.dart index 78ec943..9baa209 100644 --- a/lib/configs/network_api_host.dart +++ b/lib/configs/network_api_host.dart @@ -1,5 +1,4 @@ import 'package:flutter/material.dart'; -import 'package:jasmine/basic/commons.dart'; import 'package:jasmine/basic/methods.dart'; late String _apiHost; @@ -9,6 +8,7 @@ const apiHosts = { "分流1": "1", "分流2": "2", "分流3": "3", + "分流4": "4", }; String _apiHostName(String value) { diff --git a/lib/configs/network_cdn_host.dart b/lib/configs/network_cdn_host.dart index 42b9d45..d2cce2d 100644 --- a/lib/configs/network_cdn_host.dart +++ b/lib/configs/network_cdn_host.dart @@ -1,5 +1,4 @@ import 'package:flutter/material.dart'; -import 'package:jasmine/basic/commons.dart'; import 'package:jasmine/basic/methods.dart'; late String _cdnHost; @@ -15,15 +14,7 @@ Future initCdnHost() async { } Future chooseCdnHost(BuildContext context) async { - final choose = await chooseMapDialog( - context, - title: "图片分流", - values: { - "随机": "0", - "分流1": "1", - "分流2": "2", - }, - ); + final choose = await chooseCdnDialog(context); if (choose != null) { await methods.saveCdnHost(choose); _cdnHost = choose; @@ -44,3 +35,127 @@ Widget cdnHostSetting() { }, ); } + + +var cdnHosts = { +"随机": "0", +"分流1": "1", +"分流2": "2", +"分流3": "3", +"分流4": "4", +}; + +Future chooseCdnDialog(BuildContext buildContext) async { + return await showDialog( + context: buildContext, + builder: (BuildContext context) { + return SimpleDialog( + title: const Text("图片分流"), + children: cdnHosts.entries + .map( + (e) => SimpleDialogOption( + child: CdnOptionRow( + e.key, + e.value, + key: Key("CDN:${e.value}"), + ), + onPressed: () { + Navigator.of(context).pop(e.value); + }, + ), + ) + .toList(), + ); + }, + ); +} + +class CdnOptionRow extends StatefulWidget { + final String title; + final String value; + + const CdnOptionRow(this.title, this.value, {Key? key}) : super(key: key); + + @override + State createState() => _CdnOptionRowState(); +} + +class _CdnOptionRowState extends State { + late Future _feature; + + @override + void initState() { + super.initState(); + _feature = methods.pingCdn(widget.value); + } + + @override + Widget build(BuildContext context) { + return Row( + children: [ + Text(widget.title), + Expanded(child: Container()), + FutureBuilder( + future: _feature, + builder: ( + BuildContext context, + AsyncSnapshot snapshot, + ) { + if (snapshot.connectionState != ConnectionState.done) { + return const PingStatus( + "测速中", + Colors.blue, + ); + } + if (snapshot.hasError) { + return const PingStatus( + "失败", + Colors.red, + ); + } + int ping = snapshot.requireData; + if (ping <= 200) { + return PingStatus( + "${ping}ms", + Colors.green, + ); + } + if (ping <= 500) { + return PingStatus( + "${ping}ms", + Colors.yellow, + ); + } + return PingStatus( + "${ping}ms", + Colors.orange, + ); + }, + ), + ], + ); + } +} + +class PingStatus extends StatelessWidget { + final String title; + final Color color; + + const PingStatus(this.title, this.color, {Key? key}) : super(key: key); + + @override + Widget build(BuildContext context) { + return Row( + children: [ + Text( + '\u2022', + style: TextStyle( + color: color, + ), + ), + Text(" $title"), + ], + ); + } +} + diff --git a/lib/screens/about_screen.dart b/lib/screens/about_screen.dart index da2e368..56bbb08 100644 --- a/lib/screens/about_screen.dart +++ b/lib/screens/about_screen.dart @@ -54,22 +54,30 @@ class _AboutState extends State { builder: (BuildContext context, BoxConstraints constraints) { double? width, height; if (constraints.maxWidth < constraints.maxHeight) { - width = constraints.maxWidth / 2; + width = constraints.maxWidth / 3; } else { - height = constraints.maxHeight / 2; + height = constraints.maxHeight / 3; } - return Container( - padding: const EdgeInsets.all(10), - child: Center( - child: SizedBox( - width: width, - height: height, - child: Image.asset( - "lib/assets/startup.png", - fit: BoxFit.contain, + double l = width ?? height!; + return Column( + children: [ + Container(height: l / 4), + SizedBox( + width: l, + height: l, + child: ConstrainedBox( + constraints: const BoxConstraints.expand(), + child: Container( + padding: const EdgeInsets.all(10), + child: Image.asset( + "lib/assets/ic_launcher.png", + fit: BoxFit.contain, + ), + ), ), ), - ), + Container(height: l / 4), + ], ); }, ); diff --git a/lib/screens/components/comic_comments_list.dart b/lib/screens/components/comic_comments_list.dart index 626e1ad..fe45cae 100644 --- a/lib/screens/components/comic_comments_list.dart +++ b/lib/screens/components/comic_comments_list.dart @@ -144,16 +144,17 @@ Widget _buildComment( if (!jumpList) { return; } - if (comment.AID != null) {} - Navigator.of(context).push( - MaterialPageRoute( - builder: (context) => _CommentChildrenScreen( - aid: comment.AID!, - mode: mode, - comment: comment, + if (comment.AID != null) { + Navigator.of(context).push( + MaterialPageRoute( + builder: (context) => _CommentChildrenScreen( + aid: comment.AID!, + mode: mode, + comment: comment, + ), ), - ), - ); + ); + } }, child: _ComicCommentItem( aid: comment.AID, @@ -296,7 +297,7 @@ class _ComicCommentItemState extends State<_ComicCommentItem> { crossAxisAlignment: WrapCrossAlignment.center, alignment: WrapAlignment.spaceBetween, children: [ - Text("Lv. ? (?)", style: levelStyle), + Text("Lv.${comment.expinfo.level}", style: levelStyle), Text.rich(TextSpan( style: levelStyle, children: [ @@ -387,9 +388,25 @@ class _ComicCommentItemState extends State<_ComicCommentItem> { Container(height: 5), GestureDetector( onLongPress: () { - confirmCopy(context, comment.content); + confirmCopy( + context, + comment.content + .replaceAll( + "
", + "", + ) + .replaceAll("
", ""), + ); }, - child: Text(comment.content, style: connectStyle), + child: Text( + comment.content + .replaceAll( + "
", + "", + ) + .replaceAll("
", ""), + style: connectStyle, + ), ), ...widget.gotoComic ? [ diff --git a/lib/screens/init_screen.dart b/lib/screens/init_screen.dart index cefff76..0b9bf12 100644 --- a/lib/screens/init_screen.dart +++ b/lib/screens/init_screen.dart @@ -32,27 +32,25 @@ class _InitScreenState extends State { Widget build(BuildContext context) { return Scaffold( backgroundColor: const Color(0xffeeeeee), - body: Stack( - children: [ - Container( - decoration: const BoxDecoration( - image: DecorationImage( - image: AssetImage('lib/assets/backpoint.png'), - repeat: ImageRepeat.repeat, + body: LayoutBuilder( + builder: (BuildContext context, BoxConstraints constraints) { + return Center( + child: SizedBox( + width: constraints.maxWidth / 2, + height: constraints.maxHeight / 2, + child: ConstrainedBox( + constraints: const BoxConstraints.expand(), + child: Container( + padding: const EdgeInsets.all(10), + child: Image.asset( + "lib/assets/ic_launcher.png", + fit: BoxFit.contain, + ), + ), ), ), - ), - ConstrainedBox( - constraints: const BoxConstraints.expand(), - child: Container( - padding: const EdgeInsets.all(10), - child: Image.asset( - "lib/assets/startup.png", - fit: BoxFit.contain, - ), - ), - ), - ], + ); + }, ), ); } @@ -62,14 +60,34 @@ class _InitScreenState extends State { await methods.init(); await initConfigs(); print("STATE : ${loginStatus}"); - Future.delayed(Duration.zero, () async { - await webDavSyncAuto(context); - Navigator.of(context).pushReplacement( - MaterialPageRoute(builder: (BuildContext context) { - return const AppScreen(); - }), - ); - }); + if (!currentPassed()) { + Future.delayed(Duration.zero, () async { + await webDavSyncAuto(context); + Navigator.of(context).pushReplacement( + MaterialPageRoute(builder: (BuildContext context) { + return const CalculatorScreen(); + }), + ); + }); + } else if (loginStatus == LoginStatus.notSet) { + Future.delayed(Duration.zero, () async { + await webDavSyncAuto(context); + Navigator.of(context).pushReplacement( + MaterialPageRoute(builder: (BuildContext context) { + return firstLoginScreen; + }), + ); + }); + } else { + Future.delayed(Duration.zero, () async { + await webDavSyncAuto(context); + Navigator.of(context).pushReplacement( + MaterialPageRoute(builder: (BuildContext context) { + return const AppScreen(); + }), + ); + }); + } } catch (e, st) { print("$e\n$st"); defaultToast(context, "初始化失败, 请设置网络"); diff --git a/macos/Podfile b/macos/Podfile index dade8df..049abe2 100644 --- a/macos/Podfile +++ b/macos/Podfile @@ -1,4 +1,4 @@ -platform :osx, '10.11' +platform :osx, '10.14' # CocoaPods analytics sends network stats synchronously affecting flutter build latency. ENV['COCOAPODS_DISABLE_STATS'] = 'true' diff --git a/macos/Podfile.lock b/macos/Podfile.lock index 137814e..dc6fbd6 100644 --- a/macos/Podfile.lock +++ b/macos/Podfile.lock @@ -1,22 +1,34 @@ PODS: - FlutterMacOS (1.0.0) + - screen_retriever (0.0.1): + - FlutterMacOS - url_launcher_macos (0.0.1): - FlutterMacOS + - window_manager (0.2.0): + - FlutterMacOS DEPENDENCIES: - FlutterMacOS (from `Flutter/ephemeral`) + - screen_retriever (from `Flutter/ephemeral/.symlinks/plugins/screen_retriever/macos`) - url_launcher_macos (from `Flutter/ephemeral/.symlinks/plugins/url_launcher_macos/macos`) + - window_manager (from `Flutter/ephemeral/.symlinks/plugins/window_manager/macos`) EXTERNAL SOURCES: FlutterMacOS: :path: Flutter/ephemeral + screen_retriever: + :path: Flutter/ephemeral/.symlinks/plugins/screen_retriever/macos url_launcher_macos: :path: Flutter/ephemeral/.symlinks/plugins/url_launcher_macos/macos + window_manager: + :path: Flutter/ephemeral/.symlinks/plugins/window_manager/macos SPEC CHECKSUMS: - FlutterMacOS: ae6af50a8ea7d6103d888583d46bd8328a7e9811 - url_launcher_macos: 597e05b8e514239626bcf4a850fcf9ef5c856ec3 + FlutterMacOS: 8f6f14fa908a6fb3fba0cd85dbd81ec4b251fb24 + screen_retriever: 59634572a57080243dd1bf715e55b6c54f241a38 + url_launcher_macos: c04e4fa86382d4f94f6b38f14625708be3ae52e2 + window_manager: 3a1844359a6295ab1e47659b1a777e36773cd6e8 -PODFILE CHECKSUM: 6eac6b3292e5142cfc23bdeb71848a40ec51c14c +PODFILE CHECKSUM: 353c8bcc5d5b0994e508d035b5431cfe18c1dea7 -COCOAPODS: 1.11.3 +COCOAPODS: 1.12.1 diff --git a/macos/Runner.xcodeproj/project.pbxproj b/macos/Runner.xcodeproj/project.pbxproj index c80eb11..32204e6 100644 --- a/macos/Runner.xcodeproj/project.pbxproj +++ b/macos/Runner.xcodeproj/project.pbxproj @@ -3,7 +3,7 @@ archiveVersion = 1; classes = { }; - objectVersion = 51; + objectVersion = 54; objects = { /* Begin PBXAggregateTarget section */ @@ -218,7 +218,7 @@ isa = PBXProject; attributes = { LastSwiftUpdateCheck = 0920; - LastUpgradeCheck = 1300; + LastUpgradeCheck = 1430; ORGANIZATIONNAME = ""; TargetAttributes = { 33CC10EC2044A3C60003C045 = { @@ -271,6 +271,7 @@ /* Begin PBXShellScriptBuildPhase section */ 3399D490228B24CF009A79C7 /* ShellScript */ = { isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; buildActionMask = 2147483647; files = ( ); @@ -446,7 +447,7 @@ "$(inherited)", "$(PROJECT_DIR)", ); - MACOSX_DEPLOYMENT_TARGET = 10.13; + MACOSX_DEPLOYMENT_TARGET = 10.14; PRODUCT_BUNDLE_IDENTIFIER = opensource.jasmine; PROVISIONING_PROFILE_SPECIFIER = ""; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; @@ -580,7 +581,7 @@ "$(inherited)", "$(PROJECT_DIR)", ); - MACOSX_DEPLOYMENT_TARGET = 10.13; + MACOSX_DEPLOYMENT_TARGET = 10.14; PRODUCT_BUNDLE_IDENTIFIER = opensource.jasmine; PROVISIONING_PROFILE_SPECIFIER = ""; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; @@ -607,7 +608,7 @@ "$(inherited)", "$(PROJECT_DIR)", ); - MACOSX_DEPLOYMENT_TARGET = 10.13; + MACOSX_DEPLOYMENT_TARGET = 10.14; PRODUCT_BUNDLE_IDENTIFIER = opensource.jasmine; PROVISIONING_PROFILE_SPECIFIER = ""; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; diff --git a/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme index a3e3245..638bbd3 100644 --- a/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme +++ b/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -1,6 +1,6 @@ ( - mut env: JNIEnv<'local>, - _class: JClass<'local>, - params: JString<'local>, -) { - let params: String = env.get_string(¶ms).unwrap().into(); - init_sync(params.as_str()); -} - -#[no_mangle] -pub unsafe extern "C" fn Java_opensource_jenny_Jni_invoke<'local>( - mut env: JNIEnv<'local>, - _: JClass<'local>, - params: JString<'local>, -) -> JString<'local> { - let params: String = env.get_string(¶ms).unwrap().into(); - env.new_string(invoke(params.as_str())).unwrap() -} diff --git a/native/jmbackend/platforms/ios-sim/Cargo.toml b/native/jmbackend/platforms/ios-sim/Cargo.toml deleted file mode 100644 index 452c3c2..0000000 --- a/native/jmbackend/platforms/ios-sim/Cargo.toml +++ /dev/null @@ -1,10 +0,0 @@ -[package] -name = "rust" -version = "0.1.0" -edition = "2021" - -[dependencies] -jmbackend = { path = "../../" } - -[lib] -crate-type = ["staticlib"] diff --git a/native/jmbackend/platforms/ios-sim/src/lib.rs b/native/jmbackend/platforms/ios-sim/src/lib.rs deleted file mode 100644 index a70214d..0000000 --- a/native/jmbackend/platforms/ios-sim/src/lib.rs +++ /dev/null @@ -1 +0,0 @@ -pub use jmbackend::*; diff --git a/native/jmbackend/platforms/ios/Cargo.toml b/native/jmbackend/platforms/ios/Cargo.toml deleted file mode 100644 index 452c3c2..0000000 --- a/native/jmbackend/platforms/ios/Cargo.toml +++ /dev/null @@ -1,10 +0,0 @@ -[package] -name = "rust" -version = "0.1.0" -edition = "2021" - -[dependencies] -jmbackend = { path = "../../" } - -[lib] -crate-type = ["staticlib"] diff --git a/native/jmbackend/platforms/ios/src/lib.rs b/native/jmbackend/platforms/ios/src/lib.rs deleted file mode 100644 index a70214d..0000000 --- a/native/jmbackend/platforms/ios/src/lib.rs +++ /dev/null @@ -1 +0,0 @@ -pub use jmbackend::*; diff --git a/native/jmbackend/platforms/linux/Cargo.toml b/native/jmbackend/platforms/linux/Cargo.toml deleted file mode 100644 index f5b4c9b..0000000 --- a/native/jmbackend/platforms/linux/Cargo.toml +++ /dev/null @@ -1,17 +0,0 @@ -[package] -name = "rust" -version = "0.1.0" -edition = "2021" - -[dependencies] -jmbackend = { path = "../../" } -lazy_static = "1.4.0" -reqwest = { version = "0.11.9", features = ["rustls", "tokio-rustls", "rustls-tls"], default-features = false } -serde = { version = "1.0.152", features = ["derive"] } -serde_derive = "1.0.152" -serde_json = "1.0.93" -warp = "0.3.2" -tokio = { version = "1.26.0", features = ["full"] } - -[lib] -crate-type = ["staticlib"] diff --git a/native/jmbackend/platforms/linux/src/lib.rs b/native/jmbackend/platforms/linux/src/lib.rs deleted file mode 100644 index fe65ffd..0000000 --- a/native/jmbackend/platforms/linux/src/lib.rs +++ /dev/null @@ -1,53 +0,0 @@ -use std::process::exit; -use std::time::Duration; - -use serde_json::to_string; -use warp::Filter; - -pub use jmbackend::*; - -#[derive(Debug, serde::Deserialize, serde::Serialize)] -pub struct DartQuery { - pub method: String, - pub params: String, -} - -#[no_mangle] -pub unsafe extern "C" fn init_http_server() { - let ping = warp::get() - .and(warp::path("ping")) - .map(|| "pong".to_owned()); - let invoke = warp::post() - .and(warp::path("invoke")) - .and(warp::body::json()) - .map(invoke); - std::thread::spawn(move || { - jmbackend::RUNTIME.block_on(warp::serve(ping.or(invoke)).run(([127, 0, 0, 1], 52764))) - }); - jmbackend::RUNTIME.block_on(test_startup()); -} - -fn invoke(dq: DartQuery) -> String { - jmbackend::RUNTIME.block_on(jmbackend::invoke_async(to_string(&dq).unwrap().as_str())) -} - -async fn test_startup() { - for i in 0..6 { - if i == 5 { - exit(2); - } - std::thread::sleep(Duration::new(1, 0)); - match reqwest::get("http://127.0.0.1:52764/ping").await { - Ok(req) => match req.text().await { - Ok(txt) => { - println!("OK : {}", txt); - break; - } - Err(err) => println!("ERR : {}", err), - }, - Err(err) => { - println!("ERR : {}", err) - } - } - } -} diff --git a/native/jmbackend/platforms/macos/Cargo.toml b/native/jmbackend/platforms/macos/Cargo.toml deleted file mode 100644 index 452c3c2..0000000 --- a/native/jmbackend/platforms/macos/Cargo.toml +++ /dev/null @@ -1,10 +0,0 @@ -[package] -name = "rust" -version = "0.1.0" -edition = "2021" - -[dependencies] -jmbackend = { path = "../../" } - -[lib] -crate-type = ["staticlib"] diff --git a/native/jmbackend/platforms/macos/src/lib.rs b/native/jmbackend/platforms/macos/src/lib.rs deleted file mode 100644 index a70214d..0000000 --- a/native/jmbackend/platforms/macos/src/lib.rs +++ /dev/null @@ -1 +0,0 @@ -pub use jmbackend::*; diff --git a/native/jmbackend/platforms/windows/Cargo.toml b/native/jmbackend/platforms/windows/Cargo.toml deleted file mode 100644 index 452c3c2..0000000 --- a/native/jmbackend/platforms/windows/Cargo.toml +++ /dev/null @@ -1,10 +0,0 @@ -[package] -name = "rust" -version = "0.1.0" -edition = "2021" - -[dependencies] -jmbackend = { path = "../../" } - -[lib] -crate-type = ["staticlib"] diff --git a/native/jmbackend/platforms/windows/src/lib.rs b/native/jmbackend/platforms/windows/src/lib.rs deleted file mode 100644 index a70214d..0000000 --- a/native/jmbackend/platforms/windows/src/lib.rs +++ /dev/null @@ -1 +0,0 @@ -pub use jmbackend::*; diff --git a/native/jmbackend/src/database/active_db/dl_album.rs b/native/jmbackend/src/database/active_db/dl_album.rs deleted file mode 100644 index fd19d3e..0000000 --- a/native/jmbackend/src/database/active_db/dl_album.rs +++ /dev/null @@ -1,141 +0,0 @@ -use crate::database::utils::create_table_if_not_exists; -use sea_orm::entity::prelude::*; -use sea_orm::sea_query::Expr; -use sea_orm::ColumnTrait; -use sea_orm::ConnectionTrait; -use sea_orm::DatabaseTransaction; -use sea_orm::DeleteResult; -use sea_orm::EntityTrait; -use sea_orm::QueryFilter; -use sea_orm::QuerySelect; -use serde::{Deserialize, Serialize}; - -#[derive(Clone, Debug, Serialize, Deserialize, Eq, Hash, PartialEq, DeriveEntityModel)] -#[sea_orm(table_name = "dl_album")] -pub struct Model { - #[sea_orm(primary_key, auto_increment = false)] - pub id: i64, - pub name: String, - /// JSON(Vec) - pub author: String, - /// JSON(Vec) - pub tags: String, - /// JSON(Vec) - pub works: String, - pub description: String, - /// 方形状的封面下载状态 - /// 0:未下载, 1:下载成功 2:下载失败 - pub dl_square_cover_status: i32, - /// 3x4的封面下载状态 - /// 0:未下载, 1:下载成功 2:下载失败 - pub dl_3x4_cover_status: i32, - /// chapter(所有章节的下载状态) - /// 0:未下载, 1:全部下载成功 2:任何一个下载失败 3:删除中 - pub dl_status: i32, - /// 图片总数 - pub image_count: i32, - /// 下载了的图片总数 - pub dled_image_count: i32, -} - -#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] -pub enum Relation {} - -impl ActiveModelBehavior for ActiveModel {} - -pub(crate) async fn init(db: &DatabaseConnection) { - create_table_if_not_exists(db, Entity).await; -} - -pub(crate) async fn load_first_need_download_album(db: &DatabaseConnection) -> Option { - Entity::find() - .filter(Column::DlStatus.eq(0)) - .limit(1) - .one(db) - .await - .unwrap() -} - -pub(crate) async fn load_first_need_delete_album(db: &DatabaseConnection) -> Option { - Entity::find() - .filter(Column::DlStatus.eq(3)) - .limit(1) - .one(db) - .await - .unwrap() -} - -pub(crate) async fn inc_image_count(db: &DatabaseTransaction, id: i64, count: i32) { - Entity::update_many() - .col_expr(Column::ImageCount, Expr::col(Column::ImageCount).add(count)) - .filter(Column::Id.eq(id)) - .exec(db) - .await - .unwrap(); -} - -pub(crate) async fn set_dl_status(db: &impl ConnectionTrait, id: i64, status: i32) { - Entity::update_many() - .col_expr(Column::DlStatus, Expr::value(status)) - .filter(Column::Id.eq(id)) - .exec(db) - .await - .unwrap(); -} - -pub(crate) async fn renew_failed(db: &impl ConnectionTrait) { - Entity::update_many() - .col_expr(Column::DlStatus, Expr::value(0)) - .filter(Column::DlStatus.eq(2)) - .exec(db) - .await - .unwrap(); -} - -pub(crate) async fn set_3x4_cover_status(db: &impl ConnectionTrait, id: i64, status: i32) { - Entity::update_many() - .col_expr(Column::Dl3x4CoverStatus, Expr::value(status)) - .filter(Column::Id.eq(id)) - .exec(db) - .await - .unwrap(); -} - -pub(crate) async fn set_square_cover_status(db: &impl ConnectionTrait, id: i64, status: i32) { - Entity::update_many() - .col_expr(Column::DlSquareCoverStatus, Expr::value(status)) - .filter(Column::Id.eq(id)) - .exec(db) - .await - .unwrap(); -} - -pub(crate) async fn find_by_id(db: &impl ConnectionTrait, id: i64) -> Option { - Entity::find_by_id(id).one(db).await.unwrap() -} - -pub(crate) async fn all(db: &impl ConnectionTrait) -> Vec { - Entity::find().all(db).await.unwrap() -} - -pub(crate) async fn download_one_image(db: &impl ConnectionTrait, id: i64) { - Entity::update_many() - .col_expr( - Column::DledImageCount, - Expr::col(Column::DledImageCount).add(1), - ) - .filter(Column::Id.eq(id)) - .exec(db) - .await - .unwrap(); -} - -pub(crate) async fn delete_by_album_id( - db: &impl ConnectionTrait, - album_id: i64, -) -> Result { - Entity::delete_many() - .filter(Column::Id.eq(album_id)) - .exec(db) - .await -} diff --git a/native/jmbackend/src/database/active_db/dl_chapter.rs b/native/jmbackend/src/database/active_db/dl_chapter.rs deleted file mode 100644 index 3be9f3b..0000000 --- a/native/jmbackend/src/database/active_db/dl_chapter.rs +++ /dev/null @@ -1,124 +0,0 @@ -use super::dl_album; -use crate::database::utils::{create_index, create_table_if_not_exists, index_exists}; -use sea_orm::entity::prelude::*; -use sea_orm::sea_query::Expr; -use sea_orm::ConnectionTrait; -use sea_orm::DeleteResult; -use sea_orm::EntityTrait; -use serde::{Deserialize, Serialize}; - -#[derive(Clone, Debug, Deserialize, Serialize, Eq, Hash, PartialEq, DeriveEntityModel)] -#[sea_orm(table_name = "dl_chapter")] -pub struct Model { - pub album_id: i64, - #[sea_orm(primary_key, auto_increment = false)] - pub id: i64, - pub name: String, - pub sort: String, - /// 0:未加载图片 1:已加载图片 - pub load_images: i32, - /// 图片总数 - pub image_count: i32, - /// 下载了的图片总数 - pub dled_image_count: i32, - /// image(图片的下载状态) - /// 0:未下载, 1:全部下载成功 2:任何一个下载失败 - // "JM_PAGE_IMAGE:{}:{}" - pub dl_status: i32, -} - -#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] -pub enum Relation {} - -impl ActiveModelBehavior for ActiveModel {} - -pub(crate) async fn init(db: &DatabaseConnection) { - create_table_if_not_exists(db, Entity).await; - if !index_exists(db, Entity {}.table_name(), "idx_album_id").await { - create_index(db, Entity {}.table_name(), vec!["album_id"], "idx_album_id").await; - } -} - -pub(crate) async fn load_all_need_download_chapter( - db: &impl ConnectionTrait, - album: &dl_album::Model, -) -> Vec { - Entity::find() - .filter(Column::AlbumId.eq(album.id)) - .filter(Column::DlStatus.eq(0)) - .all(db) - .await - .unwrap() -} - -pub(crate) async fn set_dl_status(db: &impl ConnectionTrait, id: i64, status: i32) { - Entity::update_many() - .col_expr(Column::DlStatus, Expr::value(status)) - .filter(Column::Id.eq(id)) - .exec(db) - .await - .unwrap(); -} - -pub(crate) async fn renew_failed(db: &impl ConnectionTrait) { - Entity::update_many() - .col_expr(Column::DlStatus, Expr::value(0)) - .filter(Column::DlStatus.eq(2)) - .exec(db) - .await - .unwrap(); -} - -pub(crate) async fn save_image_count(db: &impl ConnectionTrait, id: i64, count: i32) { - Entity::update_many() - .col_expr(Column::LoadImages, Expr::value(1)) - .col_expr(Column::ImageCount, Expr::value(count)) - .filter(Column::Id.eq(id)) - .exec(db) - .await - .unwrap(); -} - -pub(crate) async fn has_not_success_chapter(db: &impl ConnectionTrait, album_id: i64) -> bool { - Entity::find() - .filter(Column::AlbumId.eq(album_id)) - .filter(Column::DlStatus.ne(1)) - .count(db) - .await - .unwrap() - > 0 -} - -pub(crate) async fn find_by_id(db: &impl ConnectionTrait, id: i64) -> Option { - Entity::find_by_id(id).one(db).await.unwrap() -} - -pub(crate) async fn list_by_album_id(db: &impl ConnectionTrait, album_id: i64) -> Vec { - Entity::find() - .filter(Column::AlbumId.eq(album_id)) - .all(db) - .await - .unwrap() -} - -pub(crate) async fn download_one_image(db: &impl ConnectionTrait, id: i64) { - Entity::update_many() - .col_expr( - Column::DledImageCount, - Expr::col(Column::DledImageCount).add(1), - ) - .filter(Column::Id.eq(id)) - .exec(db) - .await - .unwrap(); -} - -pub(crate) async fn delete_by_album_id( - db: &impl ConnectionTrait, - album_id: i64, -) -> Result { - Entity::delete_many() - .filter(Column::AlbumId.eq(album_id)) - .exec(db) - .await -} diff --git a/native/jmbackend/src/database/active_db/dl_image.rs b/native/jmbackend/src/database/active_db/dl_image.rs deleted file mode 100644 index f9b6a12..0000000 --- a/native/jmbackend/src/database/active_db/dl_image.rs +++ /dev/null @@ -1,150 +0,0 @@ -use sea_orm::entity::prelude::*; -use sea_orm::sea_query::Expr; -use sea_orm::ConnectionTrait; -use sea_orm::DeleteResult; -use sea_orm::EntityTrait; -use sea_orm::QueryOrder; -use serde::{Deserialize, Serialize}; - -use crate::database::utils::{ - create_index, create_index_a, create_table_if_not_exists, index_exists, -}; - -use super::dl_chapter; - -#[derive(Clone, Debug, Serialize, Deserialize, Eq, Hash, PartialEq, DeriveEntityModel)] -#[sea_orm(table_name = "dl_image")] -pub struct Model { - #[sea_orm(primary_key, auto_increment = false)] - pub album_id: i64, - #[sea_orm(primary_key, auto_increment = false)] - pub chapter_id: i64, - #[sea_orm(primary_key, auto_increment = false)] - pub image_index: i64, - // index - pub name: String, - // 说明 - // 如果album只有一个chapter的话 pub series: Vec 为空 - // 如果有多个章节的话, 第一个chapter的id与album一样 - // "JM_PAGE_IMAGE:{}:{}" , chapter_id, name - pub key: String, - /// (下载状态) - /// 0:未下载, 1:下载成功 2:下载失败 - pub dl_status: i32, - /// size - pub width: u32, - pub height: u32, -} - -pub(crate) async fn init(db: &DatabaseConnection) { - create_table_if_not_exists(db, Entity).await; - if !index_exists(db, Entity {}.table_name(), "uk_chapter_id_image_index").await { - // CREATE UNIQUE INDEX uk_chapter_id_image_index ON dl_image(chapter_id,image_index); - create_index_a( - db, - Entity {}.table_name(), - vec!["chapter_id", "image_index"], - "uk_chapter_id_image_index", - true, - ) - .await; - } - if !index_exists(db, Entity {}.table_name(), "idx_name").await { - create_index(db, Entity {}.table_name(), vec!["name"], "idx_name").await; - } - if !index_exists(db, Entity {}.table_name(), "idx_key").await { - create_index(db, Entity {}.table_name(), vec!["key"], "idx_key").await; - } -} - -#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] -pub enum Relation {} - -impl ActiveModelBehavior for ActiveModel {} - -pub(crate) async fn load_all_need_download_image( - db: &impl ConnectionTrait, - chapter: &dl_chapter::Model, -) -> Vec { - Entity::find() - .filter(Column::ChapterId.eq(chapter.id)) - .filter(Column::DlStatus.eq(0)) - .all(db) - .await - .unwrap() -} - -pub(crate) async fn set_dl_status( - db: &impl ConnectionTrait, - chapter_id: i64, - image_index: i64, - status: i32, - width: i32, - height: i32, -) { - Entity::update_many() - .col_expr(Column::DlStatus, Expr::value(status)) - .col_expr(Column::Width, Expr::value(width)) - .col_expr(Column::Height, Expr::value(height)) - .filter(Column::ChapterId.eq(chapter_id)) - .filter(Column::ImageIndex.eq(image_index)) - .exec(db) - .await - .unwrap(); -} - -pub(crate) async fn renew_failed(db: &impl ConnectionTrait) { - Entity::update_many() - .col_expr(Column::DlStatus, Expr::value(0)) - .filter(Column::DlStatus.eq(2)) - .exec(db) - .await - .unwrap(); -} - -pub(crate) async fn has_not_success_images(db: &impl ConnectionTrait, chapter_id: i64) -> bool { - Entity::find() - .filter(Column::ChapterId.eq(chapter_id)) - .filter(Column::DlStatus.ne(1)) - .count(db) - .await - .unwrap() - > 0 -} - -pub(crate) async fn find_by_key(db: &impl ConnectionTrait, key: &str) -> Option { - Entity::find() - .filter(Column::Key.eq(key)) - .one(db) - .await - .unwrap() -} - -pub(crate) async fn find_by_chapter_id(db: &impl ConnectionTrait, chapter_id: i64) -> Vec { - Entity::find() - .filter(Column::ChapterId.eq(chapter_id)) - .order_by_asc(Column::ImageIndex) - .all(db) - .await - .unwrap() -} - -pub(crate) async fn delete_by_album_id( - db: &impl ConnectionTrait, - album_id: i64, -) -> Result { - Entity::delete_many() - .filter(Column::AlbumId.eq(album_id)) - .exec(db) - .await -} - -pub(crate) async fn lisst_by_album_id( - db: &impl ConnectionTrait, - album_id: i64, -) -> Result, DbErr> { - Ok(Entity::find() - .filter(Column::AlbumId.eq(album_id)) - .all(db) - .await?) -} diff --git a/native/jmbackend/src/database/active_db/mod.rs b/native/jmbackend/src/database/active_db/mod.rs deleted file mode 100644 index d4547f2..0000000 --- a/native/jmbackend/src/database/active_db/mod.rs +++ /dev/null @@ -1,243 +0,0 @@ -use crate::database::utils::connect_db; -use crate::tools::join_paths; -use crate::{is_pro, Result, NO_PRO_MAX}; -use anyhow::anyhow; -use jmcomic::ComicSimple; -use jmcomic::SearchPage; -use once_cell::sync::OnceCell; -use sea_orm::sea_query::Expr; -use sea_orm::ActiveModelTrait; -use sea_orm::ColumnTrait; -use sea_orm::ConnectionTrait; -use sea_orm::DatabaseConnection; -use sea_orm::DbErr; -use sea_orm::EntityName; -use sea_orm::EntityTrait; -use sea_orm::QueryFilter; -use sea_orm::QueryOrder; -use sea_orm::QuerySelect; -use sea_orm::Set; -use sea_orm::Statement; -use sea_orm::TransactionTrait; -use std::ops::Deref; -use tokio::sync::Mutex; - -pub(crate) mod dl_album; -pub(crate) mod dl_chapter; -pub(crate) mod dl_image; -pub(crate) mod search_history; -pub(crate) mod view_log; -pub(crate) mod view_log_tag; - -pub(crate) static ACTIVE_DB: OnceCell> = OnceCell::new(); - -pub(crate) async fn init_db() { - let path = join_paths(vec![crate::FOLDER.lock().await.deref(), "active.db"]); - let db = connect_db(&path).await; - view_log::init(&db).await; - view_log_tag::init(&db).await; - search_history::init(&db).await; - dl_album::init(&db).await; - dl_chapter::init(&db).await; - dl_image::init(&db).await; - ACTIVE_DB.set(Mutex::new(db)).expect("INIT ACTIVE DB DUP"); -} - -pub(crate) async fn last_view_album(album: jmcomic::ComicAlbumResponse) -> Result<()> { - let db = ACTIVE_DB.get().unwrap().lock().await; - db.transaction::<_, (), sea_orm::DbErr>(|txn| { - Box::pin(async move { - let in_db_view_log: Option = - view_log::Entity::find_by_id(album.id.clone()) - .one(txn) - .await?; - match in_db_view_log { - Some(in_db_view_log) => { - let mut in_db_view_log: view_log::ActiveModel = in_db_view_log.into(); - in_db_view_log.last_view_time = Set(chrono::Local::now().timestamp()); - in_db_view_log.update(txn).await?; - } - None => { - view_log::ActiveModel { - id: Set(album.id), - author: Set(album.author.join(",")), - description: Set(album.description), - name: Set(album.name), - last_view_time: Set(chrono::Local::now().timestamp()), - last_view_chapter_id: Set(0), - last_view_page: Set(0), - ..Default::default() - } - .insert(txn) - .await?; - let mut tags: Vec = vec![]; - for tag_name in album.tags.clone() { - if !tags.contains(&tag_name) { - tags.push(tag_name); - } - } - for tag_name in tags { - view_log_tag::ActiveModel { - id: Set(album.id), - tag_name: Set(tag_name), - ..Default::default() - } - .insert(txn) - .await?; - } - } - }; - Ok(()) - }) - }) - .await?; - Ok(()) -} - -pub(crate) async fn update_view_log(query: crate::types::UpdateViewLogQuery) -> Result { - view_log::Entity::update_many() - .col_expr( - view_log::Column::LastViewTime, - Expr::value(chrono::Local::now().timestamp()), - ) - .col_expr( - view_log::Column::LastViewChapterId, - Expr::value(query.last_view_chapter_id), - ) - .col_expr( - view_log::Column::LastViewPage, - Expr::value(query.last_view_page), - ) - .filter(view_log::Column::Id.eq(query.id)) - .exec(ACTIVE_DB.get().unwrap().lock().await.deref()) - .await?; - Ok(String::default()) -} - -pub(crate) async fn find_view_log(id: i64) -> Result> { - Ok(view_log::Entity::find_by_id(id) - .one(ACTIVE_DB.get().unwrap().lock().await.deref()) - .await?) -} - -pub(crate) async fn page_view_log(page_number: i64) -> Result> { - if !is_pro().await?.is_pro && page_number > NO_PRO_MAX { - return Err(anyhow!("需要发电鸭")); - } - let active_db = ACTIVE_DB.get().unwrap().lock().await; - let stmt = Statement::from_string( - active_db.get_database_backend(), - format!( - "SELECT COUNT(*) AS c FROM {};", - view_log::Entity {}.table_name(), - ), - ); - let rsp = active_db.query_one(stmt).await?.unwrap(); - let total: i32 = rsp.try_get("", "c")?; - let page_size = 20; - let list: Vec = view_log::Entity::find() - .order_by_desc(view_log::Column::LastViewTime) - .offset(Some(((page_number - 1) * page_size).try_into()?)) - .limit(Some(page_size.try_into()?)) - .all(active_db.deref()) - .await?; - let vec = list - .iter() - .map(|model| ComicSimple { - id: model.id, - author: model.author.clone(), - description: model.description.clone(), - name: model.name.clone(), - image: "".to_string(), - category: Default::default(), - category_sub: Default::default(), - }) - .collect::>(); - Ok(SearchPage { - search_query: "".to_string(), - total: total.try_into()?, - content: vec, - redirect_aid: None, - }) -} - -pub(crate) async fn clear_view_log() -> Result { - let db = ACTIVE_DB.get().unwrap().lock().await; - db.transaction::<_, (), sea_orm::DbErr>(|txn| { - Box::pin(async move { - view_log::Entity::delete_many().exec(txn).await?; - view_log_tag::Entity::delete_many().exec(txn).await?; - Ok(()) - }) - }) - .await?; - let stmt = Statement::from_string(db.get_database_backend(), "VACUUM;".to_owned()); - db.query_one(stmt).await?; - Ok(String::default()) -} - -pub(crate) async fn db_clear_all_search_log() -> Result<()> { - let db = ACTIVE_DB.get().unwrap().lock().await; - search_history::Entity::delete_many() - .exec(db.deref()) - .await?; - Ok(()) -} - -pub(crate) async fn db_clear_a_search_log(content: String) -> Result<()> { - let db = ACTIVE_DB.get().unwrap().lock().await; - search_history::Entity::delete_many() - .filter(search_history::Column::SearchQuery.eq(content)) - .exec(db.deref()) - .await?; - Ok(()) -} - -pub(crate) async fn save_search_history(search_query: String) -> Result<()> { - let db = ACTIVE_DB.get().unwrap().lock().await; - let in_db = search_history::Entity::find_by_id(search_query.clone()) - .one(db.deref()) - .await?; - match in_db { - Some(in_db) => { - let mut data: search_history::ActiveModel = in_db.into(); - data.last_search_time = Set(chrono::Local::now().timestamp()); - data.update(db.deref()).await?; - } - None => { - let insert = search_history::ActiveModel { - search_query: Set(search_query), - last_search_time: Set(chrono::Local::now().timestamp()), - ..Default::default() - }; - insert.insert(db.deref()).await?; - } - }; - drop(db); - Ok(()) -} - -pub(crate) async fn load_last_search_histories(limit: i64) -> Result> { - let db = ACTIVE_DB.get().unwrap().lock().await; - let list: Vec = search_history::Entity::find() - .order_by_desc(search_history::Column::LastSearchTime) - .offset(0) - .limit(Some(limit.try_into()?)) - .all(db.deref()) - .await?; - Ok(list) -} - -pub(crate) async fn clear_download_album(id: i64) { - let db = ACTIVE_DB.get().unwrap().lock().await; - db.transaction::<_, (), DbErr>(|db| { - Box::pin(async move { - dl_image::delete_by_album_id(db, id.clone()).await?; - dl_chapter::delete_by_album_id(db, id.clone()).await?; - dl_album::delete_by_album_id(db, id.clone()).await?; - Ok(()) - }) - }) - .await - .unwrap(); -} diff --git a/native/jmbackend/src/database/active_db/search_history.rs b/native/jmbackend/src/database/active_db/search_history.rs deleted file mode 100644 index 6492577..0000000 --- a/native/jmbackend/src/database/active_db/search_history.rs +++ /dev/null @@ -1,30 +0,0 @@ -use crate::database::utils::{create_index, create_table_if_not_exists, index_exists}; -use sea_orm::entity::prelude::*; -use sea_orm::EntityTrait; -use serde::{Deserialize, Serialize}; - -#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Deserialize, Serialize)] -#[sea_orm(table_name = "search_history")] -pub struct Model { - #[sea_orm(primary_key, auto_increment = false)] - pub search_query: String, - pub last_search_time: i64, -} - -#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] -pub enum Relation {} - -impl ActiveModelBehavior for ActiveModel {} - -pub(crate) async fn init(db: &DatabaseConnection) { - create_table_if_not_exists(db, Entity).await; - if !index_exists(db, "search_history", "idx_last_search_time").await { - create_index( - db, - "search_history", - vec!["last_search_time"], - "idx_last_search_time", - ) - .await; - } -} diff --git a/native/jmbackend/src/database/active_db/view_log.rs b/native/jmbackend/src/database/active_db/view_log.rs deleted file mode 100644 index a37d98b..0000000 --- a/native/jmbackend/src/database/active_db/view_log.rs +++ /dev/null @@ -1,33 +0,0 @@ -use crate::database::utils::{create_index, create_table_if_not_exists, index_exists}; -use sea_orm::entity::prelude::*; -use sea_orm::EntityTrait; -use serde::{Deserialize, Serialize}; - -#[derive(Clone, Debug, PartialEq, Eq, Hash, DeriveEntityModel, Deserialize, Serialize)] -#[sea_orm(table_name = "view_log")] -pub struct Model { - // 原漫画id - #[sea_orm(primary_key, auto_increment = false)] - pub id: i64, - pub author: String, - pub description: String, - pub name: String, - // 最后阅读或查看详情的时间 - pub last_view_time: i64, - // 0 为没阅读过漫画 - pub last_view_chapter_id: i64, - // 最后阅读过了第几页 - pub last_view_page: i64, -} - -#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] -pub enum Relation {} - -impl ActiveModelBehavior for ActiveModel {} - -pub(crate) async fn init(db: &DatabaseConnection) { - create_table_if_not_exists(db, Entity).await; - if !index_exists(db, "view_log", "idx_last_view_time").await { - create_index(db, "view_log", vec!["last_view_time"], "idx_last_view_time").await; - } -} diff --git a/native/jmbackend/src/database/active_db/view_log_tag.rs b/native/jmbackend/src/database/active_db/view_log_tag.rs deleted file mode 100644 index 5019e93..0000000 --- a/native/jmbackend/src/database/active_db/view_log_tag.rs +++ /dev/null @@ -1,23 +0,0 @@ -use crate::database::utils::create_table_if_not_exists; -use sea_orm::entity::prelude::*; -use sea_orm::EntityTrait; -use serde::{Deserialize, Serialize}; - -#[derive(Clone, Debug, PartialEq, Eq, Hash, DeriveEntityModel, Deserialize, Serialize)] -#[sea_orm(table_name = "view_log_tag")] -pub struct Model { - // 原漫画id - #[sea_orm(primary_key)] - pub id: i64, - #[sea_orm(primary_key)] - pub tag_name: String, -} - -#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] -pub enum Relation {} - -impl ActiveModelBehavior for ActiveModel {} - -pub(crate) async fn init(db: &DatabaseConnection) { - create_table_if_not_exists(db, Entity).await; -} diff --git a/native/jmbackend/src/database/image_cache_db/image_cache.rs b/native/jmbackend/src/database/image_cache_db/image_cache.rs deleted file mode 100644 index 71e08f0..0000000 --- a/native/jmbackend/src/database/image_cache_db/image_cache.rs +++ /dev/null @@ -1,27 +0,0 @@ -use crate::database::utils::{create_index, create_table_if_not_exists, index_exists}; -use sea_orm::entity::prelude::*; -use sea_orm::EntityTrait; - -#[derive(Clone, Debug, PartialEq, DeriveEntityModel)] -#[sea_orm(table_name = "image_cache")] -pub struct Model { - #[sea_orm(primary_key, auto_increment = false)] - pub cache_key: String, - pub cache_path: String, - pub cache_time: i64, -} - -#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] -pub enum Relation {} - -impl ActiveModelBehavior for ActiveModel {} - -// CREATE INDEX idx_cache_time ON image_cache(cache_time); -// select * from sqlite_master where type='index' AND tbl_name='image_cache' AND name='idx_cache_time'; - -pub(crate) async fn init(db: &DatabaseConnection) { - create_table_if_not_exists(db, Entity).await; - if !index_exists(db, "image_cache", "idx_cache_time").await { - create_index(db, "image_cache", vec!["cache_time"], "idx_cache_time").await; - } -} diff --git a/native/jmbackend/src/database/image_cache_db/mod.rs b/native/jmbackend/src/database/image_cache_db/mod.rs deleted file mode 100644 index d6a69f4..0000000 --- a/native/jmbackend/src/database/image_cache_db/mod.rs +++ /dev/null @@ -1,117 +0,0 @@ -use crate::database::utils::connect_db; -use crate::tools::join_paths; -use crate::Result; -use once_cell::sync::OnceCell; -use sea_orm::ActiveModelTrait; -use sea_orm::DatabaseConnection; -use sea_orm::EntityTrait; -use sea_orm::Set; -use std::future::Future; -use std::ops::Deref; -use tokio::sync::Mutex; -pub(crate) mod image_cache; -use crate::take_hash_lock; -use bytes::Bytes; -use sea_orm::ColumnTrait; -use sea_orm::QueryFilter; -use sea_orm::QuerySelect; -use std::pin::Pin; - -static IMAGE_CACHE_DB: OnceCell> = OnceCell::new(); - -pub(crate) async fn init_db() { - let path = join_paths(vec![crate::FOLDER.lock().await.deref(), "image_cache.db"]); - let db = connect_db(&path).await; - image_cache::init(&db).await; - IMAGE_CACHE_DB - .set(Mutex::new(db)) - .expect("INIT ACTIVE DB DUP"); -} - -static IMAGE_CACHE_FOLDER: OnceCell = OnceCell::new(); - -pub(crate) async fn init_dir() { - let dir = join_paths(vec![crate::FOLDER.lock().await.deref(), "image_cache"]); - tokio::fs::create_dir_all(dir.clone()).await.unwrap(); - IMAGE_CACHE_FOLDER.set(dir).expect("INIT ACTIVE DB DUP"); -} - -pub(crate) async fn use_image_cache(key: String, f: Pin>) -> Result -where - F: Future>, -{ - // 哈希锁 - let lock = take_hash_lock(key.clone()).await; - // 查找图片是否有缓存 - let db = IMAGE_CACHE_DB.get().unwrap().lock().await; - let db_image: Option = image_cache::Entity::find_by_id(key.clone()) - .one(db.deref()) - .await?; - drop(db); - let path = match db_image { - // 有缓存直接使用 - Some(db_image) => db_image.cache_path, - // 没有缓存则下载 - None => { - let data: (Bytes, u32, u32) = f.await?; - let now = chrono::Local::now().timestamp(); - let path = format!( - "{}{}", - hex::encode(md5::compute(key.clone()).to_vec()), - &now, - ); - let cache_folder = IMAGE_CACHE_FOLDER.get().unwrap(); - let local = join_paths(vec![&cache_folder, &path.clone()]); - // drop(cache_folder); - std::fs::write(local, data.0)?; - let insert = image_cache::ActiveModel { - cache_key: Set(key.clone()), - cache_path: Set(path.clone()), - cache_time: Set(now.clone()), - ..Default::default() - }; - let db = IMAGE_CACHE_DB.get().unwrap().lock().await; - insert.insert(db.deref()).await?; - drop(db); - path - } - }; - let cache_folder = IMAGE_CACHE_FOLDER.get().unwrap(); - let local = join_paths(vec![&cache_folder, &path]); - // drop(cache_folder); - drop(lock); - Ok(local) -} - -pub(crate) async fn clean_all_image_cache() -> Result { - clean_image_cache_by_time(chrono::Local::now().timestamp()).await -} - -pub(crate) async fn clean_image_cache_by_time(time: i64) -> Result { - let cache_folder = IMAGE_CACHE_FOLDER.get().unwrap(); - let dir = cache_folder.clone(); - // drop(cache_folder); - let db = IMAGE_CACHE_DB.get().unwrap().lock().await; - loop { - let caches: Vec = image_cache::Entity::find() - .filter(image_cache::Column::CacheTime.lt(time)) - .limit(100) - .all(db.deref()) - .await?; - if caches.is_empty() { - break; - } - for cache in caches { - let local = join_paths(vec![ - dir.clone().as_str(), - cache.cache_path.clone().as_str(), - ]); - image_cache::Entity::delete_many() - .filter(image_cache::Column::CacheKey.eq(cache.cache_key)) - .exec(db.deref()) - .await?; // 不管有几条被作用 - let _ = std::fs::remove_file(local); // 不管成功与否 - } - } - Ok(String::default()) -} diff --git a/native/jmbackend/src/database/mod.rs b/native/jmbackend/src/database/mod.rs deleted file mode 100644 index 109df70..0000000 --- a/native/jmbackend/src/database/mod.rs +++ /dev/null @@ -1,5 +0,0 @@ -pub(crate) mod active_db; -pub(crate) mod image_cache_db; -pub(crate) mod property_db; -pub(crate) mod utils; -pub(crate) mod web_cache_db; diff --git a/native/jmbackend/src/database/property_db/migrations.rs b/native/jmbackend/src/database/property_db/migrations.rs deleted file mode 100644 index b30bb79..0000000 --- a/native/jmbackend/src/database/property_db/migrations.rs +++ /dev/null @@ -1,32 +0,0 @@ -use sea_orm::{ConnectionTrait, Statement}; -use sea_orm_migration::prelude::*; - -pub struct Migrator; - -#[async_trait::async_trait] -impl MigratorTrait for Migrator { - fn migrations() -> Vec> { - vec![Box::new(M20220305V100CleanCookie)] - } -} - -pub struct M20220305V100CleanCookie; - -impl MigrationName for M20220305V100CleanCookie { - fn name(&self) -> &str { - "M20220305V100CleanCookie" - } -} - -#[async_trait::async_trait] -impl MigrationTrait for M20220305V100CleanCookie { - async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> { - let sql = "DELETE FROM property WHERE k = 'cookie'"; - let stmt = Statement::from_string(manager.get_database_backend(), sql.to_owned()); - manager.get_connection().execute(stmt).await.map(|_| ()) - } - - async fn down(&self, _: &SchemaManager) -> Result<(), DbErr> { - Ok(()) - } -} diff --git a/native/jmbackend/src/database/property_db/mod.rs b/native/jmbackend/src/database/property_db/mod.rs deleted file mode 100644 index de96475..0000000 --- a/native/jmbackend/src/database/property_db/mod.rs +++ /dev/null @@ -1,75 +0,0 @@ -use std::ops::Deref; - -use once_cell::sync::OnceCell; -use sea_orm::ActiveModelTrait; -use sea_orm::DatabaseConnection; -use sea_orm::EntityTrait; -use sea_orm::Set; -use sea_orm_migration::MigratorTrait; -use tokio::sync::Mutex; - -use crate::database::utils::connect_db; -use crate::tools::join_paths; -use crate::Result; - -pub(crate) mod property; - -pub(crate) mod migrations; - -static PROPERTY_DB: OnceCell> = OnceCell::new(); - -pub(crate) async fn init_db() { - let path = join_paths(vec![crate::FOLDER.lock().await.deref(), "property.db"]); - let db = connect_db(&path).await; - property::init(&db).await; - migrations::Migrator::up(&db, None).await.unwrap(); - PROPERTY_DB.set(Mutex::new(db)).expect("INIT ACTIVE DB DUP"); -} - -pub(crate) async fn save_property(k: String, v: String) -> Result { - let db = PROPERTY_DB.get().unwrap().lock().await; - let in_db = property::Entity::find_by_id(k.clone()) - .one(db.deref()) - .await?; - match in_db { - Some(in_db) => { - let mut data: property::ActiveModel = in_db.into(); - data.k = Set(k.clone()); - data.v = Set(v.clone()); - data.update(db.deref()).await?; - } - None => { - let insert = property::ActiveModel { - k: Set(k.clone()), - v: Set(v.clone()), - ..Default::default() - }; - insert.insert(db.deref()).await?; - } - }; - drop(db); - Ok("".to_string()) -} - -pub(crate) async fn load_property(k: String) -> Result { - let db = PROPERTY_DB.get().unwrap().lock().await; - let in_db = property::Entity::find_by_id(k.clone()) - .one(db.deref()) - .await?; - let v = match in_db { - Some(in_db) => in_db.v, - None => String::default(), - }; - drop(db); - Ok(v) -} - -pub(crate) async fn load_int_property(k: String, default: i64) -> i64 { - match load_property(k).await { - Ok(p) => match p.parse::() { - Ok(data) => data, - Err(_) => default, - }, - Err(_) => default, - } -} diff --git a/native/jmbackend/src/database/property_db/property.rs b/native/jmbackend/src/database/property_db/property.rs deleted file mode 100644 index cc3413d..0000000 --- a/native/jmbackend/src/database/property_db/property.rs +++ /dev/null @@ -1,24 +0,0 @@ -use sea_orm::entity::prelude::*; -use sea_orm::EntityTrait; - -use crate::database::utils::{create_index, create_table_if_not_exists, index_exists}; - -#[derive(Clone, Debug, PartialEq, DeriveEntityModel)] -#[sea_orm(table_name = "property")] -pub struct Model { - #[sea_orm(primary_key, auto_increment = false)] - pub k: String, - pub v: String, -} - -#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] -pub enum Relation {} - -impl ActiveModelBehavior for ActiveModel {} - -pub(crate) async fn init(db: &DatabaseConnection) { - create_table_if_not_exists(&db, Entity).await; - if !index_exists(db, "property", "idx_k").await { - create_index(db, "property", vec!["k"], "idx_k").await; - } -} diff --git a/native/jmbackend/src/database/utils.rs b/native/jmbackend/src/database/utils.rs deleted file mode 100644 index aa582d2..0000000 --- a/native/jmbackend/src/database/utils.rs +++ /dev/null @@ -1,101 +0,0 @@ -use std::time::Duration; - -use sea_orm::ConnectionTrait; -use sea_orm::DatabaseConnection; -use sea_orm::EntityTrait; -use sea_orm::Schema; -use sea_orm::Statement; - -pub(crate) async fn connect_db(path: &str) -> DatabaseConnection { - let url = format!("sqlite:{}?mode=rwc", path); - let mut opt = sea_orm::ConnectOptions::new(url); - opt.max_connections(20) - .min_connections(5) - .connect_timeout(Duration::from_secs(8)) - .idle_timeout(Duration::from_secs(8)) - .sqlx_logging(true); - sea_orm::Database::connect(opt).await.unwrap() -} - -pub(crate) async fn create_table_if_not_exists(db: &DatabaseConnection, entity: E) -where - E: EntityTrait, -{ - if !has_table(db, entity.table_name()).await { - create_table(db, entity).await; - }; -} - -pub(crate) async fn has_table(db: &DatabaseConnection, table_name: &str) -> bool { - let stmt = Statement::from_string( - db.get_database_backend(), - format!( - "SELECT COUNT(*) AS c FROM sqlite_master WHERE type='table' AND name='{}';", - table_name, - ), - ); - let rsp = db.query_one(stmt).await.unwrap().unwrap(); - let count: i32 = rsp.try_get("", "c").unwrap(); - count > 0 -} - -pub(crate) async fn create_table(db: &DatabaseConnection, entity: E) -where - E: EntityTrait, -{ - let builder = db.get_database_backend(); - let schema = Schema::new(builder); - let stmt = &schema.create_table_from_entity(entity); - let stmt = builder.build(stmt); - db.execute(stmt).await.unwrap(); -} - -pub(crate) async fn index_exists( - db: &DatabaseConnection, - table_name: &str, - index_name: &str, -) -> bool { - let stmt = Statement::from_string( - db.get_database_backend(), - format!( - "select COUNT(*) AS c from sqlite_master where type='index' AND tbl_name='{}' AND name='{}';", - table_name, index_name, - ), - ); - db.query_one(stmt) - .await - .unwrap() - .unwrap() - .try_get::("", "c") - .unwrap() - > 0 -} - -pub(crate) async fn create_index_a( - db: &DatabaseConnection, - table_name: &str, - columns: Vec<&str>, - index_name: &str, - uk: bool, -) { - let stmt = Statement::from_string( - db.get_database_backend(), - format!( - "CREATE {} INDEX {} ON {}({});", - if uk { "UNIQUE" } else { "" }, - index_name, - table_name, - columns.join(","), - ), - ); - db.execute(stmt).await.unwrap(); -} - -pub(crate) async fn create_index( - db: &DatabaseConnection, - table_name: &str, - columns: Vec<&str>, - index_name: &str, -) { - create_index_a(db, table_name, columns, index_name, false).await -} diff --git a/native/jmbackend/src/database/web_cache_db/mod.rs b/native/jmbackend/src/database/web_cache_db/mod.rs deleted file mode 100644 index a83d713..0000000 --- a/native/jmbackend/src/database/web_cache_db/mod.rs +++ /dev/null @@ -1,107 +0,0 @@ -use crate::database::utils::connect_db; -use crate::tools::join_paths; -use crate::{check_first, Result}; -use once_cell::sync::OnceCell; -use sea_orm::ActiveModelTrait; -use sea_orm::DatabaseConnection; -use sea_orm::EntityTrait; -use sea_orm::Set; -use std::future::Future; -use std::ops::Deref; -use std::time::Duration; -use tokio::sync::Mutex; -pub(crate) mod web_cache; -use crate::take_hash_lock; -use sea_orm::ColumnTrait; -use sea_orm::QueryFilter; - -static WEB_CACHE_DB: OnceCell> = OnceCell::new(); - -pub(crate) async fn init_db() { - let path = join_paths(vec![crate::FOLDER.lock().await.deref(), "web_cache.db"]); - let db = connect_db(&path).await; - web_cache::init(&db).await; - WEB_CACHE_DB - .set(Mutex::new(db)) - .expect("INIT ACTIVE DB DUP"); -} - -pub(crate) async fn use_web_cache( - key: String, - expire: Duration, - reload: impl FnOnce() -> Fut, -) -> Result -where - Fut: Future>, -{ - check_first().await?; - // 时间 - let now = chrono::Local::now().timestamp(); - let earliest = now - (expire.as_secs() as i64); - // 哈希锁 - let lock = take_hash_lock(key.clone()).await; - // 读取数据库 - let db = WEB_CACHE_DB.get().unwrap().lock().await; - let cache = web_cache::Entity::find() - .filter(web_cache::Column::CacheKey.eq(key.clone())) - // 如果框架支持upert的话 - // .filter(web_cache::Column::CacheTime.gt(&now.clone())) - .one(db.deref()) - .await?; - drop(db); - if cache.is_some() { - let cache = cache.clone().unwrap(); - if cache.cache_time > earliest { - return Ok(cache.cache_content); - } - } - let load = reload().await?; - match cache { - Some(cache) => { - let mut data: web_cache::ActiveModel = cache.into(); - data.cache_content = Set(load.clone()); - data.cache_time = Set(now); - let db = WEB_CACHE_DB.get().unwrap().lock().await; - data.update(db.deref()).await?; - drop(db); - } - None => { - let data = web_cache::ActiveModel { - cache_key: Set(key), - cache_content: Set(load.clone()), - cache_time: Set(now), - ..Default::default() - }; - let db = WEB_CACHE_DB.get().unwrap().lock().await; - web_cache::Entity::insert(data).exec(db.deref()).await?; - drop(db); - } - } - drop(lock); - Ok(load) -} - -pub(crate) async fn clean_web_cache_by_patten(patten: String) -> Result { - let db = WEB_CACHE_DB.get().unwrap().lock().await; - web_cache::Entity::delete_many() - .filter(web_cache::Column::CacheKey.like(patten.as_str())) - .exec(db.deref()) - .await?; // 不管有几条被作用 - drop(db); - Ok("".to_owned()) -} - -pub(crate) async fn clean_all_web_cache() -> Result { - web_cache::Entity::delete_many() - .exec(WEB_CACHE_DB.get().unwrap().lock().await.deref()) - .await?; - Ok(String::default()) -} - -pub(crate) async fn clean_web_cache_by_time(time: i64) -> Result { - web_cache::Entity::delete_many() - .filter(web_cache::Column::CacheTime.lt(time)) - .exec(WEB_CACHE_DB.get().unwrap().lock().await.deref()) - .await?; - Ok(String::default()) -} diff --git a/native/jmbackend/src/database/web_cache_db/web_cache.rs b/native/jmbackend/src/database/web_cache_db/web_cache.rs deleted file mode 100644 index 33c7844..0000000 --- a/native/jmbackend/src/database/web_cache_db/web_cache.rs +++ /dev/null @@ -1,25 +0,0 @@ -use sea_orm::entity::prelude::*; -use sea_orm::EntityTrait; - -use crate::database::utils::{create_index, create_table_if_not_exists, index_exists}; - -#[derive(Clone, Debug, PartialEq, DeriveEntityModel)] -#[sea_orm(table_name = "web_cache")] -pub struct Model { - #[sea_orm(primary_key, auto_increment = false)] - pub cache_key: String, - pub cache_content: String, - pub cache_time: i64, -} - -#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] -pub enum Relation {} - -impl ActiveModelBehavior for ActiveModel {} - -pub(crate) async fn init(db: &DatabaseConnection) { - create_table_if_not_exists(db, Entity).await; - if !index_exists(db, "web_cache", "idx_cache_time").await { - create_index(db, "web_cache", vec!["cache_time"], "idx_cache_time").await; - } -} diff --git a/native/jmbackend/src/define.rs b/native/jmbackend/src/define.rs deleted file mode 100644 index 2c1f9ba..0000000 --- a/native/jmbackend/src/define.rs +++ /dev/null @@ -1,59 +0,0 @@ -use jmcomic::Client; -use std::collections::hash_map::DefaultHasher; -use std::hash::Hasher; -use std::time::Duration; - -use lazy_static::lazy_static; -use tokio::runtime::Runtime; -use tokio::sync::Mutex; -use tokio::sync::MutexGuard; - -use crate::types::*; - -pub const HASH_LOCK_COUNT: u64 = 64; - -lazy_static! { - - pub static ref UA:&'static str = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/96.0.4664.110 Safari/537.36"; - - pub static ref RUNTIME: Runtime = tokio::runtime::Builder::new_multi_thread() - .enable_all() - .thread_keep_alive(Duration::new(60, 0)) - .worker_threads(30) - .max_blocking_threads(30) - .build() - .unwrap(); - - pub(crate) static ref CONTEXT: Mutex = - Mutex::::new(BackendContext { - login: false, - last_login: 0, - }); - - pub(crate) static ref FIRST_LOGIN: Mutex = Mutex::new(false); - - pub(crate) static ref CLIENT :Client = Client::new(); -} - -lazy_static! { - pub(crate) static ref INITED: Mutex = Mutex::::new(false); - pub(crate) static ref FOLDER: Mutex = Mutex::::new(String::new()); -} - -lazy_static::lazy_static! { - static ref HASH_LOCK: Vec> = { - let mut mutex_vec = vec![]; - for _ in 0..HASH_LOCK_COUNT { - mutex_vec.push(Mutex::<()>::new(())); - } - mutex_vec - }; -} - -pub(crate) async fn take_hash_lock(url: String) -> MutexGuard<'static, ()> { - let mut s = DefaultHasher::new(); - s.write(url.as_bytes()); - HASH_LOCK[(s.finish() % HASH_LOCK_COUNT) as usize] - .lock() - .await -} diff --git a/native/jmbackend/src/download.rs b/native/jmbackend/src/download.rs deleted file mode 100644 index c730dc8..0000000 --- a/native/jmbackend/src/download.rs +++ /dev/null @@ -1,570 +0,0 @@ -use std::collections::VecDeque; -use std::fs::create_dir_all; -use std::ops::Deref; -use std::path::Path; -use std::sync::Arc; -use std::time::Duration; - -use itertools::Itertools; -use lazy_static::lazy_static; -use once_cell::sync::OnceCell; -use sea_orm::ActiveValue::Set; -use sea_orm::DbErr; -use sea_orm::TransactionTrait; -use sea_orm::{ActiveModelTrait, ConnectionTrait}; -use serde_json::{from_str, to_string}; -use tokio::sync::Mutex; -use tokio::time::sleep; - -use crate::database::active_db::{dl_album, dl_chapter, dl_image, ACTIVE_DB}; -use crate::tools::join_paths; -use crate::{download_image_from_url, load_download_thread, page_image_key, CLIENT}; -use crate::{DownloadCreate, DownloadCreateAlbum}; -use crate::{DownloadCreateChapter, Result}; - -pub(crate) static DOWNLOAD_FOLDER: OnceCell = OnceCell::new(); - -pub(crate) async fn init_dir() { - let dir = join_paths(vec![crate::FOLDER.lock().await.deref(), "download"]); - tokio::fs::create_dir_all(dir.clone()).await.unwrap(); - DOWNLOAD_FOLDER.set(dir).expect("INIT ACTIVE DB DUP"); -} - -lazy_static! { - pub(crate) static ref RESTART_FLAG: Mutex = Mutex::new(false); - pub(crate) static ref DOWNLOAD_AND_EXPORT_TO: Mutex = Mutex::new("".to_owned()); -} - -async fn need_restart() -> bool { - *RESTART_FLAG.lock().await.deref() -} - -// -pub(crate) async fn start_download() { - loop { - // 检测重启flag - let mut restart_flag = RESTART_FLAG.lock().await; - if *restart_flag.deref() { - *restart_flag = false; - } - drop(restart_flag); - // 删除 - let mut need_delete = load_first_need_delete_album().await; - while need_delete.is_some() { - delete_file_and_database(need_delete.unwrap()).await; - need_delete = load_first_need_delete_album().await; - } - // 下载 - match load_first_need_download_album().await { - None => sleep(Duration::new(3, 0)).await, - Some(album) => { - println!("LOAD ALBUM : {}", album.id); - let album_dir = join_paths(vec![ - &DOWNLOAD_FOLDER.get().unwrap(), - &format!("{}", album.id), - ]); - create_dir_if_not_exists(&album_dir); - download_cover(&album_dir, &album).await; - if need_restart().await { - continue; - } - let chapters = load_chapters(&album).await; - for chapter in &chapters { - let chapter_dir = join_paths(vec![&album_dir, &format!("{}", chapter.id)]); - create_dir_if_not_exists(&chapter_dir); - - let images = Arc::new(Mutex::new(VecDeque::from( - load_all_need_download_image(&chapter).await, - ))); - - let _ = futures_util::future::join_all( - num_iter::range(0, load_download_thread().await.unwrap_or(1)) - .map(|_| download_line(&chapter_dir, images.clone())) - .collect_vec(), - ) - .await; - - if need_restart().await { - break; - } - - println!("PRE SUMMARY chapter : {}", chapter.id); - summary_chapter(chapter.id).await; - } - if need_restart().await { - continue; - } - println!("PRE SUMMARY album : {}", album.id); - summary_album(album.id).await; - } - }; - } -} - -pub(crate) async fn delete_file_and_database(album: dl_album::Model) { - println!("DELETE ALBUM : {}", album.id); - let album_dir = join_paths(vec![ - &DOWNLOAD_FOLDER.get().unwrap(), - &format!("{}", album.id), - ]); - if Path::new(&album_dir).exists() { - let _ = tokio::fs::remove_dir_all(&album_dir).await; - } - crate::database::active_db::clear_download_album(album.id).await; -} - -async fn download_cover(album_dir: &str, album: &dl_album::Model) { - if album.dl_3x4_cover_status == 0 { - let url = CLIENT.comic_cover_url_3x4(album.id).await; - let data = download_image_from_url(&url, 0, String::new()).await; - match data { - Err(_) => { - dl_album::set_3x4_cover_status( - ACTIVE_DB.get().unwrap().lock().await.deref(), - album.id, - 2, - ) - .await - } - Ok((data, _, _)) => { - tokio::fs::write(&join_paths(vec![album_dir, "cover_3x4"]), data) - .await - .unwrap(); - dl_album::set_3x4_cover_status( - ACTIVE_DB.get().unwrap().lock().await.deref(), - album.id, - 1, - ) - .await; - } - } - } - if album.dl_square_cover_status == 0 { - let url = CLIENT.comic_cover_url_square(album.id).await; - let data = download_image_from_url(&url, 0, String::new()).await; - match data { - Err(_) => { - dl_album::set_square_cover_status( - ACTIVE_DB.get().unwrap().lock().await.deref(), - album.id, - 2, - ) - .await - } - Ok((data, _, _)) => { - tokio::fs::write(&join_paths(vec![album_dir, "cover_square"]), data) - .await - .unwrap(); - dl_album::set_square_cover_status( - ACTIVE_DB.get().unwrap().lock().await.deref(), - album.id, - 1, - ) - .await; - } - } - } -} - -async fn summary_chapter(chapter_id: i64) { - let lock = ACTIVE_DB.get().unwrap().lock().await; - let chapter = dl_chapter::find_by_id(lock.deref(), chapter_id) - .await - .unwrap(); - match chapter.load_images == 1 - && dl_image::has_not_success_images(lock.deref(), chapter_id).await - { - true => { - println!("SUMMARY CHAPTER : {} : FAIL", chapter_id); - dl_chapter::set_dl_status(lock.deref(), chapter_id, 2).await - } - false => { - println!("SUMMARY CHAPTER : {} : SUCCESS", chapter_id); - dl_chapter::set_dl_status(lock.deref(), chapter_id, 1).await - } - }; -} - -async fn summary_album(album_id: i64) { - // todo check album cover - let lock = ACTIVE_DB.get().unwrap().lock().await; - match dl_chapter::has_not_success_chapter(lock.deref(), album_id).await { - true => { - println!("SUMMARY ALBUM : {} : FAIL", album_id); - dl_album::set_dl_status(lock.deref(), album_id, 2).await - } - false => { - println!("SUMMARY ALBUM : {} : SUCCESS", album_id); - dl_album::set_dl_status(lock.deref(), album_id, 1).await - } - }; -} - -async fn download_line( - chapter_dir: &str, - deque: Arc>>, -) -> Result<()> { - loop { - if need_restart().await { - break; - } - let mut model_stream = deque.lock().await; - let model = model_stream.pop_back(); - drop(model_stream); - if let Some(image) = model { - let _ = download_image(&chapter_dir, &image).await; - } else { - break; - } - } - Ok(()) -} - -async fn download_image(chapter_dir: &str, image: &dl_image::Model) { - let image = image.clone(); - let url = CLIENT - .comic_page_url(image.chapter_id, image.name.clone()) - .await; - let result = download_image_from_url(&url, image.chapter_id, image.name.clone()).await; - match result { - Err(err) => { - println!("ERR : {}", err.to_string()); - dl_image::set_dl_status( - ACTIVE_DB.get().unwrap().lock().await.deref(), - image.chapter_id, - image.image_index, - 0, - 0, - 0, - ) - .await - } - Ok((buff, width, height)) => { - { - let exp = DOWNLOAD_AND_EXPORT_TO.lock().await; - if !exp.is_empty() { - let dir = join_paths(vec![ - exp.as_str(), - image.album_id.to_string().as_str(), - image.chapter_id.to_string().as_str(), - ]); - if !Path::new(&dir).exists() { - let _ = tokio::fs::create_dir_all(&dir).await; - } - drop(exp); - let path = join_paths(vec![&dir, &image.name]); - let _ = tokio::fs::write(path, buff.clone()).await; - } - } - std::fs::write( - join_paths(vec![chapter_dir, format!("{}", image.image_index).as_str()]), - buff.clone(), - ) - .unwrap(); - ACTIVE_DB - .get() - .unwrap() - .lock() - .await - .transaction::<_, (), DbErr>(|db| { - Box::pin(async move { - dl_image::set_dl_status( - db, - image.chapter_id, - image.image_index, - 1, - width.try_into().unwrap(), - height.try_into().unwrap(), - ) - .await; - dl_chapter::download_one_image(db, image.chapter_id).await; - dl_album::download_one_image(db, image.album_id).await; - Ok(()) - }) - }) - .await - .unwrap(); - } - } -} - -fn create_dir_if_not_exists>(path: P) { - if !path.as_ref().exists() { - create_dir_all(path).unwrap(); - } -} - -async fn load_chapters(album: &dl_album::Model) -> Vec { - let album = album.clone(); - let chapters = load_all_need_download_chapter(&album).await; - for chapter in &chapters { - if chapter.load_images == 0 { - let chapter = chapter.clone(); - match CLIENT.chapter(chapter.id).await { - Err(_) => { - dl_chapter::set_dl_status( - ACTIVE_DB.get().unwrap().lock().await.deref(), - chapter.id, - 1, - ) - .await - } - Ok(load) => { - // 设置已经下载图片和图片个数 - ACTIVE_DB - .get() - .unwrap() - .lock() - .await - .transaction::<_, (), DbErr>(|db| { - Box::pin(async move { - let images = &load.images; - for idx in 0..images.len() { - let image = &images[idx]; - dl_image::ActiveModel { - album_id: Set(chapter.album_id), - chapter_id: Set(chapter.id), - image_index: Set(idx.try_into().unwrap()), - name: Set(image.to_string()), - key: Set(page_image_key(chapter.id, image)), - dl_status: Set(0), - width: Set(0), - height: Set(0), - ..Default::default() - } - .insert(db) - .await - .unwrap(); - } - dl_chapter::save_image_count( - db, - chapter.id, - images.len().try_into().unwrap(), - ) - .await; - dl_album::inc_image_count( - db, - album.id, - images.len().try_into().unwrap(), - ) - .await; - Ok(()) - }) - }) - .await - .unwrap(); - } - }; - } - } - load_all_need_download_chapter(&album).await -} - -async fn load_first_need_download_album() -> Option { - return dl_album::load_first_need_download_album(ACTIVE_DB.get().unwrap().lock().await.deref()) - .await; -} - -async fn load_first_need_delete_album() -> Option { - return dl_album::load_first_need_delete_album(ACTIVE_DB.get().unwrap().lock().await.deref()) - .await; -} - -async fn load_all_need_download_chapter(album: &dl_album::Model) -> Vec { - dl_chapter::load_all_need_download_chapter( - ACTIVE_DB.get().unwrap().lock().await.deref(), - &album, - ) - .await -} - -async fn load_all_need_download_image(chapter: &dl_chapter::Model) -> Vec { - dl_image::load_all_need_download_image(ACTIVE_DB.get().unwrap().lock().await.deref(), &chapter) - .await -} - -pub(crate) async fn create_download(params: &str) -> Result { - let create: DownloadCreate = from_str(params)?; - // todo upddate - ACTIVE_DB - .get() - .unwrap() - .lock() - .await - .transaction::<_, (), DbErr>(|db| { - Box::pin(async move { - let album = match dl_album::find_by_id(db, create.album.id).await { - None => { - dl_album::ActiveModel { - id: Set(create.album.id), - name: Set(create.album.name), - author: Set(to_string(&create.album.author).unwrap()), - tags: Set(to_string(&create.album.tags).unwrap()), - works: Set(to_string(&create.album.tags).unwrap()), - description: Set(create.album.description), - dl_square_cover_status: Set(0), - dl_3x4_cover_status: Set(0), - dl_status: Set(0), - image_count: Set(0), - dled_image_count: Set(0), - ..Default::default() - } - .insert(db) - .await? - } - Some(model) => { - dl_album::set_dl_status(db, create.album.id, 0).await; - model - } - }; - for chapter in &create.chapters { - match dl_chapter::find_by_id(db, chapter.id).await { - None => { - dl_chapter::ActiveModel { - album_id: Set(album.id), - id: Set(chapter.id), - name: Set(chapter.name.to_string()), - sort: Set(chapter.sort.to_string()), - load_images: Set(0), - image_count: Set(0), - dled_image_count: Set(0), - dl_status: Set(0), - ..Default::default() - } - .insert(db) - .await?; - () - } - Some(_) => (), - } - } - Ok(()) - }) - }) - .await - .unwrap(); - Ok(String::new()) -} - -pub(crate) async fn download_by_id(id: i64) -> Result { - let lock = ACTIVE_DB.get().unwrap().lock().await; - match dl_album::find_by_id(lock.deref(), id).await { - None => Ok("null".to_owned()), - Some(album) => { - let chapters = dl_chapter::list_by_album_id(lock.deref(), id).await; - Ok(to_string(&DownloadCreate { - album: DownloadCreateAlbum { - id: album.id, - name: album.name, - author: from_str(&album.author).unwrap(), - tags: from_str(&album.tags).unwrap(), - works: from_str(&album.works).unwrap(), - description: album.description, - }, - chapters: chapters - .iter() - .map(|chapter| DownloadCreateChapter { - id: chapter.id, - name: chapter.name.clone(), - sort: chapter.sort.clone(), - }) - .collect_vec(), - }) - .unwrap()) - } - } -} - -pub(crate) async fn all_downloads() -> Result { - Ok(to_string( - &dl_album::all(ACTIVE_DB.get().unwrap().lock().await.deref()).await, - )?) -} - -pub(crate) async fn page_image_by_key(key: &str) -> Option { - match dl_image::find_by_key(ACTIVE_DB.get().unwrap().lock().await.deref(), key).await { - None => None, - Some(model) => Some(join_paths(vec![ - &DOWNLOAD_FOLDER.get().unwrap(), - &model.album_id.to_string(), - &model.chapter_id.to_string(), - &model.image_index.to_string(), - ])), - } -} - -pub(crate) async fn jm_3x4_cover_by_id(id: i64) -> Option { - match dl_album::find_by_id(ACTIVE_DB.get().unwrap().lock().await.deref(), id).await { - None => None, - Some(model) => match model.dl_3x4_cover_status { - 1 => Some(join_paths(vec![ - &DOWNLOAD_FOLDER.get().unwrap(), - &model.id.to_string(), - "cover_3x4", - ])), - _ => None, - }, - } -} - -pub(crate) async fn jm_square_cover_by_id(id: i64) -> Option { - match dl_album::find_by_id(ACTIVE_DB.get().unwrap().lock().await.deref(), id).await { - None => None, - Some(model) => match model.dl_square_cover_status { - 1 => Some(join_paths(vec![ - &DOWNLOAD_FOLDER.get().unwrap(), - &model.id.to_string(), - "cover_square", - ])), - _ => None, - }, - } -} - -pub(crate) async fn dl_image_by_chapter_id(chapter_id: i64) -> Result { - Ok(to_string( - &dl_image::find_by_chapter_id(ACTIVE_DB.get().unwrap().lock().await.deref(), chapter_id) - .await, - )?) -} - -pub(crate) async fn delete_download(id: i64) -> Result { - let lock = ACTIVE_DB.get().unwrap().lock().await; - let mut restart_flag = RESTART_FLAG.lock().await; - if *restart_flag.deref() { - *restart_flag = true; - } - // delete_flag - dl_album::set_dl_status(lock.deref(), id, 3).await; - drop(restart_flag); - Ok(String::default()) -} - -pub(crate) async fn delete_download_no_lock(db: &impl ConnectionTrait, id: i64) -> Result { - let mut restart_flag = RESTART_FLAG.lock().await; - if *restart_flag.deref() { - *restart_flag = true; - } - // delete_flag - dl_album::set_dl_status(db, id, 3).await; - drop(restart_flag); - Ok(String::default()) -} - -pub(crate) async fn renew_all_downloads() -> Result { - let lock = ACTIVE_DB.get().unwrap().lock().await; - let mut restart_flag = RESTART_FLAG.lock().await; - if *restart_flag.deref() { - *restart_flag = true; - } - lock.transaction::<_, (), DbErr>(|db| { - Box::pin(async move { - dl_album::renew_failed(db).await; - dl_chapter::renew_failed(db).await; - dl_image::renew_failed(db).await; - Ok(()) - }) - }) - .await?; - Ok(String::default()) -} diff --git a/native/jmbackend/src/export.rs b/native/jmbackend/src/export.rs deleted file mode 100644 index 541b3ed..0000000 --- a/native/jmbackend/src/export.rs +++ /dev/null @@ -1,1047 +0,0 @@ -use std::ops::Deref; -use std::path::Path; - -use anyhow::{anyhow, Context}; -use async_zip::{Compression, ZipEntryBuilder, ZipEntryBuilderExt}; -use image::EncodableLayout; -use itertools::Itertools; -use sea_orm::{ConnectionTrait, DbErr, IntoActiveModel}; -use sea_orm::{EntityTrait, TransactionTrait}; -use serde_json::{from_str, to_string}; -use tokio::fs::File; -use tokio::io::{AsyncReadExt, AsyncWriteExt, BufWriter}; - -use crate::database::active_db::{dl_album, dl_chapter, dl_image, ACTIVE_DB}; -use crate::download::{delete_download_no_lock, DOWNLOAD_FOLDER}; -use crate::tools::join_paths; -use crate::{is_pro, ExportQuery, ExportSingleQuery, Result}; - -use serde_derive::Deserialize; -use serde_derive::Serialize; - -#[async_trait::async_trait] -trait ArchiveWriter { - async fn write_to(&mut self, path: String, data: &mut [u8]) -> Result<()>; - async fn finish(mut self) -> Result<()>; -} - -struct JmiWriter<'a>(async_zip::write::ZipFileWriter<&'a mut BufWriter>); -struct ZipWriter<'a>(async_zip::write::ZipFileWriter<&'a mut BufWriter>); -struct JpegsWriter<'a>(&'a String); - -const K: u8 = 170; - -#[async_trait::async_trait] -impl ArchiveWriter for JmiWriter<'_> { - async fn write_to(&mut self, path: String, data: &'_ mut [u8]) -> Result<()> { - for i in 0..data.len() { - data[i] ^= K; - } - let builder = ZipEntryBuilder::new(path, Compression::Deflate); - let builder = builder.unix_permissions(644); - self.0.write_entry_whole(builder, data).await?; - Ok(()) - } - async fn finish(self) -> Result<()> { - self.0.close().await?; - Ok(()) - } -} - -#[async_trait::async_trait] -impl ArchiveWriter for ZipWriter<'_> { - async fn write_to(&mut self, path: String, data: &mut [u8]) -> Result<()> { - let builder = ZipEntryBuilder::new(path, Compression::Deflate); - let builder = builder.unix_permissions(644); - self.0.write_entry_whole(builder, data).await?; - Ok(()) - } - async fn finish(self) -> Result<()> { - self.0.close().await?; - Ok(()) - } -} - -#[async_trait::async_trait] -impl ArchiveWriter for JpegsWriter<'_> { - async fn write_to(&mut self, path: String, data: &mut [u8]) -> Result<()> { - let path = join_paths(vec![self.0.as_str(), path.as_str()]); - tokio::fs::write(path, data).await?; - Ok(()) - } - async fn finish(mut self) -> Result<()> { - Ok(()) - } -} - -#[async_trait::async_trait] -trait ArchiveReader { - async fn read_path( - &self, - reader: &mut async_zip::read::fs::ZipFileReader, - path: &str, - ) -> Result>; -} - -struct JmiReader; -struct ZipReader; - -#[async_trait::async_trait] -impl ArchiveReader for JmiReader { - async fn read_path( - &self, - reader: &mut async_zip::read::fs::ZipFileReader, - path: &str, - ) -> Result> { - let entry = reader - .entry(path) - .with_context(|| format!("not found {}", path))?; - let mut e = reader.entry_reader(entry.0).await?; - let mut data = vec![]; - e.read_to_end(&mut data).await?; - for i in 0..data.len() { - data.as_mut_slice()[i] ^= K; - } - Ok(data) - } -} - -#[async_trait::async_trait] -impl ArchiveReader for ZipReader { - async fn read_path( - &self, - reader: &mut async_zip::read::fs::ZipFileReader, - path: &str, - ) -> Result> { - let entry = reader - .entry(path) - .with_context(|| format!("not found {}", path))?; - let mut e = reader.entry_reader(entry.0).await?; - let mut data = vec![]; - e.read_to_end(&mut data).await?; - Ok(data) - } -} - -fn local_name(name: &str) -> String { - name.replace("\\", "_") - .replace("/", "_") - .replace("*", "_") - .replace("%", "_") - .replace("&", "_") - .replace("$", "_") - .replace(" ", "_") - .replace("(", "_") - .replace(")", "_") - .replace("[", "_") - .replace("]", "_") - .replace("?", "_") - .replace("<", "_") - .replace(">", "_") - .replace("|", "_") - .replace("\"", "_") - .replace("'", "_") -} - -pub(crate) async fn export_jm_jpegs(params: &str) -> Result { - if !is_pro().await?.is_pro { - return Err(anyhow!("请先发电鸭")); - } - let mut paths: Vec = vec![]; - let query: ExportQuery = from_str(params)?; - let db = ACTIVE_DB.get().unwrap().lock().await; - for comic_id in query.comic_id { - let ab = dl_album::find_by_id(db.deref(), comic_id) - .await - .with_context(|| "not found")?; - let archive_path = join_paths(vec![ - query.dir.as_str(), - format!( - "{}-{}", - local_name(ab.name.as_str()), - chrono::Local::now().timestamp() - ) - .as_str(), - ]); - tokio::fs::create_dir_all(archive_path.as_str()).await?; - put_comic_to_zip(Box::new(JpegsWriter(&archive_path)), db.deref(), ab, true).await?; - paths.push(archive_path); - if query.delete_exported { - let _ = delete_download_no_lock(db.deref(), comic_id).await; - } - } - Ok(to_string(&paths)?) -} - -pub(crate) async fn export_jm_zip(params: &str) -> Result { - if !is_pro().await?.is_pro { - return Err(anyhow!("请先发电鸭")); - } - let mut paths: Vec = vec![]; - let query: ExportQuery = from_str(params)?; - let db = ACTIVE_DB.get().unwrap().lock().await; - for comic_id in query.comic_id { - let ab = dl_album::find_by_id(db.deref(), comic_id) - .await - .with_context(|| "not found")?; - let archive_path = join_paths(vec![ - query.dir.as_str(), - format!( - "{}-{}.jm.zip", - local_name(ab.name.as_str()), - chrono::Local::now().timestamp() - ) - .as_str(), - ]); - - let writer_file = tokio::fs::File::create(archive_path.as_str()).await; - if writer_file.is_ok() { - let writer_file = writer_file.unwrap(); - let mut buff_writer = tokio::io::BufWriter::new(writer_file); - let writer = async_zip::write::ZipFileWriter::new(&mut buff_writer); - let write_result1 = - put_comic_to_zip(Box::new(ZipWriter(writer)), db.deref(), ab, false).await; - let write_result3 = buff_writer.flush().await; - if write_result1.is_err() { - // todo delete file - return Err(anyhow!("{}", write_result1.err().unwrap().to_string())); - } - if write_result3.is_err() { - // todo delete file - return Err(anyhow!( - "{} : {}", - write_result3.err().unwrap().to_string(), - archive_path - )); - } - } else { - // todo - return Err(anyhow!("{}", writer_file.err().unwrap().to_string())); - } - paths.push(archive_path); - if query.delete_exported { - let _ = delete_download_no_lock(db.deref(), comic_id).await; - } - } - Ok(to_string(&paths)?) -} - -pub(crate) async fn export_jm_zip_single(params: &str) -> Result { - if !is_pro().await?.is_pro { - return Err(anyhow!("请先发电鸭")); - } - - let query: ExportSingleQuery = from_str(params)?; - let db = ACTIVE_DB.get().unwrap().lock().await; - let ab = dl_album::find_by_id(db.deref(), query.id) - .await - .with_context(|| "not found")?; - - let archive_path = join_paths(vec![ - query.folder.as_str(), - format!( - "{}-{}.jm.zip", - local_name( - if let Some(rename) = query.rename { - rename - } else { - ab.name.clone() - } - .as_str() - ), - chrono::Local::now().timestamp() - ) - .as_str(), - ]); - let writer_file = tokio::fs::File::create(archive_path.as_str()).await; - if writer_file.is_ok() { - let writer_file = writer_file.unwrap(); - let mut buff_writer = tokio::io::BufWriter::new(writer_file); - let writer = async_zip::write::ZipFileWriter::new(&mut buff_writer); - let write_result1 = - put_comic_to_zip(Box::new(ZipWriter(writer)), db.deref(), ab, false).await; - let write_result3 = buff_writer.flush().await; - if write_result1.is_err() { - // todo delete file - return Err(anyhow!("{}", write_result1.err().unwrap().to_string())); - } - if write_result3.is_err() { - // todo delete file - return Err(anyhow!( - "{} : {}", - write_result3.err().unwrap().to_string(), - archive_path - )); - } - } else { - // todo - return Err(anyhow!("{}", writer_file.err().unwrap().to_string())); - } - if query.delete_exported { - let _ = delete_download_no_lock(db.deref(), query.id).await; - } - Ok(archive_path) -} - -pub(crate) async fn export_jm_jpegs_zip_single(params: &str) -> Result { - if !is_pro().await?.is_pro { - return Err(anyhow!("请先发电鸭")); - } - - let query: ExportSingleQuery = from_str(params)?; - let db = ACTIVE_DB.get().unwrap().lock().await; - let ab = dl_album::find_by_id(db.deref(), query.id) - .await - .with_context(|| "not found")?; - - let archive_path = join_paths(vec![ - query.folder.as_str(), - format!( - "{}-{}.jm.jpegs.zip", - local_name( - if let Some(rename) = query.rename { - rename - } else { - ab.name.clone() - } - .as_str() - ), - chrono::Local::now().timestamp() - ) - .as_str(), - ]); - let writer_file = tokio::fs::File::create(archive_path.as_str()).await; - if writer_file.is_ok() { - let writer_file = writer_file.unwrap(); - let mut buff_writer = tokio::io::BufWriter::new(writer_file); - let writer = async_zip::write::ZipFileWriter::new(&mut buff_writer); - let write_result1 = - put_comic_to_zip(Box::new(ZipWriter(writer)), db.deref(), ab, true).await; - let write_result3 = buff_writer.flush().await; - if write_result1.is_err() { - // todo delete file - return Err(anyhow!("{}", write_result1.err().unwrap().to_string())); - } - if write_result3.is_err() { - // todo delete file - return Err(anyhow!( - "{} : {}", - write_result3.err().unwrap().to_string(), - archive_path - )); - } - } else { - // todo - return Err(anyhow!("{}", writer_file.err().unwrap().to_string())); - } - if query.delete_exported { - let _ = delete_download_no_lock(db.deref(), query.id).await; - } - Ok(archive_path) -} - -pub(crate) async fn export_jm_jmi(params: &str) -> Result { - if !is_pro().await?.is_pro { - return Err(anyhow!("请先发电鸭")); - } - let mut paths: Vec = vec![]; - let query: ExportQuery = from_str(params)?; - let db = ACTIVE_DB.get().unwrap().lock().await; - for comic_id in query.comic_id { - let ab = dl_album::find_by_id(db.deref(), comic_id) - .await - .with_context(|| "not found")?; - let archive_path = join_paths(vec![ - query.dir.as_str(), - format!( - "{}-{}.jmi", - local_name(ab.name.as_str()), - chrono::Local::now().timestamp() - ) - .as_str(), - ]); - - let writer_file = tokio::fs::File::create(archive_path.as_str()).await; - if writer_file.is_ok() { - let writer_file = writer_file.unwrap(); - let mut buff_writer = tokio::io::BufWriter::new(writer_file); - let writer = async_zip::write::ZipFileWriter::new(&mut buff_writer); - let write_result1 = - put_comic_to_zip(Box::new(JmiWriter(writer)), db.deref(), ab, false).await; - let write_result3 = buff_writer.flush().await; - if write_result1.is_err() { - // todo delete file - return Err(anyhow!( - "{} : {}", - write_result1.err().unwrap().to_string(), - archive_path - )); - } - if write_result3.is_err() { - // todo delete file - return Err(anyhow!( - "{} : {}", - write_result3.err().unwrap().to_string(), - archive_path - )); - } - } else { - // todo - return Err(anyhow!( - "{} : {}", - writer_file.err().unwrap().to_string(), - archive_path - )); - } - paths.push(archive_path); - if query.delete_exported { - let _ = delete_download_no_lock(db.deref(), comic_id).await; - } - } - Ok(to_string(&paths)?) -} - -pub(crate) async fn export_jm_jmi_single(params: &str) -> Result { - if !is_pro().await?.is_pro { - return Err(anyhow!("请先发电鸭")); - } - - let query: ExportSingleQuery = from_str(params)?; - let db = ACTIVE_DB.get().unwrap().lock().await; - let ab = dl_album::find_by_id(db.deref(), query.id) - .await - .with_context(|| "not found")?; - - let archive_path = join_paths(vec![ - query.folder.as_str(), - format!( - "{}-{}.jmi", - local_name( - if let Some(rename) = query.rename { - rename - } else { - ab.name.clone() - } - .as_str() - ), - chrono::Local::now().timestamp() - ) - .as_str(), - ]); - let writer_file = tokio::fs::File::create(archive_path.as_str()).await; - if writer_file.is_ok() { - let writer_file = writer_file.unwrap(); - let mut buff_writer = tokio::io::BufWriter::new(writer_file); - let writer = async_zip::write::ZipFileWriter::new(&mut buff_writer); - let write_result1 = - put_comic_to_zip(Box::new(JmiWriter(writer)), db.deref(), ab, false).await; - let write_result3 = buff_writer.flush().await; - if write_result1.is_err() { - // todo delete file - return Err(anyhow!( - "{} : {}", - write_result1.err().unwrap().to_string(), - archive_path - )); - } - if write_result3.is_err() { - // todo delete file - return Err(anyhow!( - "{} : {}", - write_result3.err().unwrap().to_string(), - archive_path - )); - } - } else { - // todo - return Err(anyhow!("{}", writer_file.err().unwrap().to_string())); - } - if query.delete_exported { - let _ = delete_download_no_lock(db.deref(), query.id).await; - } - Ok(archive_path) -} - -pub(crate) async fn import_jm_zip(params: &str) -> Result { - if !is_pro().await?.is_pro { - return Err(anyhow!("请先发电鸭")); - } - import_archive(params, ZipReader).await -} - -pub(crate) async fn import_jm_jmi(params: &str) -> Result { - if !is_pro().await?.is_pro { - return Err(anyhow!("请先发电鸭")); - } - import_archive(params, JmiReader).await -} - -pub(crate) async fn import_jm_dir(params: &str) -> Result { - if !is_pro().await?.is_pro { - return Err(anyhow!("请先发电鸭")); - } - - let paths = std::fs::read_dir(params).unwrap(); - for path in paths { - let entry = path?; - let path = entry - .path() - .to_str() - .with_context(|| "What's up")? - .to_owned(); - if path.ends_with(".jm.zip") { - let _ = import_jm_zip(entry.path().to_str().with_context(|| "What's up")?).await; - } else if path.ends_with(".jmi") { - let _ = import_jm_jmi(entry.path().to_str().with_context(|| "What's up")?).await; - } - } - Ok("".to_string()) -} - -async fn import_archive(params: &str, x: impl ArchiveReader) -> Result { - let mut zip = async_zip::read::fs::ZipFileReader::new(params).await?; - // 读取基本信息 - let comic: dl_album::Model = - from_str(String::from_utf8(x.read_path(&mut zip, "comic.json").await?)?.as_str())?; - let chapters: Vec = - from_str(String::from_utf8(x.read_path(&mut zip, "chapters.json").await?)?.as_str())?; - let images: Vec = - from_str(String::from_utf8(x.read_path(&mut zip, "images.json").await?)?.as_str())?; - - // 删除数据库 - let db = ACTIVE_DB.get().unwrap().lock().await; - db.transaction::<_, (), DbErr>(|txn| { - Box::pin(async move { - dl_album::delete_by_album_id(txn, comic.id).await?; - dl_chapter::delete_by_album_id(txn, comic.id).await?; - dl_image::delete_by_album_id(txn, comic.id).await?; - Ok(()) - }) - }) - .await?; - // 删除文件夹 - let album_dir = join_paths(vec![ - &DOWNLOAD_FOLDER.get().unwrap(), - &format!("{}", comic.id), - ]); - if Path::new(&album_dir).exists() { - let _ = tokio::fs::remove_dir_all(&album_dir).await; - } - // 导入数据库 - let images1 = images.clone(); - db.transaction::<_, (), DbErr>(|txn| { - Box::pin(async move { - dl_album::Entity::insert(comic.clone().into_active_model()) - .exec(txn) - .await?; - dl_chapter::Entity::insert_many( - chapters - .iter() - .map(|x| x.clone().into_active_model()) - .collect_vec(), - ) - .exec(txn) - .await?; - dl_image::Entity::insert_many( - images1 - .iter() - .map(|x| x.clone().into_active_model()) - .collect_vec(), - ) - .exec(txn) - .await?; - Ok(()) - }) - }) - .await?; - - // 导入logo - if !Path::new(&album_dir).exists() { - tokio::fs::create_dir_all(&album_dir).await?; - } - let path_3x4_cover = join_paths(vec![album_dir.as_str(), "cover_3x4"]); - let path_cover_square = join_paths(vec![album_dir.as_str(), "cover_square"]); - { - let image_data = x.read_path(&mut zip, "cover_3x4").await?; - tokio::fs::write(&path_3x4_cover, image_data).await?; - } - { - let image_data = x.read_path(&mut zip, "cover_square").await?; - tokio::fs::write(&path_cover_square, image_data).await?; - } - - // 导入图片 - for image in images { - let chapter_dir = join_paths(vec![album_dir.as_str(), &format!("{}", image.chapter_id)]); - if !Path::new(&chapter_dir).exists() { - tokio::fs::create_dir_all(&chapter_dir).await?; - } - let image_path = join_paths(vec![ - chapter_dir.as_str(), - format!("{}", image.image_index).as_str(), - ]); - let in_zip = format!( - "{}_{}_{}", - image.album_id, image.chapter_id, image.image_index - ); - let buff = x.read_path(&mut zip, in_zip.as_str()).await?; - tokio::fs::write(image_path.as_str(), buff).await?; - } - Ok("".to_owned()) -} - -async fn put_comic_to_zip( - mut x: Box, - db: &impl ConnectionTrait, - ab: dl_album::Model, - ext: bool, -) -> Result<()> { - let chapters = dl_chapter::list_by_album_id(db, ab.id.clone()).await; - let images = dl_image::lisst_by_album_id(db, ab.id.clone()).await?; - - let comic_json_str = to_string(&ab)?; - let chapters_json_str = to_string(&chapters)?; - let images_json_str = to_string(&images)?; - - x.write_to( - "comic.json".to_string(), - &mut comic_json_str.as_bytes().to_vec(), - ) - .await?; - - x.write_to( - "comic.js".to_string(), - &mut format!("comic = {}", comic_json_str).as_bytes().to_vec(), - ) - .await?; - - x.write_to( - "chapters.json".to_string(), - &mut chapters_json_str.as_bytes().to_vec(), - ) - .await?; - - x.write_to( - "chapters.js".to_string(), - &mut format!("chapters = {}", chapters_json_str) - .as_bytes() - .to_vec(), - ) - .await?; - - x.write_to( - "images.json".to_string(), - &mut images_json_str.as_bytes().to_vec(), - ) - .await?; - - x.write_to( - "images.js".to_string(), - &mut format!("images = {}", images_json_str).as_bytes().to_vec(), - ) - .await?; - - let album_dir = join_paths(vec![&DOWNLOAD_FOLDER.get().unwrap(), &format!("{}", ab.id)]); - - // 写logo - let path_3x4_cover = join_paths(vec![album_dir.as_str(), "cover_3x4"]); - let path_cover_square = join_paths(vec![album_dir.as_str(), "cover_square"]); - - if Path::new(&path_3x4_cover).exists() { - let mut image_data = tokio::fs::read(&path_3x4_cover).await?; - x.write_to("cover_3x4".to_owned(), &mut image_data).await?; - } - if Path::new(&path_cover_square).exists() { - let mut image_data = tokio::fs::read(&path_cover_square).await?; - x.write_to("cover_square".to_owned(), &mut image_data) - .await?; - } - - // 写入图片 - for image in images { - let chapter_dir = join_paths(vec![album_dir.as_str(), &format!("{}", image.chapter_id)]); - let image_path = join_paths(vec![ - chapter_dir.as_str(), - format!("{}", image.image_index).as_str(), - ]); - let mut in_zip = format!( - "{}_{}_{}", - image.album_id, image.chapter_id, image.image_index - ); - if ext { - in_zip = format!("{}.jpg", in_zip) - } - if image.dl_status == 1 { - let mut image_data = tokio::fs::read(&image_path).await?; - x.write_to(in_zip, &mut image_data).await?; - } - } - - //写html - x.write_to("index.html".to_string(), &mut HTML.as_bytes().to_vec()) - .await?; - - x.finish().await?; - - Ok(()) -} - -const HTML: &str = " - - - - - - - - - - - -
-
    -
  • - -
  • - -
- -
-
-
- - -"; - -pub(crate) async fn export_cbzs_zip_single(params: &str) -> Result { - if !is_pro().await?.is_pro { - return Err(anyhow!("请先发电鸭")); - } - let query: ExportSingleQuery = from_str(params)?; - let db = ACTIVE_DB.get().unwrap().lock().await; - // 找到漫画 - let ab = dl_album::find_by_id(db.deref(), query.id) - .await - .with_context(|| "not found")?; - // 创建文件并创建zip输出流 - let archive_path = join_paths(vec![ - query.folder.as_str(), - format!( - "{}-{}.cbzs.zip", - local_name( - if let Some(rename) = query.rename { - rename - } else { - ab.name.clone() - } - .as_str() - ), - chrono::Local::now().timestamp() - ) - .as_str(), - ]); - let writer_file = tokio::fs::File::create(archive_path.as_str()).await; - if writer_file.is_ok() { - let mut writer_file = writer_file.unwrap(); - let writer = async_zip::write::ZipFileWriter::new(&mut writer_file); - // 导出内容 - let write_result1 = export_cbzs_zip_single_export(writer, db.deref(), ab).await; - // 关闭文件 - drop(writer_file); - if write_result1.is_err() { - // todo delete file - return Err(anyhow!( - "{} : {}", - write_result1.err().unwrap().to_string(), - archive_path - )); - } - } else { - // todo - return Err(anyhow!("{}", writer_file.err().unwrap().to_string())); - } - if query.delete_exported { - let _ = delete_download_no_lock(db.deref(), query.id).await; - } - Ok(archive_path) -} - -pub(crate) async fn export_cbzs_zip_single_export( - mut x: async_zip::write::ZipFileWriter<&mut File>, - db: &impl ConnectionTrait, - ab: dl_album::Model, -) -> Result<()> { - // chpaters - let chapters = dl_chapter::list_by_album_id(db, ab.id.clone()).await; - let mut chapters = chapters - .into_iter() - .map(|c| CbzChapter { - album_id: c.album_id, - id: c.id, - name: c.name, - sort: c.sort, - // 0:未加载图片 1:已加载图片 - load_images: c.load_images, - // 图片总数 - image_count: c.image_count, - // 下载了的图片总数 - dled_image_count: c.dled_image_count, - // image(图片的下载状态) - // 0:未下载, 1:全部下载成功 2:任何一个下载失败 - // "JM_PAGE_IMAGE:{}:{}" - dl_status: c.dl_status, - images: vec![], - cbz_name: String::default(), - }) - .collect::>(); - chapters.sort_by_key(|c| c.sort.clone()); - let mut map = std::collections::HashMap::::new(); - for chapter in &mut chapters { - map.insert(chapter.id.clone(), chapter); - } - // images push to cbzChpater - let images = dl_image::lisst_by_album_id(db, ab.id.clone()).await?; - for image in images { - if let Some(c) = map.get_mut(&image.chapter_id) { - (*c).images.push(CbzImage { - album_id: image.album_id, - chapter_id: image.chapter_id, - image_index: image.image_index, - name: image.name, - key: image.key, - dl_status: image.dl_status, - width: image.width, - height: image.height, - file_name: "".to_string(), - }); - } - } - drop(map); - let mut i = 1; - for chapter in &mut chapters { - chapter.cbz_name = format!("{:04}.cbz", { - let tmp = i; - i += 1; - tmp - }); - for image in &mut chapter.images { - image.file_name = format!("{:04}.jpg", image.image_index); - } - } - // album - let ab = CbzAlbum { - id: ab.id, - name: ab.name, - author: ab.author, - tags: ab.tags, - works: ab.works, - description: ab.description, - dl_square_cover_status: ab.dl_square_cover_status, - dl_3x4_cover_status: ab.dl_3x4_cover_status, - dl_status: ab.dl_status, - image_count: ab.image_count, - dled_image_count: ab.dled_image_count, - chapters, - }; - // export - let album_dir = join_paths(vec![&DOWNLOAD_FOLDER.get().unwrap(), &format!("{}", ab.id)]); - for chapter in &ab.chapters { - let chapter_dir = join_paths(vec![album_dir.as_str(), &format!("{}", chapter.id)]); - let builder = ZipEntryBuilder::new(chapter.cbz_name.clone(), Compression::Deflate); - let builder = builder.unix_permissions(644); - let mut cbz_entry = x.write_entry_stream(builder).await?; - let mut cbz_writer = async_zip::write::ZipFileWriter::new(&mut cbz_entry); - for image in &chapter.images { - let image_path = join_paths(vec![ - chapter_dir.as_str(), - format!("{}", image.image_index).as_str(), - ]); - let image_data = tokio::fs::read(&image_path).await?; - let builder = ZipEntryBuilder::new(image.file_name.clone(), Compression::Deflate); - let builder = builder.unix_permissions(644); - cbz_writer - .write_entry_whole(builder, image_data.as_bytes()) - .await?; - } - cbz_writer.close().await?; - cbz_entry.close().await?; - } - // 写logo - let path_3x4_cover = join_paths(vec![album_dir.as_str(), "cover_3x4"]); - let path_cover_square = join_paths(vec![album_dir.as_str(), "cover_square"]); - if Path::new(&path_3x4_cover).exists() { - let image_data = tokio::fs::read(&path_3x4_cover).await?; - let builder = ZipEntryBuilder::new("cover_3x4.jpg".to_owned(), Compression::Deflate); - let builder = builder.unix_permissions(644); - x.write_entry_whole(builder, image_data.as_bytes()).await?; - } - if Path::new(&path_cover_square).exists() { - let image_data = tokio::fs::read(&path_cover_square).await?; - let builder = ZipEntryBuilder::new("cover_square.jpg".to_owned(), Compression::Deflate); - let builder = builder.unix_permissions(644); - x.write_entry_whole(builder, image_data.as_bytes()).await?; - } - // 写数据 - let json = to_string(&ab)?; - let builder = ZipEntryBuilder::new("z-jm-cbzs-info.json".to_owned(), Compression::Deflate); - let builder = builder.unix_permissions(644); - x.write_entry_whole(builder, json.as_bytes()).await?; - // - x.close().await?; - Ok(()) -} - -#[derive(Clone, Debug, Serialize, Deserialize, Eq, Hash, PartialEq)] -pub struct CbzAlbum { - pub id: i64, - pub name: String, - pub author: String, - pub tags: String, - pub works: String, - pub description: String, - pub dl_square_cover_status: i32, - pub dl_3x4_cover_status: i32, - pub dl_status: i32, - pub image_count: i32, - pub dled_image_count: i32, - pub chapters: Vec, -} - -#[derive(Clone, Debug, Serialize, Deserialize, Eq, Hash, PartialEq)] -pub struct CbzChapter { - pub album_id: i64, - pub id: i64, - pub name: String, - pub sort: String, - pub load_images: i32, - pub image_count: i32, - pub dled_image_count: i32, - pub dl_status: i32, - pub images: Vec, - pub cbz_name: String, -} - -#[derive(Clone, Debug, Serialize, Deserialize, Eq, Hash, PartialEq)] -pub struct CbzImage { - pub album_id: i64, - pub chapter_id: i64, - pub image_index: i64, - pub name: String, - pub key: String, - pub dl_status: i32, - pub width: u32, - pub height: u32, - pub file_name: String, -} diff --git a/native/jmbackend/src/lib.rs b/native/jmbackend/src/lib.rs deleted file mode 100644 index ff10c90..0000000 --- a/native/jmbackend/src/lib.rs +++ /dev/null @@ -1,1040 +0,0 @@ -use std::ffi::{CStr, CString}; -use std::ops::Deref; -use std::path::Path; -use std::time::Duration; - -use anyhow::{anyhow, Result}; -use image::codecs::png::PngEncoder; -use image::EncodableLayout; -use image::ImageEncoder; -use image::{ColorType, GenericImageView}; -use jmcomic::*; -use libc::c_char; -use rand::random; -use serde_derive::{Deserialize, Serialize}; -use serde_json::{from_str, to_string}; -use tokio::spawn; - -use database::image_cache_db::{clean_all_image_cache, use_image_cache}; -pub use define::RUNTIME; -use define::*; -use types::*; - -use crate::active_db::{db_clear_a_search_log, db_clear_all_search_log}; -use crate::database::active_db::{ - clear_view_log, find_view_log, last_view_album, load_last_search_histories, page_view_log, - save_search_history, update_view_log, -}; -use crate::database::property_db::{load_property, save_property}; -use crate::database::web_cache_db::{ - clean_all_web_cache, clean_web_cache_by_patten, use_web_cache, -}; -use crate::database::{active_db, image_cache_db, property_db, web_cache_db}; -use crate::download::{ - all_downloads, create_download, delete_download, dl_image_by_chapter_id, download_by_id, - jm_3x4_cover_by_id, jm_square_cover_by_id, page_image_by_key, renew_all_downloads, - DOWNLOAD_AND_EXPORT_TO, RESTART_FLAG, -}; -use crate::export::{ - export_cbzs_zip_single, export_jm_jmi, export_jm_jmi_single, export_jm_jpegs, - export_jm_jpegs_zip_single, export_jm_zip, export_jm_zip_single, import_jm_dir, import_jm_jmi, - import_jm_zip, -}; -use crate::sync::sync_webdav; - -mod database; -mod define; -mod download; -mod export; -mod sync; -mod tools; -mod types; - -#[no_mangle] -pub unsafe extern "C" fn init_ffi(c: *const c_char) { - init_sync(CStr::from_ptr(c).to_str().unwrap()); -} - -#[no_mangle] -pub unsafe extern "C" fn migration_ffi(from: *const c_char, to: *const c_char) { - let from_string = CStr::from_ptr(from).to_str().unwrap(); - let to_string = CStr::from_ptr(to).to_str().unwrap(); - RUNTIME.block_on(migration(from_string, to_string)); -} - -async fn migration(from: &str, _: &str) { - let source = Path::new(from); - if source.exists() { - let mut rd = tokio::fs::read_dir(source).await.unwrap(); - while let Some(item) = rd.next_entry().await.unwrap() { - if item - .file_name() - .to_str() - .unwrap() - .starts_with("property.db") - { - tokio::fs::remove_file(item.path()).await.unwrap(); - } - } - } else { - tokio::fs::create_dir_all(source).await.unwrap(); - } -} - -pub fn init_sync(params: &str) { - RUNTIME.block_on(init(params)); -} - -async fn init(params: &str) { - let mut init_lock = INITED.lock().await; - if *init_lock { - drop(init_lock); - return; - } - *init_lock = true; - drop(init_lock); - - tokio::fs::create_dir_all(params.to_string()).await.unwrap(); - let mut lock = FOLDER.lock().await; - *lock = params.to_string(); - - println!("RUST INIT : {}", lock.clone()); - - drop(lock); - - image_cache_db::init_dir().await; - download::init_dir().await; - - // init_database - active_db::init_db().await; - image_cache_db::init_db().await; - property_db::init_db().await; - web_cache_db::init_db().await; - - // - - // auto clean - let mut auto_clean_time_str = load_property("auto_clean".to_string()).await.unwrap(); - if auto_clean_time_str == "" { - auto_clean_time_str = format!("{}", 3600 * 24 * 30); - save_property("auto_clean".to_string(), auto_clean_time_str.clone()) - .await - .unwrap(); - } - let timestamp: i64 = auto_clean_time_str.parse::().unwrap(); - let timestamp = chrono::Local::now().timestamp() - timestamp; - image_cache_db::clean_image_cache_by_time(timestamp) - .await - .unwrap(); - web_cache_db::clean_web_cache_by_time(timestamp) - .await - .unwrap(); - - let proxy_url = load_property("proxy_url".to_owned()).await.unwrap(); - if proxy_url.len() > 0 { - let _ = set_proxy(&proxy_url).await; - } - - // - - let ua = load_property("ua1".to_owned()).await.unwrap(); - if ua.len() > 0 { - CLIENT.set_user_agent(ua).await; - } else { - let ua = Client::rand_user_agent(); - save_property("ua1".to_owned(), ua.clone()).await.unwrap(); - CLIENT.set_user_agent(ua).await; - } - save_property("ua".to_owned(), "".to_owned()).await.unwrap(); - - if let Ok(is_pro) = is_pro().await { - if is_pro.is_pro { - *DOWNLOAD_AND_EXPORT_TO.lock().await = - load_property("download_and_export_to".to_owned()) - .await - .unwrap(); - } - } - - let _ = spawn(download::start_download()); -} - -async fn get_download_and_export_to() -> Result { - Ok((*DOWNLOAD_AND_EXPORT_TO.lock().await).clone()) -} - -async fn set_download_and_export_to(params: &str) -> Result { - save_property("download_and_export_to".to_owned(), params.to_string()).await?; - *DOWNLOAD_AND_EXPORT_TO.lock().await = params.to_string(); - Ok("".to_string()) -} - -#[no_mangle] -pub unsafe extern "C" fn invoke_ffi(params: *const c_char) -> *mut c_char { - let params = CStr::from_ptr(params).to_str().unwrap(); - let response = invoke(params); - CString::new(response).unwrap().into_raw() -} - -#[no_mangle] -pub unsafe extern "C" fn free_str_ffi(c: *mut c_char) { - drop(CString::from_raw(c)); -} - -pub fn invoke(params: &str) -> String { - RUNTIME.block_on(invoke_async(params)) -} - -pub async fn invoke_async(params: &str) -> String { - let query: DartQuery = serde_json::from_str(params).unwrap(); - let result: Result = match_method(query.method.as_str(), query.params.as_str()).await; - let result: ResponseToDart = match result { - Ok(str) => ResponseToDart { - response_data: str, - error_message: "".to_string(), - }, - Err(err) => ResponseToDart { - response_data: "".to_string(), - error_message: err.to_string(), - }, - }; - serde_json::to_string(&result).unwrap() -} - -async fn match_method(method: &str, params: &str) -> Result { - match method { - "test" => Ok("".to_string()), - "save_api_host" => save_api_host(params).await, - "load_api_host" => load_api_host().await, - "save_cdn_host" => save_cdn_host(params).await, - "load_cdn_host" => load_cdn_host().await, - "load_username" => load_username().await, - "loadLastLoginUsername" => load_last_login_username().await, - "load_password" => load_password().await, - "init" => init_dart().await, - "pre_login" => pre_login().await, - "login" => login(params).await, - "logout" => logout(params).await, - "save_property" => { - let sp: SaveProperty = serde_json::from_str(params)?; - save_property(sp.k, sp.v).await - } - "load_property" => load_property(params.to_owned()).await, - "comics" => comics(params).await, - "comic_search" => comic_search(params).await, - "categories" => categories().await, - "album" => album(params).await, - "chapter" => chapter(params).await, - "forum" => forum(params).await, - "comment" => comment(params).await, - "child_comment" => child_comment(params).await, - "set_favorite" => set_favorite(params).await, - "favorites" => favorites(params).await, - "games" => games(params).await, - "jm_3x4_cover" => jm_3x4_cover(params).await, - "jm_square_cover" => jm_square_cover(params).await, - "jm_page_image" => jm_page_image(params).await, - "jm_photo_image" => jm_photo_image(params).await, - "image_size" => image_size(params).await, - "http_get" => http_get(params).await, - "clean_all_cache" => clean_all_cache().await, - "update_view_log" => update_view_log(from_str(params)?).await, - "find_view_log" => Ok(to_string(&find_view_log(params.parse::()?).await?)?), - "page_view_log" => Ok(to_string(&page_view_log(params.parse::()?).await?)?), - "clear_view_log" => clear_view_log().await, - "last_search_histories" => last_search_histories(params).await, - "create_download" => create_download(params).await, - "all_downloads" => all_downloads().await, - "download_by_id" => download_by_id(params.parse::()?).await, - "dl_image_by_chapter_id" => dl_image_by_chapter_id(params.parse::()?).await, - "delete_download" => delete_download(params.parse::()?).await, - "renew_all_downloads" => renew_all_downloads().await, - "export_jm_jpegs" => export_jm_jpegs(params).await, - "export_jm_zip" => export_jm_zip(params).await, - "export_jm_zip_single" => export_jm_zip_single(params).await, - "export_jm_jpegs_zip_single" => export_jm_jpegs_zip_single(params).await, - "export_jm_jmi" => export_jm_jmi(params).await, - "export_jm_jmi_single" => export_jm_jmi_single(params).await, - "export_cbzs_zip_single" => export_cbzs_zip_single(params).await, - "import_jm_zip" => import_jm_zip(params).await, - "import_jm_jmi" => import_jm_jmi(params).await, - "import_jm_dir" => import_jm_dir(params).await, - "reload_pro" => reload_pro().await, - "is_pro" => async { Ok(to_string(&is_pro().await?)?) }.await, - "input_cd_key" => input_cd_key(params).await, - "set_download_thread" => set_download_thread(params.parse::()?).await, - "load_download_thread" => Ok(load_download_thread().await?.to_string()), - "clear_all_search_log" => clear_all_search_log().await, - "clear_a_search_log" => clear_a_search_log(params).await, - "set_proxy" => set_proxy(params).await, - "get_proxy" => get_proxy().await, - "sync_webdav" => sync_webdav(params).await, - "set_download_and_export_to" => set_download_and_export_to(params).await, - "get_download_and_export_to" => get_download_and_export_to().await, - "ping_server" => ping_server(params).await, - "getHomeDir" => get_home_dir(), - "mkdirs" => mkdirs(params), - "copyPictureToFolder" => copy_picture_to_folder(params).await, - "set_pro_server_name" => set_pro_server_name(params).await, - "get_pro_server_name" => get_pro_server_name().await, - name => return Err(anyhow!("NO FLAT : {}", name)), - } -} - -async fn init_dart() -> Result { - let mut api_host_db = load_property("api_host".to_string()).await?; - if api_host_db.is_empty() { - api_host_db = "1".to_owned(); - save_property("api_host".to_string(), api_host_db.clone()).await?; - } - CLIENT - .set_api_host(match api_host_db.as_str() { - "0" => ApiHost::Default, - "1" => ApiHost::Branch1, - "2" => ApiHost::Branch2, - "3" => ApiHost::Branch3, - _ => ApiHost::Default, - }) - .await; - let cdn_host_db = load_property("cdn_host".to_string()).await?; - if cdn_host_db.is_empty() { - save_property("cdn_host".to_string(), "1".to_owned()).await?; - } - CLIENT - .set_cdn_host(match cdn_host_db.as_str() { - "0" => None, - "1" => Some(CdnHost::Proxy1), - "2" => Some(CdnHost::Proxy2), - _ => None, - }) - .await; - Ok("".to_string()) -} - -async fn save_api_host(params: &str) -> Result { - let api_host = match params.parse::()? { - 0 => ApiHost::Default, - 1 => ApiHost::Branch1, - 2 => ApiHost::Branch2, - 3 => ApiHost::Branch3, - _ => return Err(anyhow!("不支持的分流")), - }; - let _ = save_property("api_host".to_string(), params.to_string()).await?; - CLIENT.set_api_host(api_host).await; - CONTEXT.lock().await.last_login = 0; - Ok("".to_owned()) -} - -async fn load_api_host() -> Result { - Ok(match CLIENT.get_api_host().await { - None => "0".to_owned(), - Some(ApiHost::Default) => "0".to_owned(), - Some(ApiHost::Branch1) => "1".to_owned(), - Some(ApiHost::Branch2) => "2".to_owned(), - Some(ApiHost::Branch3) => "3".to_owned(), - }) -} - -async fn save_cdn_host(params: &str) -> Result { - let cdn_host = match params.parse::()? { - 0 => None, - 1 => Some(CdnHost::Proxy1), - 2 => Some(CdnHost::Proxy2), - _ => return Err(anyhow!("不支持的分流")), - }; - let _ = save_property("cdn_host".to_string(), params.to_string()).await?; - CLIENT.set_cdn_host(cdn_host).await; - Ok("".to_owned()) -} - -async fn load_cdn_host() -> Result { - Ok(match CLIENT.get_cdn_host().await { - None => "0".to_owned(), - Some(CdnHost::Proxy1) => "1".to_owned(), - Some(CdnHost::Proxy2) => "2".to_owned(), - }) -} - -async fn load_download_thread() -> Result { - if is_pro().await?.is_pro { - Ok(property_db::load_int_property("download_thread_count".to_owned(), 1).await) - } else { - Ok(1) - } -} - -async fn set_download_thread(count: i64) -> Result { - if !is_pro().await?.is_pro { - return Err(anyhow!("需要发电鸭")); - } - property_db::save_property("download_thread_count".to_owned(), format!("{count}")).await?; - let mut restart_flag = RESTART_FLAG.lock().await; - if *restart_flag.deref() { - *restart_flag = true; - } - Ok("".to_string()) -} - -async fn load_username() -> Result { - load_property("username".to_owned()).await -} - -async fn load_last_login_username() -> Result { - load_property("last_login_username".to_owned()).await -} - -async fn load_password() -> Result { - load_property("password".to_owned()).await -} - -async fn pre_login() -> Result { - let username = load_property("username".to_owned()).await?; - let password = load_property("password".to_owned()).await?; - check_first().await?; - let data = match username != "" && password != "" { - true => match CLIENT.login(username.clone(), password).await { - Ok(mut info) => { - let mut lock = CONTEXT.lock().await; - lock.login = true; - lock.last_login = chrono::Local::now().timestamp(); - save_property("cookie".to_string(), CLIENT.cookie_str().await).await?; - save_property("last_login_username".to_owned(), username.to_string()).await?; - drop(lock); - if let Ok(json) = load_property("fav_list".to_string()).await { - if json.len() > 0 { - if let Ok(ff) = from_str::(&json) { - if username.eq(&ff.username) { - if let Ok(ff) = from_str::>(&ff.ff_json) { - info.favorite_list = ff; - } - } - } - } - } - PreLoginResponse { - pre_set: true, - pre_login: true, - self_info: Some(info), - message: None, - } - } - Err(err) => PreLoginResponse { - pre_set: true, - pre_login: false, - self_info: None, - message: Some(format!("{:?}", err)), - }, - }, - false => PreLoginResponse { - pre_set: false, - pre_login: false, - self_info: None, - message: None, - }, - }; - Ok(to_string(&data)?) -} - -pub(crate) async fn is_pro() -> Result { - Ok(IsPro { - is_pro: true, - expire: chrono::Local::now().timestamp() + 3600 * 24 * 30, - }) -} - -#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)] -pub struct IsPro { - pub is_pro: bool, - pub expire: i64, -} - -async fn reload_pro() -> Result { - Ok("".to_string()) -} - - -async fn input_cd_key(_params: &str) -> Result { - Ok("".to_string()) -} - -pub(crate) async fn check_first() -> Result<()> { - let mut lock = FIRST_LOGIN.lock().await; - if !lock.deref() { - let cookie = load_property("cookie".to_string()).await?; - if cookie.is_empty() { - save_property("cookie".to_string(), CLIENT.cookie_str().await).await?; - } else { - CLIENT.set_cookie(&cookie).await?; - } - } - *lock = true; - Ok(()) -} - -async fn login(params: &str) -> Result { - check_first().await?; - let query: LoginQuery = from_str(params)?; - let mut data = match CLIENT - .login(query.username.clone(), query.password.clone()) - .await - { - Ok(data) => data, - Err(err) => { - println!("LOGIN ERROR : {:?}", err); - return Err(err); - } - }; - let mut lock = CONTEXT.lock().await; - lock.login = true; - lock.last_login = chrono::Local::now().timestamp(); - save_property("cookie".to_string(), CLIENT.cookie_str().await).await?; - drop(lock); - save_property("username".to_owned(), query.username.clone()).await?; - save_property("password".to_owned(), query.password).await?; - let lpp = load_property("last_login_username".to_owned()).await?; - save_property("last_login_username".to_owned(), query.username.to_string()).await?; - if !lpp.eq(&query.username) { - let _ = reload_pro().await; - } - if data.favorite_list.len() > 0 { - if let Ok(json) = to_string(&data.favorite_list) { - if let Ok(json) = to_string(&PerUserFavourFolder { - username: query.username.clone(), - ff_json: json, - }) { - let _ = save_property("fav_list".to_string(), json).await; - } - } - } else { - if let Ok(json) = load_property("fav_list".to_string()).await { - if json.len() > 0 { - if let Ok(ff) = from_str::(&json) { - if query.username.eq(&ff.username) { - if let Ok(ff) = from_str::>(&ff.ff_json) { - data.favorite_list = ff; - } - } - } - } - } - } - Ok(to_string(&data)?) -} - -async fn logout(_: &str) -> Result { - save_property("cookie".to_string(), "".to_owned()).await?; - save_property("username".to_owned(), "".to_owned()).await?; - save_property("password".to_owned(), "".to_owned()).await?; - save_property("last_login_username".to_owned(), "".to_owned()).await?; - save_property("fav_list".to_string(), "".to_owned()).await?; - Ok("".to_owned()) -} - -async fn categories() -> Result { - use_web_cache("CATEGORIES".to_string(), Duration::new(3600, 0), || async { - Ok(to_string(&CLIENT.categories().await?)?) - }) - .await -} - -const NO_PRO_MAX: i64 = 10; - -async fn comics(params: &str) -> Result { - let query: ComicsQuery = from_str(params)?; - let key = format!( - "COMICS:{}:{}:{}", - query.categories_slug.clone(), - query.sort_by.clone(), - query.page.clone(), - ); - if !is_pro().await?.is_pro && query.page > NO_PRO_MAX { - return Err(anyhow!("需要发电鸭")); - } - use_web_cache(key, Duration::new(3600, 0), || async { - Ok(to_string( - &CLIENT - .comics(query.categories_slug, query.sort_by, query.page) - .await?, - )?) - }) - .await -} - -async fn comic_search(params: &str) -> Result { - let query: ComicSearchQuery = from_str(params)?; - if !is_pro().await?.is_pro && query.page > NO_PRO_MAX { - return Err(anyhow!("需要发电鸭")); - } - save_search_history(query.search_query.clone()).await?; - let key = format!( - "COMIC_SEARCH:{}:{}:{}", - query.search_query.clone(), - query.sort_by.clone(), - query.page.clone(), - ); - use_web_cache(key, Duration::new(3600, 0), || async { - Ok(to_string( - &CLIENT - .comics_search(query.search_query, query.sort_by, query.page) - .await?, - )?) - }) - .await -} - -async fn clear_all_search_log() -> Result { - db_clear_all_search_log().await?; - Ok("".to_string()) -} - -async fn clear_a_search_log(content: &str) -> Result { - db_clear_a_search_log(content.to_string()).await?; - Ok("".to_string()) -} - -async fn album(params: &str) -> Result { - let comic_id = params.parse::()?; - let key = format!("ALBUM:{}", comic_id); - let data = use_web_cache(key, Duration::new(3600, 0), || async { - Ok(to_string(&CLIENT.album(comic_id).await?)?) - }) - .await?; - let model: ComicAlbumResponse = from_str(&data)?; - last_view_album(model).await?; - Ok(data) -} - -async fn chapter(params: &str) -> Result { - let comic_id = params.parse::()?; - let key = format!("CHAPTER:{}", comic_id); - use_web_cache(key, Duration::new(3600, 0), || async { - Ok(to_string(&CLIENT.chapter(comic_id).await?)?) - }) - .await -} - -async fn forum(params: &str) -> Result { - let query: ForumQuery = serde_json::from_str(params)?; - let key = format!( - "FORUM:{}:{}:{}", - if let Some(mode) = query.mode.clone() { - mode - } else { - "null".to_string() - }, - if let Some(aid) = query.aid.clone() { - aid.to_string() - } else { - "null".to_string() - }, - query.page - ); - use_web_cache(key, Duration::new(3600, 0), || async { - let mut comments = CLIENT.forum(query.mode, query.aid, query.page).await?; - for i in 0..comments.list.len() { - let tmp = comments.list[i] - .content - .trim_start_matches("
") - .trim_end_matches("
") - .to_string(); - comments.list[i].content = tmp; - } - Ok(to_string(&comments)?) - }) - .await -} - -async fn comment(params: &str) -> Result { - check_login().await?; - let query: CommentQuery = serde_json::from_str(params)?; - clean_web_cache_by_patten(format!("FORUM:%:{}:%", query.aid.clone())).await?; - clean_web_cache_by_patten(format!("CHAPTER:{}", query.aid.clone())).await?; - Ok(to_string(&CLIENT.comment(query.aid, query.comment).await?)?) -} - -async fn child_comment(params: &str) -> Result { - check_login().await?; - let query: ChildCommentQuery = serde_json::from_str(params)?; - clean_web_cache_by_patten(format!("FORUM:%:{}:%", query.aid.clone())).await?; - clean_web_cache_by_patten(format!("CHAPTER:{}", query.aid.clone())).await?; - Ok(to_string( - &CLIENT - .child_comment(query.aid, query.comment, query.comment_id) - .await?, - )?) -} - -async fn check_login() -> Result { - let time = chrono::Local::now().timestamp(); - let mut lock = CONTEXT.lock().await; - if !lock.login { - drop(lock); - return Err(anyhow!("请登录")); - } - if time - 600 > lock.last_login { - println!("need re login"); - // 调用login会重复加锁死锁 - let username = load_property("username".to_owned()).await?; - let password = load_property("password".to_owned()).await?; - let _ = CLIENT.login(username, password).await?; - println!("re login"); - lock.login = true; - lock.last_login = chrono::Local::now().timestamp(); - save_property("cookie".to_string(), CLIENT.cookie_str().await).await?; - drop(lock); - } - return Ok("".to_owned()); -} - -async fn favorites(params: &str) -> Result { - check_login().await?; - let query: FavoursQuery = from_str(params)?; - if !is_pro().await?.is_pro && query.page > NO_PRO_MAX { - return Err(anyhow!("需要发电鸭")); - } - let key = format!("FAVORITES:{}:{}:{}", query.folder_id, query.page, query.o); - use_web_cache(key, Duration::new(600, 0), || async { - Ok(to_string( - &CLIENT - .favorites(query.folder_id, query.page, query.o) - .await?, - )?) - }) - .await -} - -async fn set_favorite(params: &str) -> Result { - check_login().await?; - let aid = params.parse::()?; - let data = CLIENT.set_favorite(aid).await?; - clean_web_cache_by_patten("FAVORITES:%".to_owned()).await?; - clean_web_cache_by_patten(format!("ALBUM:{}", aid)).await?; - clean_web_cache_by_patten(format!("CHAPTER:{}", aid)).await?; - Ok(to_string(&data)?) -} - -async fn games(params: &str) -> Result { - check_login().await?; - let page = params.parse::()?; - let key = format!("GAMES:{}", page); - use_web_cache(key, Duration::new(3600, 0), || async { - Ok(to_string(&CLIENT.games(page).await?)?) - }) - .await -} - -async fn jm_3x4_cover(params: &str) -> Result { - let comic_id = params.parse::()?; - if let Some(path) = jm_3x4_cover_by_id(comic_id).await { - return Ok(path); - } - let key = format!("JM_3X4_COVER:{}", comic_id.clone()); - let url = CLIENT.comic_cover_url_3x4(comic_id).await; - - use_image_cache( - key, - Box::pin(download_image_from_url(&url, 0, String::default())), - ) - .await -} - -async fn jm_square_cover(params: &str) -> Result { - let comic_id = params.parse::()?; - if let Some(path) = jm_square_cover_by_id(comic_id).await { - return Ok(path); - } - let key = format!("JM_SQUARE_COVER:{}", comic_id.clone()); - let url = CLIENT.comic_cover_url_square(comic_id).await; - use_image_cache( - key, - Box::pin(download_image_from_url(&url, 0, String::default())), - ) - .await -} - -async fn jm_page_image(params: &str) -> Result { - let query: PageImageQuery = serde_json::from_str(params)?; - let key = page_image_key(query.id, &query.image_name); - if let Some(path) = page_image_by_key(&key).await { - return Ok(path); - } - let url = CLIENT - .comic_page_url(query.id.clone(), query.image_name.clone()) - .await; - use_image_cache( - key, - Box::pin(download_image_from_url(&url, query.id, query.image_name)), - ) - .await -} - -async fn jm_photo_image(params: &str) -> Result { - let key = format!("JM_PHOTO_IMAGE:{}", params,); - let url = CLIENT.photo_url(params.to_string()).await; - use_image_cache( - key, - Box::pin(download_image_from_url(&url, 0, String::default())), - ) - .await -} - -//let data = download_image_from_url(&url, page_image_flag, page_image_flag2).await?; - -async fn image_size(params: &str) -> Result { - let img = image::load_from_memory(std::fs::read(params)?.as_slice())?; - let w = img.width(); - let h = img.height(); - Ok(to_string(&ImageSize { w, h })?) -} - -async fn clean_all_cache() -> Result { - clean_all_image_cache().await?; - clean_all_web_cache().await?; - Ok(String::default()) -} - -async fn http_get(params: &str) -> Result { - Ok(reqwest::ClientBuilder::new() - .build() - .unwrap() - .get(params) - .header("User-Agent", "jasmine") - .send() - .await? - .text() - .await?) -} - -async fn last_search_histories(params: &str) -> Result { - let limit = params.parse::()?; - let history = load_last_search_histories(limit).await?; - Ok(to_string(&history)?) -} - -pub(crate) fn page_image_key(chapter_id: i64, image_name: &str) -> String { - format!("JM_PAGE_IMAGE:{}:{}", chapter_id, image_name,) -} - -/// page_image_flag : 如果是pageImage, 传chapterId, 否则传0 -/// page_image_flag2 : 如果是pageImage, 传name, 否则传"" -pub(crate) async fn download_image_from_url( - url: &str, - page_image_flag: i64, - page_image_flag2: String, -) -> Result<(bytes::Bytes, u32, u32)> { - let agent = CLIENT.agent.lock().await; - let req = agent.get(url); - drop(agent); - let data: bytes::Bytes = req - .header("user-agent", crate::define::UA.clone()) - .send() - .await? - .error_for_status()? - .bytes() - .await?; - Ok(check_page_image_flag( - data, - page_image_flag, - page_image_flag2, - )?) -} - -fn check_page_image_flag( - data: bytes::Bytes, - page_image_flag: i64, - page_image_flag2: String, -) -> Result<(bytes::Bytes, u32, u32)> { - let src = image::load_from_memory(&data)?; - if page_image_flag <= 220980 { - return Ok((data, src.width(), src.height())); - } - let format = image::guess_format(&data)?.extensions_str()[0]; - if "gif".eq(format) { - let src = image::load_from_memory(&data)?; - return Ok((data, src.width(), src.height())); - } - let rows = if page_image_flag < 268850 { - 10 - } else { - let md5 = md5::compute(format!( - "{}{}", - page_image_flag, - page_image_flag2.split(".").nth(0).unwrap() - )); - let hex = hex::encode(md5.as_ref()); - let bytes = hex.as_bytes(); - let byte = bytes[bytes.len() - 1]; - if page_image_flag <= 421925 { - ((byte as i64) % 10) * 2 + 2 - } else { - ((byte as i64) % 8) * 2 + 2 - } - } as u32; - let height = src.height(); - let width = src.width(); - let remainder = height % rows; - let mut dst = image::ImageBuffer::new(width, height); - let mut copy_image = |src_start_x: u32, - src_start_y: u32, - dst_start_x: u32, - dst_start_y: u32, - width: u32, - height: u32| { - for y in 0..height { - for x in 0..width { - let pixel = src.get_pixel(src_start_x + x, src_start_y + y); - dst.put_pixel(dst_start_x + x, dst_start_y + y, pixel); - } - } - }; - for x in 0..rows { - let mut copy_h = height / rows; - let mut py = copy_h * (x); - let y = height - (copy_h * (x + 1)) - remainder; - if x == 0 { - copy_h += remainder - } else { - py += remainder - } - copy_image(0, y, 0, py, width, copy_h); - } - let pixels = dst.as_bytes(); - let mut file_buffer: Vec = vec![]; - PngEncoder::new(&mut file_buffer).write_image(pixels, width, height, ColorType::Rgba8)?; - Ok((bytes::Bytes::from(file_buffer), width, height)) -} - -async fn set_proxy(params: &str) -> Result { - let agent = if params.len() > 0 { - reqwest::ClientBuilder::new() - .proxy(reqwest::Proxy::all(params)?) - .timeout(Duration::new(30, 0)) - .build() - } else { - reqwest::ClientBuilder::new() - .timeout(Duration::new(30, 0)) - .build() - }?; - save_property("proxy_url".to_string(), params.to_owned()).await?; - CLIENT.set_agent(agent).await; - Ok("".to_owned()) -} - -async fn get_proxy() -> Result { - load_property("proxy_url".to_string()).await -} - -async fn ping_server(params: &str) -> Result { - let api_host = match params { - "0" => ApiHost::Default, - "1" => ApiHost::Branch1, - "2" => ApiHost::Branch2, - "3" => ApiHost::Branch3, - _ => return Err(anyhow!("不支持的分流")), - }; - let lock = CLIENT.agent.lock().await; - let agent = lock.clone(); - drop(lock); - let request = agent - .get(format!("https://{}/", api_host.as_str())) - .timeout(Duration::from_secs(10)); - let time1 = chrono::Local::now().timestamp_millis(); - let response = request.send().await?; - let status = response.status(); - let time2 = chrono::Local::now().timestamp_millis(); - let _ = response.text().await?; - if status.is_client_error() { - Ok(format!("{}", time2 - time1)) - } else { - Err(anyhow!("不支持的分流")) - } -} - -#[cfg(test)] -mod tests; - -#[no_mangle] -pub unsafe extern "C" fn load_int_property(name: *const c_char, default_value: i32) -> i32 { - let name = CStr::from_ptr(name).to_str().unwrap(); - let name = name.to_string(); - let result = RUNTIME.block_on(async move { load_property(name).await }); - match result { - Ok(value) => { - if value.is_empty() { - default_value - } else { - match value.parse::() { - Ok(value) => value, - Err(err) => { - println!("{:?}", err); - default_value - } - } - } - } - Err(err) => { - println!("{:?}", err); - default_value - } - } -} - -#[no_mangle] -pub unsafe extern "C" fn save_int_property(name: *const c_char, value: i32) { - let name = CStr::from_ptr(name).to_str().unwrap(); - let name = name.to_string(); - let value = value.to_string(); - let _ = RUNTIME.block_on(async move { save_property(name, value).await }); -} - -fn get_home_dir() -> Result { - match std::env::var("HOME") { - Ok(var) => Ok(var), - Err(_) => Ok(String::default()), - } -} - -fn mkdirs(folder: &str) -> Result { - let dir = Path::new(folder); - if !dir.exists() { - std::fs::create_dir_all(dir)?; - } - Ok("".to_string()) -} - -async fn copy_picture_to_folder(query_str: &str) -> Result { - let query: SaveImage = serde_json::from_str(query_str)?; - let folder = Path::new(query.folder.as_str()); - if !folder.exists() { - tokio::fs::create_dir_all(folder).await?; - } - let buff = tokio::fs::read(query.path.as_str()).await?; - let ext = image::guess_format(&buff)?.extensions_str()[0]; - let file = folder.join(format!( - "{}{}.{}", - chrono::Utc::now().timestamp_micros(), - random::(), - ext - )); - tokio::fs::write(file, buff).await?; - Ok("".to_string()) -} - -async fn load_server_name() -> Result { - let sn = load_property("pro_server_name".to_string()).await?; - if sn.is_empty() { - let sn = "HK".to_string(); - Ok(sn) - } else { - Ok(sn) - } -} - -async fn set_pro_server_name(params: &str) -> Result { - save_property("pro_server_name".to_string(), params.to_owned()).await?; - Ok("".to_string()) -} - -async fn get_pro_server_name() -> Result { - load_server_name().await -} diff --git a/native/jmbackend/src/sync.rs b/native/jmbackend/src/sync.rs deleted file mode 100644 index c88bc10..0000000 --- a/native/jmbackend/src/sync.rs +++ /dev/null @@ -1,150 +0,0 @@ -use crate::active_db::{view_log, view_log_tag, ACTIVE_DB}; -use crate::Result; -use crate::SyncWebdav; -use futures_util::TryStreamExt; -use grouping_by::GroupingBy; -use itertools::Itertools; -use sea_orm::ActiveValue::Set; -use sea_orm::ColumnTrait; -use sea_orm::EntityTrait; -use sea_orm::QueryFilter; -use sea_orm::QueryOrder; -use sea_orm::TransactionTrait; -use sea_orm::{ActiveModelTrait, IntoActiveModel}; -use serde::{Deserialize, Serialize}; -use serde_json::{from_str, to_string}; -use std::fmt::Write; -use std::ops::Deref; -use tokio::io::{AsyncBufReadExt, BufReader}; -use tokio_util::io::StreamReader; - -pub(crate) async fn sync_webdav(json: &str) -> Result { - let config: SyncWebdav = from_str(json)?; - let client = reqwest::Client::new(); - // 向下同步 - let mut req = client.request(reqwest::Method::GET, config.url.clone()); - if !config.username.is_empty() && !config.password.is_empty() { - req = req.basic_auth(config.username.clone(), Some(config.password.clone())); - } - let rsp = req.send().await?; - match rsp.status().as_u16() { - 200 => { - let mut line = String::new(); - let mut lines = BufReader::new(StreamReader::new( - rsp.bytes_stream() - .map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, e)), - )); - loop { - if lines.read_line(&mut line).await? == 0 { - break; - } - let str = line.trim(); - if str.is_empty() { - continue; - } - let vl_info: ViewLogInfo = from_str(str)?; - upgrade(vl_info).await?; - line.clear(); - } - } - 404 => {} - _ => {} - } - // 向上同步 - let last = load_last_1000_info().await?; - let mut buf = bytes::BytesMut::new(); - for x in &last { - buf.write_str(&to_string(x)?)?; - buf.write_str("\n")?; - } - let mut req = client.request(reqwest::Method::PUT, config.url); - if !config.username.is_empty() && !config.password.is_empty() { - req = req.basic_auth(config.username, Some(config.password)); - } - let rsp = req.body(buf.to_vec()).send().await?; - rsp.error_for_status()?; - Ok("".to_owned()) -} - -#[derive(Clone, Debug, Serialize, Deserialize, Eq, Hash, PartialEq)] -struct ViewLogInfo { - pub view_log: view_log::Model, - pub view_log_tags: Vec, -} - -async fn load_last_1000_info() -> Result> { - let db = ACTIVE_DB.get().unwrap().lock().await; - let logs: Vec = view_log::Entity::find() - .order_by_desc(view_log::Column::LastViewTime) - .all(db.deref()) - .await?; - let log_ids = logs.iter().map(|e| e.id).collect_vec(); - let tags: Vec = view_log_tag::Entity::find() - .filter(view_log_tag::Column::Id.is_in(log_ids)) - .all(db.deref()) - .await?; - let group = tags.iter().cloned().grouping_by(|t| t.id); - Ok(logs - .iter() - .map(|log| ViewLogInfo { - view_log: log.clone(), - view_log_tags: if let Some(s) = group.get(&log.id) { - s.clone() - } else { - vec![] - }, - }) - .collect_vec()) -} - -async fn upgrade(vl_info: ViewLogInfo) -> Result<()> { - let db = ACTIVE_DB.get().unwrap().lock().await; - let in_db = view_log::Entity::find_by_id(vl_info.view_log.id.clone()) - .one(db.deref()) - .await?; - if let Some(in_db) = in_db { - if in_db.last_view_time < vl_info.view_log.last_view_time { - db.transaction::<_, (), sea_orm::DbErr>(|txn| { - Box::pin(async move { - let in_db_view_log = in_db; - let mut in_db_view_log: view_log::ActiveModel = in_db_view_log.into(); - in_db_view_log.last_view_time = Set(vl_info.view_log.last_view_time); - in_db_view_log.last_view_chapter_id = - Set(vl_info.view_log.last_view_chapter_id); - in_db_view_log.last_view_page = Set(vl_info.view_log.last_view_page); - in_db_view_log.update(txn).await?; - Ok(()) - }) - }) - .await?; - } - } else { - db.transaction::<_, (), sea_orm::DbErr>(|txn| { - Box::pin(async move { - // 插入主体 - let in_db_view_log = view_log::Model { - id: vl_info.view_log.id.clone(), - author: vl_info.view_log.author, - description: vl_info.view_log.description, - name: vl_info.view_log.name, - last_view_time: vl_info.view_log.last_view_time, - last_view_chapter_id: vl_info.view_log.last_view_chapter_id, - last_view_page: vl_info.view_log.last_view_page, - }; - view_log::Entity::insert(in_db_view_log.into_active_model()) - .exec(txn) - .await?; - // 插入tag - for tag in vl_info.view_log_tags { - view_log_tag::Entity::insert(tag.into_active_model()) - .exec(txn) - .await?; - } - // ok - Ok(()) - }) - }) - .await?; - }; - Ok(()) -} diff --git a/native/jmbackend/src/tests.rs b/native/jmbackend/src/tests.rs deleted file mode 100644 index 2002ef1..0000000 --- a/native/jmbackend/src/tests.rs +++ /dev/null @@ -1,2 +0,0 @@ -#[test] -fn it_works() {} diff --git a/native/jmbackend/src/tools.rs b/native/jmbackend/src/tools.rs deleted file mode 100644 index 1edfd07..0000000 --- a/native/jmbackend/src/tools.rs +++ /dev/null @@ -1,14 +0,0 @@ -use std::path::PathBuf; - -pub(crate) fn join_paths(paths: Vec<&str>) -> String { - match paths.len() { - 0 => String::default(), - _ => { - let mut path: PathBuf = PathBuf::new(); - for x in 0..paths.len() { - path = path.join(paths[x]); - } - return path.to_str().unwrap().to_string(); - } - } -} diff --git a/native/jmbackend/src/types.rs b/native/jmbackend/src/types.rs deleted file mode 100644 index 0e08d86..0000000 --- a/native/jmbackend/src/types.rs +++ /dev/null @@ -1,214 +0,0 @@ -use jmcomic::{FavoritesOrder, SelfInfo, SortBy}; -use serde::{Deserialize, Serialize}; - -#[derive(Clone, Debug, Deserialize, Serialize)] -pub struct BackendContext { - pub login: bool, - pub last_login: i64, -} - -#[derive(Clone, Debug, Deserialize, Serialize)] -pub struct DartQuery { - pub method: String, - pub params: String, -} - -#[derive(Clone, Debug, Deserialize, Serialize)] -pub struct ResponseToDart { - pub error_message: String, - pub response_data: String, -} - -#[derive(Clone, Debug, Deserialize, Serialize)] -pub struct PreLoginResponse { - pub pre_set: bool, - pub pre_login: bool, - pub self_info: Option, - pub message: Option, -} - -#[derive(Clone, Debug, Deserialize, Serialize)] -pub struct LoginQuery { - pub username: String, - pub password: String, -} - -#[derive(Clone, Debug, Deserialize, Serialize)] -pub struct ComicsQuery { - pub categories_slug: String, - pub sort_by: SortBy, - pub page: i64, -} - -#[derive(Clone, Debug, Deserialize, Serialize)] -pub struct ComicSearchQuery { - pub search_query: String, - pub sort_by: SortBy, - pub page: i64, -} - -#[derive(Clone, Debug, Deserialize, Serialize)] -pub struct ForumQuery { - pub mode: Option, - pub aid: Option, - pub page: i64, -} - -#[derive(Clone, Debug, Deserialize, Serialize)] -pub struct CommentQuery { - pub aid: i64, - pub comment: String, -} - -#[derive(Clone, Debug, Deserialize, Serialize)] -pub struct ChildCommentQuery { - pub aid: i64, - pub comment: String, - pub comment_id: i64, -} - -#[derive(Clone, Debug, Deserialize, Serialize)] -pub struct SaveProperty { - pub k: String, - pub v: String, -} - -#[derive(Clone, Debug, Deserialize, Serialize)] -pub struct PageImageQuery { - pub id: i64, - pub image_name: String, -} - -#[derive(Clone, Debug, Deserialize, Serialize)] -pub struct ImageSize { - pub w: u32, - pub h: u32, -} - -#[derive(Clone, Debug, Deserialize, Serialize)] -pub struct UpdateViewLogQuery { - pub id: i64, - pub last_view_chapter_id: i64, - pub last_view_page: i64, -} - -#[derive(Clone, Debug, Deserialize, Serialize)] -pub struct DownloadCreate { - pub album: DownloadCreateAlbum, - pub chapters: Vec, -} - -#[derive(Clone, Debug, Deserialize, Serialize)] -pub struct DownloadCreateAlbum { - pub id: i64, - pub name: String, - pub author: Vec, - pub tags: Vec, - pub works: Vec, - pub description: String, -} - -#[derive(Clone, Debug, Deserialize, Serialize)] -pub struct DownloadCreateChapter { - pub id: i64, - pub name: String, - pub sort: String, -} - -#[derive(Clone, Debug, Deserialize, Serialize)] -pub struct FavoursQuery { - pub folder_id: i64, - pub page: i64, - pub o: FavoritesOrder, -} - -#[derive(Clone, Debug, Deserialize, Serialize)] -pub struct PerUserFavourFolder { - pub username: String, - pub ff_json: String, -} - -//////////////////////////// - -#[derive(Clone, Debug, Deserialize, Serialize)] -pub struct ExportQuery { - pub dir: String, - pub comic_id: Vec, - pub delete_exported: bool, -} - -macro_rules! enum_str { - ($name:ident { $($variant:ident($str:expr), )* }) => { - #[derive(Clone, Copy, Debug, Eq, PartialEq)] - pub enum $name { - $($variant,)* - } - - impl ::serde::Serialize for $name { - fn serialize(&self, serializer: S) -> Result - where S: ::serde::Serializer, - { - // 将枚举序列化为字符串。 - serializer.serialize_str(match *self { - $( $name::$variant => $str, )* - }) - } - } - - impl<'de> ::serde::Deserialize<'de> for $name { - fn deserialize(deserializer: D) -> Result - where D: ::serde::Deserializer<'de>, - { - struct Visitor; - - impl<'de> ::serde::de::Visitor<'de> for Visitor { - type Value = $name; - - fn expecting(&self, formatter: &mut ::std::fmt::Formatter) -> ::std::fmt::Result { - write!(formatter, "a string for {}", stringify!($name)) - } - - fn visit_str(self, value: &str) -> Result<$name, E> - where E: ::serde::de::Error, - { - match value { - $( $str => Ok($name::$variant), )* - _ => Err(E::invalid_value(::serde::de::Unexpected::Other( - &format!("unknown {} variant: {}", stringify!($name), value) - ), &self)), - } - } - } - - // 从字符串反序列化枚举。 - deserializer.deserialize_str(Visitor) - } - } - } -} - -enum_str!(SyncDirection { - Merge("Merge"), -}); - -#[derive(Clone, Debug, Deserialize, Serialize)] -pub struct SyncWebdav { - pub url: String, - pub username: String, - pub password: String, - pub direction: SyncDirection, -} - -#[derive(Clone, Debug, Deserialize, Serialize)] -pub struct ExportSingleQuery { - pub id: i64, - pub folder: String, - pub rename: Option, - pub delete_exported: bool, -} - -#[derive(Clone, Debug, Deserialize, Serialize)] -pub struct SaveImage { - pub folder: String, - pub path: String, -} diff --git a/native/jmcomic-rs/.gitignore b/native/jmcomic-rs/.gitignore deleted file mode 100644 index 4262d78..0000000 --- a/native/jmcomic-rs/.gitignore +++ /dev/null @@ -1,6 +0,0 @@ -/target -Cargo.lock - -/.idea/ -*.iml -/data.txt diff --git a/native/jmcomic-rs/Cargo.toml b/native/jmcomic-rs/Cargo.toml deleted file mode 100644 index b9e3d04..0000000 --- a/native/jmcomic-rs/Cargo.toml +++ /dev/null @@ -1,22 +0,0 @@ -[package] -name = "jmcomic" -version = "0.1.0" -edition = "2021" - -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - -[dependencies] -anyhow = "1.0.75" -base64 = "0.13.0" -block-modes = "0.8.1" -aes = "0.7.5" -hex = "0.4.3" -lazy_static = "1.4.0" -md5 = "0.7.0" -rand = "0.8.5" -reqwest = { version = "0.11.22", features = ["tokio-rustls", "rustls", "rustls-tls"], default-features = false } -serde = { version = "1.0.190", features = ["derive"] } -serde_derive = "1.0.190" -serde_json = "1.0.108" -serde_path_to_error = "0.1.14" -tokio = { version = "1.33.0", features = ["macros"] } diff --git a/native/jmcomic-rs/src/entities.rs b/native/jmcomic-rs/src/entities.rs deleted file mode 100644 index d58e7de..0000000 --- a/native/jmcomic-rs/src/entities.rs +++ /dev/null @@ -1,497 +0,0 @@ -use serde_derive::Deserialize; -use serde_derive::Serialize; -use std::fmt::{Display, Formatter}; -use std::num::ParseIntError; - -#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)] -pub struct CategoryData { - pub categories: Vec, - pub blocks: Vec, -} - -#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)] -pub struct Category { - #[serde(deserialize_with = "fuzzy_i64")] - pub id: i64, - pub name: String, - pub slug: String, - #[serde(deserialize_with = "fuzzy_i64")] - pub total_albums: i64, - #[serde(rename = "type")] - pub type_field: Option, -} - -#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)] -pub struct Block { - pub title: String, - pub content: Vec, -} - -#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)] -pub struct SearchPage { - pub search_query: String, - #[serde(deserialize_with = "fuzzy_i64")] - pub total: i64, - pub content: Vec, - #[serde(deserialize_with = "fuzzy_option_i64", default = "default_option_i64")] - pub redirect_aid: Option, -} - -#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)] -pub struct ComicSimple { - #[serde(deserialize_with = "fuzzy_i64")] - pub id: i64, - pub author: String, - #[serde(deserialize_with = "null_string")] - pub description: String, - pub name: String, - pub image: String, - pub category: CategorySimple, - pub category_sub: CategorySimple, -} - -#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)] -pub struct CategorySimple { - pub id: Option, - pub title: Option, -} - -#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)] -pub struct ComicAlbumResponse { - pub id: i64, - pub name: String, - pub author: Vec, - pub images: Vec, - #[serde(deserialize_with = "null_string")] - pub description: String, - #[serde(deserialize_with = "fuzzy_i64")] - pub total_views: i64, - #[serde(deserialize_with = "fuzzy_i64")] - pub likes: i64, - pub series: Vec, - #[serde(deserialize_with = "fuzzy_i64")] - pub series_id: i64, - #[serde(deserialize_with = "fuzzy_i64")] - pub comment_total: i64, - pub tags: Vec, - pub works: Vec, - // pub actors: Vec, - pub related_list: Vec, - pub liked: bool, - pub is_favorite: bool, -} - -#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)] -pub struct ComicChapterResponse { - #[serde(deserialize_with = "fuzzy_i64")] - pub id: i64, - pub series: Vec, - pub tags: String, - pub name: String, - pub images: Vec, - #[serde(deserialize_with = "fuzzy_i64")] - pub series_id: i64, - pub is_favorite: bool, - pub liked: bool, -} - -#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)] -pub struct Series { - #[serde(deserialize_with = "fuzzy_i64")] - pub id: i64, - pub name: String, - pub sort: String, -} - -#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)] -pub struct RelatedList { - #[serde(deserialize_with = "fuzzy_i64")] - pub id: i64, - pub author: String, - #[serde(deserialize_with = "null_string")] - pub description: String, - pub name: String, - pub image: String, -} - -#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)] -pub struct Page { - pub list: Vec, - #[serde(deserialize_with = "fuzzy_i64")] - pub total: i64, -} - -#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)] -pub struct CountPage { - pub list: Vec, - #[serde(deserialize_with = "fuzzy_i64")] - pub total: i64, - #[serde(deserialize_with = "fuzzy_i64")] - pub count: i64, -} - -#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)] -pub struct VideoSimple { - pub id: String, - pub photo: String, - pub title: String, - pub tags: Vec, -} - -#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)] -pub struct Comment { - // 仅主要评论 - #[serde( - deserialize_with = "fuzzy_i64", - default = "default_i64", - rename = "AID" - )] - pub aid: i64, - // 仅主要评论 - // #[serde(rename = "BID")] - // pub bid: Value, - #[serde(deserialize_with = "fuzzy_i64", rename = "CID")] - pub cid: i64, - #[serde(deserialize_with = "fuzzy_i64", rename = "UID")] - pub uid: i64, - pub username: String, - pub nickname: String, - #[serde(deserialize_with = "fuzzy_i64")] - pub likes: i64, - pub gender: String, - pub update_at: String, - pub addtime: String, - #[serde(deserialize_with = "fuzzy_i64", rename = "parent_CID")] - pub parent_cid: i64, - pub expinfo: Expinfo, - // 仅主要评论, 文章名字 - #[serde(default = "default_string")] - pub name: String, - pub content: String, - pub photo: String, - #[serde(deserialize_with = "fuzzy_i64")] - pub spoiler: i64, - // 仅主要评论, 还是错别字 - #[serde(default = "default_vec")] - pub replys: Vec, -} - -#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)] -pub struct Expinfo { - pub level_name: String, - pub level: i64, - #[serde(rename = "nextLevelExp")] - pub next_level_exp: i64, - pub exp: String, - #[serde(rename = "expPercent")] - pub exp_percent: f64, - #[serde(deserialize_with = "fuzzy_i64")] - pub uid: i64, - pub badges: Vec, -} - -#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)] -pub struct Badge { - pub content: String, - pub name: String, - pub id: String, -} - -#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)] -pub struct SelfInfo { - #[serde(deserialize_with = "fuzzy_i64")] - pub uid: i64, - pub username: String, - pub email: String, - pub emailverified: String, - pub photo: String, - pub fname: String, - pub gender: String, - #[serde(deserialize_with = "null_string")] - pub message: String, - #[serde(deserialize_with = "fuzzy_i64")] - pub coin: i64, - pub album_favorites: i64, - pub s: String, - #[serde(default = "default_vec")] - pub favorite_list: Vec, - pub level_name: String, - pub level: i64, - #[serde(rename = "nextLevelExp")] - pub next_level_exp: i64, - pub exp: String, - #[serde(rename = "expPercent")] - pub exp_percent: f64, - pub badges: Vec, - pub album_favorites_max: i64, -} - -#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct FavoriteFolder { - #[serde(rename = "0")] - pub n0: String, - #[serde(rename = "FID")] - pub fid: String, - #[serde(rename = "1")] - pub n1: String, - #[serde(rename = "UID")] - pub uid: String, - #[serde(rename = "2")] - pub n2: String, - pub name: String, -} - -#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)] -pub struct ActionResponse { - pub status: String, - pub msg: String, - #[serde(rename = "type")] - pub action_type: String, -} - -#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)] -pub struct CommentResponse { - #[serde(deserialize_with = "null_string")] - pub msg: String, - pub status: String, - pub aid: i64, - pub cid: i64, - pub spoiler: String, -} - -#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)] -pub struct GamePage { - pub games: Vec, - #[serde(deserialize_with = "fuzzy_i64")] - pub games_total: i64, - pub categories: Vec, - pub hot_games: Vec, -} - -#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)] -pub struct Game { - #[serde(deserialize_with = "fuzzy_i64")] - pub gid: i64, - pub title: String, - pub description: String, - pub tags: String, - pub link: String, - pub link_title: String, - pub photo: String, - #[serde(rename = "type")] - pub game_type: Vec, - pub categories: GameCategory, - #[serde(deserialize_with = "fuzzy_i64")] - pub update_at: i64, - #[serde(deserialize_with = "fuzzy_i64")] - pub total_clicks: i64, - #[serde(deserialize_with = "fuzzy_i64")] - pub order_rank: i64, - #[serde(deserialize_with = "fuzzy_i64")] - pub status: i64, - pub show_lang: Vec, -} - -#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)] -pub struct GameCategory { - #[serde(deserialize_with = "null_string")] - pub name: String, - #[serde(deserialize_with = "null_string")] - pub slug: String, -} - -///////////////////// - -macro_rules! enum_str { - ($name:ident { $($variant:ident($str:expr), )* }) => { - #[derive(Clone, Copy, Debug, Eq, PartialEq)] - pub enum $name { - $($variant,)* - } - - impl $name { - pub fn as_str(&self) -> &'static str { - match self { - $( $name::$variant => $str, )* - } - } - } - - impl Display for $name { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - match self { - $( $name::$variant => write!(f,"{}",$str), )* - } - } - } - - impl ::serde::Serialize for $name { - fn serialize(&self, serializer: S) -> Result - where S: ::serde::Serializer, - { - // 将枚举序列化为字符串。 - serializer.serialize_str(match *self { - $( $name::$variant => $str, )* - }) - } - } - - impl<'de> ::serde::Deserialize<'de> for $name { - fn deserialize(deserializer: D) -> Result - where D: ::serde::Deserializer<'de>, - { - struct Visitor; - - impl<'de> ::serde::de::Visitor<'de> for Visitor { - type Value = $name; - - fn expecting(&self, formatter: &mut ::std::fmt::Formatter) -> ::std::fmt::Result { - write!(formatter, "a string for {}", stringify!($name)) - } - - fn visit_str(self, value: &str) -> Result<$name, E> - where E: ::serde::de::Error, - { - match value { - $( $str => Ok($name::$variant), )* - _ => Err(E::invalid_value(::serde::de::Unexpected::Other( - &format!("unknown {} variant: {}", stringify!($name), value) - ), &self)), - } - } - } - - // 从字符串反序列化枚举。 - deserializer.deserialize_str(Visitor) - } - } - } -} - -enum_str!(SortBy { - Default(""), - New("mr"), - Favourite("tf"), - View("mv"), - ViewDay("mv_t"), - ViewWeek("mv_w"), - ViewMonth("mv_m"), -}); - -// enum_str!(ApiHost { -// Default("www.jmapinode.biz"), -// Branch1("www.jmapinode.top"), -// Branch2("www.jmapinode2.top"), -// Branch3("www.jmapinode3.top"), -// }); - -enum_str!(ApiHost { - Default("www.asjmapihost.cc"), - Branch1("www.jmapinode1.top"), - Branch2("www.jmapinode2.top"), - Branch3("www.jmapinode3.top"), -}); - -enum_str!(CdnHost { - Proxy1("cdn-msp.jmapiproxy1.monster"), - Proxy2("cdn-msp2.jmapiproxy1.monster"), -}); - -enum_str!(FavoritesOrder { - Mr("mr"), - Mp("mp"), -}); - -enum_str!(ActionStatus { - Ok("ok"), - Fail("fail"), -}); - -#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] -pub struct JmActionResponse { - pub status: ActionStatus, - pub msg: String, -} - -#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)] -pub struct FFPage { - pub list: Vec, - #[serde(deserialize_with = "fuzzy_i64")] - pub total: i64, - #[serde(deserialize_with = "fuzzy_i64")] - pub count: i64, - pub folder_list: Vec, -} - -//////////////// - -fn null_string<'de, D>(d: D) -> std::result::Result -where - D: serde::Deserializer<'de>, -{ - let value: serde_json::Value = serde::Deserialize::deserialize(d)?; - if value.is_null() { - Ok(String::default()) - } else if value.is_string() { - Ok(value.as_str().unwrap().to_string()) - } else { - Err(serde::de::Error::custom("type error")) - } -} - -fn fuzzy_i64<'de, D>(d: D) -> std::result::Result -where - D: serde::Deserializer<'de>, -{ - let value: serde_json::Value = serde::Deserialize::deserialize(d)?; - if value.is_i64() { - Ok(value.as_i64().unwrap()) - } else if value.is_string() { - let str = value.as_str().unwrap(); - let from: std::result::Result = std::str::FromStr::from_str(str); - match from { - Ok(from) => Ok(from), - Err(_) => Err(serde::de::Error::custom("parse error")), - } - } else { - Err(serde::de::Error::custom("type error")) - } -} - -fn fuzzy_option_i64<'de, D>(d: D) -> std::result::Result, D::Error> -where - D: serde::Deserializer<'de>, -{ - let value: serde_json::Value = serde::Deserialize::deserialize(d)?; - if value.is_null() { - Ok(None) - } else if value.is_i64() { - Ok(Some(value.as_i64().unwrap())) - } else if value.is_string() { - let str = value.as_str().unwrap(); - let from: std::result::Result = std::str::FromStr::from_str(str); - match from { - Ok(from) => Ok(Some(from)), - Err(_) => Err(serde::de::Error::custom("parse error")), - } - } else { - Err(serde::de::Error::custom("type error")) - } -} - -fn default_string() -> String { - String::default() -} - -fn default_i64() -> i64 { - 0 -} - -fn default_option_i64() -> Option { - None -} - -fn default_vec() -> Vec { - vec![] -} diff --git a/native/jmcomic-rs/src/lib.rs b/native/jmcomic-rs/src/lib.rs deleted file mode 100644 index 22fa1b1..0000000 --- a/native/jmcomic-rs/src/lib.rs +++ /dev/null @@ -1,615 +0,0 @@ -use std::collections::HashMap; -use std::ops::Deref; -use std::time::{Duration, SystemTime, UNIX_EPOCH}; - -pub use anyhow::Result; -pub use entities::*; -use rand::prelude::SliceRandom; -use reqwest::Method; -use serde_json::{json, to_string, Value}; -use tokio::sync::Mutex; -use tools::*; -use rand::Rng; - -mod entities; -#[cfg(test)] -mod tests; -mod tools; - -const APP_VERSION: &'static str = "1.6.1"; -const USER_AGENT: &'static str = "Mozilla/5.0 (Linux; Android 13; 8d41w854d Build/TQ1A.230205.002; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/114.0.5735.196 Safari/537.36"; -const APP_KEY: &'static str = "0b931a6f4b5ccc3f8d870839d07ae7b2"; -// FINAL_KEY 由JS函数 encodeKey(magic+salt) 计算而来 -const APP_SALT: &'static str = "18comicAPP"; -const APP_CONTENT_SALT: &'static str = "18comicAPPContent"; - -pub struct Client { - api_host: Mutex>, - cdn_host: Mutex>, - pub cookie: Mutex>, - pub agent: Mutex, - pub user_agent: Mutex, -} - -impl Client { - - pub fn new() -> Self { - Self { - api_host: Mutex::new(None), - cdn_host: Mutex::new(None), - cookie: Mutex::new(HashMap::::new()), - agent: Mutex::new( - reqwest::ClientBuilder::new() - .timeout(Duration::new(30, 0)) - .build() - .unwrap(), - ), - user_agent: Mutex::new(USER_AGENT.to_string()), - } - } - - pub async fn set_agent(&self, agent: reqwest::Client) { - let mut lock = self.agent.lock().await; - *lock = agent; - } - - pub async fn set_user_agent(&self, user_agent: String) { - let mut lock = self.user_agent.lock().await; - *lock = user_agent; - } - - pub async fn init_cookie(&self) -> anyhow::Result { - self.request_data(Method::GET, "setting", json!({})).await?; - // self.request_data(Method::GET, "browser_setting", json!({})) - // .await?; - Ok(self.cookie_str().await) - } - - pub async fn set_cookie(&self, cookie: &str) -> Result<()> { - self.http_cookie(&cookie.replace(";", "\n").trim()).await?; - Ok(()) - } - - pub async fn cookie_str(&self) -> String { - let lock = self.cookie.lock().await; - let mut cookies = Vec::::new(); - for (k, v) in lock.deref() { - cookies.push(format!("{}={};", k, v)); - } - let cstr = cookies.join(""); - return cstr; - } - - pub async fn set_api_host(&self, api_host: T) - where - T: Into>, - { - *(self.api_host.lock().await) = api_host.into(); - } - - pub async fn get_api_host(&self) -> Option { - self.api_host.lock().await.clone() - } - - pub async fn set_cdn_host(&self, cdn_host: T) - where - T: Into>, - { - *(self.cdn_host.lock().await) = cdn_host.into(); - } - - pub async fn get_cdn_host(&self) -> Option { - self.cdn_host.lock().await.clone() - } - - fn random_api_host(&self) -> ApiHost { - let vec = vec![ - ApiHost::Default, - ApiHost::Branch1, - ApiHost::Branch2, - ApiHost::Branch3, - ]; - vec.choose(&mut rand::thread_rng()).unwrap().clone() - } - - fn random_cdn_host(&self) -> CdnHost { - let vec = vec![CdnHost::Proxy1, CdnHost::Proxy2]; - vec.choose(&mut rand::thread_rng()).unwrap().clone() - } - - async fn api_host_string(&self) -> String { - format!( - "{}", - if let Some(api) = self.api_host.lock().await.clone() { - api - } else { - self.random_api_host() - } - ) - } - - async fn cdn_host_string(&self) -> String { - format!( - "{}", - if let Some(api) = self.cdn_host.lock().await.clone() { - api - } else { - self.random_cdn_host() - } - ) - } - - async fn http_cookie(&self, set_cookie_str: &str) -> Result<()> { - let set_cookie_str: Vec<&str> = set_cookie_str - .split("\n") - .map(|str| str.trim()) - .filter(|str| !str.is_empty()) - .collect(); - for set_cookie in set_cookie_str { - let set_cookie = set_cookie.split(";").nth(0).unwrap_or(""); - let set_cookie: Vec<&str> = set_cookie.split("=").collect(); - if set_cookie.len() == 2 { - let mut lock = self.cookie.lock().await; - lock.insert( - set_cookie.get(0).unwrap().to_string(), - set_cookie.get(1).unwrap().to_string(), - ); - drop(lock); - } - } - Ok(()) - } - - pub async fn request_data(&self, method: Method, path: &str, query: Value) -> Result { - let mut obj = query.as_object().unwrap().clone(); - obj.insert("key".to_string(), Value::from(APP_KEY)); - obj.insert("view_mode_debug".to_string(), Value::from("1")); - obj.insert("view_mode".to_string(), Value::from("null")); - let agent = self.agent.lock().await; - let request = agent.request( - method.clone(), - format!("https://{}/{}", &self.api_host_string().await, path), - ); - drop(agent); - let time = SystemTime::now() - .duration_since(UNIX_EPOCH) - .unwrap() - .as_secs(); - let token_param = format!("{},{}", &time, &APP_VERSION); - let token = hex::encode(md5::compute(format!("{}{}", time, APP_SALT)).0); - let decode_key = hex::encode(md5::compute(format!("{}{}", time, APP_CONTENT_SALT)).0); - // 18comicAPPContent - let request = request.header("Tokenparam", token_param); - let request = request.header("token", token); - let request = request.header("cookie", &self.cookie_str().await); - let request = request.header("User-Agent", self.current_user_agent().await); - let request = request.header("Sec-Fetch-Site", "same-origin"); - let request = request.header("Accept-Language", "zh-CN,zh-Hans;q=0.9"); - let request = request.header("Sec-Fetch-Mode", "cors"); - let request = request.header("Content-Type", "application/json"); - let request = request.header("Origin", "null"); - let request = request.header("Sec-Fetch-Dest", "empty"); - let request = match method { - Method::GET => request.query(&obj), - _ => request.form(&obj), - }; - let response = request.send().await?; - if path == "setting" || path == "login" { - for x in response.headers() { - if x.0 == "set-cookie" { - self.http_cookie(std::str::from_utf8(x.1.as_bytes())?) - .await?; - } - } - } - let text = response.text().await?; - let json: Value = from_str(&text)?; - let code = json.get("code"); - match code { - None => return Err(anyhow::anyhow!("error response")), - Some(code) => { - if code.is_i64() { - match code.as_i64().unwrap() { - 200 => {} - _ => { - return match json.get("errorMsg") { - None => Err(anyhow::anyhow!("unknown error")), - Some(msg) => { - if msg.is_string() { - Err(anyhow::anyhow!(msg.as_str().unwrap().to_string())) - } else { - Err(anyhow::anyhow!("unknown error")) - } - } - }; - } - } - } else { - return Err(anyhow::anyhow!("code not is number")); - } - } - } - if path == "browser_setting" { - return Ok(to_string(&json.get("data"))?); - } - match json.get("data") { - None => Err(anyhow::anyhow!("data error 2")), - Some(data) => { - if data.is_string() { - let data = data.as_str().unwrap(); - let data = tools::decrypt_jm(data, decode_key.as_bytes())?; - Ok(data) - } else { - Err(anyhow::anyhow!("data error 3")) - } - } - } - } - - async fn request serde::Deserialize<'de>>( - &self, - method: Method, - path: &str, - query: Value, - ) -> Result { - let response = self.request_data(method, path, query).await?; - Ok(from_str(&response)?) - } - - pub async fn login(&self, username: String, password: String) -> Result { - Ok(self - .request( - Method::POST, - "login", - json!({ - "username": username, - "password": password, - }), - ) - .await?) - } - - pub async fn categories(&self) -> Result { - Ok(self.request(Method::GET, "categories", json!({})).await?) - } - - pub async fn latest(&self) -> Result> { - Ok(self.request(Method::GET, "latest", json!({})).await?) - } - - pub async fn comics( - &self, - categories_slug: String, - sort_by: SortBy, - page: i64, - ) -> Result> { - Ok(self - .request( - Method::GET, - "categories/filter", - json!({ - "page": page, - "order": "", - "c": categories_slug, - "o": sort_by, - }), - ) - .await?) - } - - pub async fn comics_search( - &self, - search_query: String, - sort_by: SortBy, - page: i64, - ) -> Result> { - Ok(self - .request( - Method::GET, - "search", - json!({ - "page":page, - "search_query": search_query, - "o":sort_by, - }), - ) - .await?) - } - - pub async fn album(&self, id: i64) -> Result { - Ok(self - .request( - Method::GET, - "album", - json!({ - "comicName":"", - "id":id, - }), - ) - .await?) - } - - pub async fn chapter(&self, id: i64) -> Result { - Ok(self - .request( - Method::GET, - "chapter", - json!({ - "comicName":"", - "id":id, - }), - ) - .await?) - } - - pub async fn comment(&self, aid: i64, comment: String) -> Result { - Ok(self - .request( - Method::POST, - "comment", - json!({ - "comment":comment, - "aid":aid, - }), - ) - .await?) - } - - pub async fn child_comment( - &self, - aid: i64, - comment: String, - comment_id: i64, - ) -> Result { - Ok(self - .request( - Method::POST, - "comment", - json!({ - "comment":comment, - "comment_id":comment_id, - "aid":aid, - }), - ) - .await?) - } - - pub async fn comic_cover_url_3x4(&self, comic_id: i64) -> String { - format!( - "https://{}/media/albums/{}_3x4.jpg", - self.cdn_host_string().await, - comic_id - ) - } - - pub async fn comic_cover_url_square(&self, comic_id: i64) -> String { - format!( - "https://{}/media/albums/{}.jpg", - self.cdn_host_string().await, - comic_id - ) - } - - pub async fn comic_page_url(&self, id: i64, name: String) -> String { - format!( - "https://{}/media/photos/{}/{}?v=", - self.cdn_host_string().await, - id, - name, - ) - } - - // cnd host or api host all allow - pub async fn photo_url(&self, photo_name: String) -> String { - format!( - "https://{}/media/users/{}", - self.cdn_host_string().await, - photo_name - ) - } - - pub async fn videos(&self, sort_by: SortBy, page: i64) -> Result> { - Ok(self - .request( - Method::GET, - "videos", - json!({ - "o": sort_by, - "page": page, - }), - ) - .await?) - } - - // 评论 - pub async fn forum( - &self, - mode: Option, - aid: Option, - page: i64, - ) -> Result> { - if let Some(mode) = mode { - if let Some(aid) = aid { - return self - .request( - Method::GET, - "forum", - json!({ - "mode": mode, - "aid": aid, - "page": page, - }), - ) - .await; - } - return self - .request( - Method::GET, - "forum", - json!({ - "mode": mode, - "page": page, - }), - ) - .await; - } - self.request( - Method::GET, - "forum", - json!({ - "page": page, - }), - ) - .await - } - - pub async fn set_favorite(&self, aid: i64) -> Result { - Ok(self - .request( - Method::POST, - "favorite", - json!({ - "aid": aid, - }), - ) - .await?) - } - - pub async fn favorites( - &self, - folder_id: i64, - page: i64, - o: FavoritesOrder, - ) -> Result> { - Ok(self - .request( - Method::GET, - "favorite", - json!({ - "folder_id": folder_id, - "page": page, - "o": o, - }), - ) - .await?) - } - - pub async fn favorites_intro(&self) -> Result { - Ok(self - .request( - Method::GET, - "favorite", - json!({ - "folder_id": 0, - "o": FavoritesOrder::Mr, - }), - ) - .await?) - } - - pub async fn create_favorite_folder(&self, name: String) -> Result<()> { - let rsp: JmActionResponse = self - .request( - Method::POST, - "favorite_folder", - json!({ - "type":"add", - "folder_name":name, - }), - ) - .await?; - match rsp.status { - ActionStatus::Ok => Ok(()), - ActionStatus::Fail => Err(anyhow::Error::msg(rsp.msg)), - } - } - - pub async fn delete_favorite_folder(&self, folder_id: i64) -> Result<()> { - let rsp: JmActionResponse = self - .request( - Method::POST, - "favorite_folder", - json!({ - "type":"delete", - "folder_id":folder_id, - }), - ) - .await?; - match rsp.status { - ActionStatus::Ok => Ok(()), - ActionStatus::Fail => Err(anyhow::Error::msg(rsp.msg)), - } - } - - pub async fn comic_favorite_folder_move(&self, comic_id: i64, folder_id: i64) -> Result<()> { - let rsp: JmActionResponse = self - .request( - Method::POST, - "favorite_folder", - json!({ - "type":"move", - "folder_id":folder_id, - "aid":comic_id, - }), - ) - .await?; - match rsp.status { - ActionStatus::Ok => Ok(()), - ActionStatus::Fail => Err(anyhow::Error::msg(rsp.msg)), - } - } - - pub async fn games(&self, page: i64) -> Result { - Ok(self - .request( - Method::GET, - "games", - json!({ - "page": page, - }), - ) - .await?) - } - - pub async fn watch_list(&self, page: i64) -> Result> { - Ok(self - .request( - Method::GET, - "watch_list", - json!({ - "page": page, - }), - ) - .await?) - } - - pub async fn current_user_agent(&self) -> String { - let lock = self.user_agent.lock().await; - lock.clone() - } - - pub fn rand_user_agent() -> String { - // mobile version - let adnroid_version = rand::thread_rng().gen_range(11..=15); - // webkit version - // rand a str [0-9a0z]{9} like 8d41w854d - let boot_id = rand::thread_rng() - .sample_iter(&rand::distributions::Alphanumeric) - .take(9) - .map(char::from) - .collect::(); - // random from TQ1A / TQ2A ... TQ9A ... TQ1Z - let mobile_model = format!( - "TQ{}{}", - rand::thread_rng().gen_range(0..=9), - rand::thread_rng().gen_range('A'..='Z'), - ); - // random 230205 - 330205 - let build_version = rand::thread_rng().gen_range(230205..=330205); - let build_version2= rand::thread_rng().gen_range(1..=9); - // - format!( - "Mozilla/5.0 (Linux; Android {adnroid_version}; {boot_id} Build/{mobile_model}.{build_version}.00{build_version2}; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/114.0.5735.196 Safari/537.36", - ) - } -} diff --git a/native/jmcomic-rs/src/tests.rs b/native/jmcomic-rs/src/tests.rs deleted file mode 100644 index 1994665..0000000 --- a/native/jmcomic-rs/src/tests.rs +++ /dev/null @@ -1,132 +0,0 @@ -use serde_json::json; - -use crate::{Client, FavoritesOrder, SortBy}; -use crate::{Method, Result}; - -/// 打印结果 -fn print(result: Result) { - match result { - Ok(data) => println!("{}", serde_json::to_string(&data).unwrap()), - Err(err) => panic!("{}", err), - } -} - -async fn client() -> Client { - let client = Client::new(); - client.set_user_agent(Client::rand_user_agent()).await; - client -} - -#[tokio::test] -async fn request_data() { - let rsp = client().await - .request_data(Method::GET, "setting", json!({})) - .await; - match rsp { - Ok(text) => println!("{}", text), - Err(err) => panic!("{}", err), - } -} - -async fn login_client() -> Client { - let client = client().await; - client - .login("username".to_owned(), "password".to_owned()) - .await - .unwrap(); - client -} - -#[tokio::test] -async fn login_request_data() { - match login_client() - .await - .request_data(Method::GET, "favorite", json!({"page":1})) - .await - { - Ok(text) => println!("{}", text), - Err(err) => panic!("{}", err), - } -} - -#[tokio::test] -async fn categories() { - print(client().await.categories().await) -} - -#[tokio::test] -async fn latest() { - print(client().await.latest().await) -} - -#[tokio::test] -async fn comics() { - print(client().await.comics("".to_string(), SortBy::Default, 1).await) -} - -#[tokio::test] -async fn album() { - print(client().await.album(215435).await) -} - -#[tokio::test] -async fn chapter() { - print(client().await.chapter(215435).await) -} - -#[tokio::test] -async fn videos() { - print(client().await.videos(SortBy::View, 2).await) -} - -#[tokio::test] -async fn forum() { - print(client().await.forum(None, None, 100).await) -} - -#[tokio::test] -async fn set_favorite() { - print(login_client().await.set_favorite(302608).await); -} - -#[tokio::test] -async fn favorites() { - print( - login_client() - .await - .favorites(0, 1, FavoritesOrder::Mp) - .await, - ); -} - -#[tokio::test] -async fn favorites_intro() { - print(login_client().await.favorites_intro().await); -} - -#[tokio::test] -async fn create_favorite_folder() { - print( - login_client() - .await - .create_favorite_folder("MY_FOLDER".to_string()) - .await, - ); -} - -#[tokio::test] -async fn games() { - print(client().await.games(1).await); -} - -#[tokio::test] -async fn comics_search() { - print( - client().await - .comics_search("ABC".to_owned(), SortBy::Default, 1) - .await, - ); -} - -#[tokio::test] -async fn test() {} diff --git a/native/jmcomic-rs/src/tools.rs b/native/jmcomic-rs/src/tools.rs deleted file mode 100644 index 7883d94..0000000 --- a/native/jmcomic-rs/src/tools.rs +++ /dev/null @@ -1,23 +0,0 @@ -use aes::Aes256; -use block_modes::block_padding::Pkcs7; -use block_modes::{BlockMode, Ecb}; - -type AesEcb = Ecb; - -pub(crate) fn decrypt_jm(data: &str, key: &[u8]) -> anyhow::Result { - let data = base64::decode(data)?; - let data = aes_decrypt_ecb(data, key)?; - let data = std::str::from_utf8(data.as_slice())?; - Ok(data.to_string()) -} - -fn aes_decrypt_ecb(data: Vec, key: &[u8]) -> anyhow::Result> { - Ok(AesEcb::new_from_slices(key, "".as_bytes())?.decrypt_vec(data.as_slice())?) -} - -/// FROM STRING 并打印出错的位置 -pub fn from_str serde::Deserialize<'de>>(json: &str) -> anyhow::Result { - Ok(serde_path_to_error::deserialize( - &mut serde_json::Deserializer::from_str(json), - )?) -} diff --git a/pubspec.yaml b/pubspec.yaml index 17b26e1..9667bd3 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -3,7 +3,7 @@ description: jasmine comic browser publish_to: 'none' -version: 1.6.1+23 +version: 1.6.6+26 environment: sdk: ">=2.15.1 <3.0.0"