mirror of
https://github.com/venera-app/venera.git
synced 2025-12-16 15:11:14 +00:00
Compare commits
9 Commits
5a76a10fb2
...
2d27f7d650
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2d27f7d650 | ||
| e1fbdfbd50 | |||
| 0a5b70b161 | |||
| 9173665afe | |||
| e0ea449c17 | |||
| c438a84537 | |||
| 8c625e212a | |||
| ab786ed2ab | |||
| d9303aab2e |
3
.github/workflows/main.yml
vendored
3
.github/workflows/main.yml
vendored
@@ -170,6 +170,9 @@ jobs:
|
|||||||
sudo apt-get update -y
|
sudo apt-get update -y
|
||||||
sudo apt-get install -y ninja-build libgtk-3-dev webkit2gtk-4.1
|
sudo apt-get install -y ninja-build libgtk-3-dev webkit2gtk-4.1
|
||||||
dart pub global activate flutter_to_debian
|
dart pub global activate flutter_to_debian
|
||||||
|
- name: "Patch font"
|
||||||
|
run: |
|
||||||
|
dart run patch/font.dart
|
||||||
- run: python3 debian/build.py arm64
|
- run: python3 debian/build.py arm64
|
||||||
- uses: actions/upload-artifact@v4
|
- uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ export "widget_utils.dart";
|
|||||||
export "context.dart";
|
export "context.dart";
|
||||||
|
|
||||||
class _App {
|
class _App {
|
||||||
final version = "1.5.1";
|
final version = "1.5.2";
|
||||||
|
|
||||||
bool get isAndroid => Platform.isAndroid;
|
bool get isAndroid => Platform.isAndroid;
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import 'dart:collection';
|
||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
import 'dart:ffi';
|
import 'dart:ffi';
|
||||||
import 'dart:isolate';
|
import 'dart:isolate';
|
||||||
@@ -213,12 +214,10 @@ class LocalFavoritesManager with ChangeNotifier {
|
|||||||
|
|
||||||
late Map<String, int> counts;
|
late Map<String, int> counts;
|
||||||
|
|
||||||
|
var _hashedIds = <int, int>{};
|
||||||
|
|
||||||
int get totalComics {
|
int get totalComics {
|
||||||
int total = 0;
|
return _hashedIds.length;
|
||||||
for (var t in counts.values) {
|
|
||||||
total += t;
|
|
||||||
}
|
|
||||||
return total;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
int folderComics(String folder) {
|
int folderComics(String folder) {
|
||||||
@@ -280,6 +279,48 @@ class LocalFavoritesManager with ChangeNotifier {
|
|||||||
for (var folder in folderNames) {
|
for (var folder in folderNames) {
|
||||||
counts[folder] = count(folder);
|
counts[folder] = count(folder);
|
||||||
}
|
}
|
||||||
|
_initHashedIds(folderNames, _db.handle).then((value) {
|
||||||
|
_hashedIds = value;
|
||||||
|
notifyListeners();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void refreshHashedIds() {
|
||||||
|
_initHashedIds(folderNames, _db.handle).then((value) {
|
||||||
|
_hashedIds = value;
|
||||||
|
notifyListeners();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void reduceHashedId(String id, int type) {
|
||||||
|
var hash = id.hashCode ^ type;
|
||||||
|
if (_hashedIds.containsKey(hash)) {
|
||||||
|
if (_hashedIds[hash]! > 1) {
|
||||||
|
_hashedIds[hash] = _hashedIds[hash]! - 1;
|
||||||
|
} else {
|
||||||
|
_hashedIds.remove(hash);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static Future<Map<int, int>> _initHashedIds(
|
||||||
|
List<String> folders, Pointer<void> p) {
|
||||||
|
return Isolate.run(() {
|
||||||
|
var db = sqlite3.fromPointer(p);
|
||||||
|
var hashedIds = <int, int>{};
|
||||||
|
for (var folder in folders) {
|
||||||
|
var rows = db.select("""
|
||||||
|
select id, type from "$folder";
|
||||||
|
""");
|
||||||
|
for (var row in rows) {
|
||||||
|
var id = row["id"] as String;
|
||||||
|
var type = row["type"] as int;
|
||||||
|
var hash = id.hashCode ^ type;
|
||||||
|
hashedIds[hash] = (hashedIds[hash] ?? 0) + 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return hashedIds;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
List<String> find(String id, ComicType type) {
|
List<String> find(String id, ComicType type) {
|
||||||
@@ -559,7 +600,6 @@ class LocalFavoritesManager with ChangeNotifier {
|
|||||||
/// return true if success, false if already exists
|
/// return true if success, false if already exists
|
||||||
bool addComic(String folder, FavoriteItem comic,
|
bool addComic(String folder, FavoriteItem comic,
|
||||||
[int? order, String? updateTime]) {
|
[int? order, String? updateTime]) {
|
||||||
_modifiedAfterLastCache = true;
|
|
||||||
if (!existsFolder(folder)) {
|
if (!existsFolder(folder)) {
|
||||||
throw Exception("Folder does not exists");
|
throw Exception("Folder does not exists");
|
||||||
}
|
}
|
||||||
@@ -614,14 +654,14 @@ class LocalFavoritesManager with ChangeNotifier {
|
|||||||
} else {
|
} else {
|
||||||
counts[folder] = counts[folder]! + 1;
|
counts[folder] = counts[folder]! + 1;
|
||||||
}
|
}
|
||||||
|
var hash = comic.id.hashCode ^ comic.type.value;
|
||||||
|
_hashedIds[hash] = (_hashedIds[hash] ?? 0) + 1;
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void moveFavorite(
|
void moveFavorite(
|
||||||
String sourceFolder, String targetFolder, String id, ComicType type) {
|
String sourceFolder, String targetFolder, String id, ComicType type) {
|
||||||
_modifiedAfterLastCache = true;
|
|
||||||
|
|
||||||
if (!existsFolder(sourceFolder)) {
|
if (!existsFolder(sourceFolder)) {
|
||||||
throw Exception("Source folder does not exist");
|
throw Exception("Source folder does not exist");
|
||||||
}
|
}
|
||||||
@@ -655,8 +695,6 @@ class LocalFavoritesManager with ChangeNotifier {
|
|||||||
|
|
||||||
void batchMoveFavorites(
|
void batchMoveFavorites(
|
||||||
String sourceFolder, String targetFolder, List<FavoriteItem> items) {
|
String sourceFolder, String targetFolder, List<FavoriteItem> items) {
|
||||||
_modifiedAfterLastCache = true;
|
|
||||||
|
|
||||||
if (!existsFolder(sourceFolder)) {
|
if (!existsFolder(sourceFolder)) {
|
||||||
throw Exception("Source folder does not exist");
|
throw Exception("Source folder does not exist");
|
||||||
}
|
}
|
||||||
@@ -691,25 +729,15 @@ class LocalFavoritesManager with ChangeNotifier {
|
|||||||
_db.execute("COMMIT");
|
_db.execute("COMMIT");
|
||||||
|
|
||||||
// Update counts
|
// Update counts
|
||||||
if (counts[targetFolder] == null) {
|
|
||||||
counts[targetFolder] = count(targetFolder);
|
counts[targetFolder] = count(targetFolder);
|
||||||
} else {
|
|
||||||
counts[targetFolder] = counts[targetFolder]! + items.length;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (counts[sourceFolder] != null) {
|
|
||||||
counts[sourceFolder] = counts[sourceFolder]! - items.length;
|
|
||||||
} else {
|
|
||||||
counts[sourceFolder] = count(sourceFolder);
|
counts[sourceFolder] = count(sourceFolder);
|
||||||
}
|
refreshHashedIds();
|
||||||
|
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
}
|
}
|
||||||
|
|
||||||
void batchCopyFavorites(
|
void batchCopyFavorites(
|
||||||
String sourceFolder, String targetFolder, List<FavoriteItem> items) {
|
String sourceFolder, String targetFolder, List<FavoriteItem> items) {
|
||||||
_modifiedAfterLastCache = true;
|
|
||||||
|
|
||||||
if (!existsFolder(sourceFolder)) {
|
if (!existsFolder(sourceFolder)) {
|
||||||
throw Exception("Source folder does not exist");
|
throw Exception("Source folder does not exist");
|
||||||
}
|
}
|
||||||
@@ -740,18 +768,14 @@ class LocalFavoritesManager with ChangeNotifier {
|
|||||||
_db.execute("COMMIT");
|
_db.execute("COMMIT");
|
||||||
|
|
||||||
// Update counts
|
// Update counts
|
||||||
if (counts[targetFolder] == null) {
|
|
||||||
counts[targetFolder] = count(targetFolder);
|
counts[targetFolder] = count(targetFolder);
|
||||||
} else {
|
refreshHashedIds();
|
||||||
counts[targetFolder] = counts[targetFolder]! + items.length;
|
|
||||||
}
|
|
||||||
|
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// delete a folder
|
/// delete a folder
|
||||||
void deleteFolder(String name) {
|
void deleteFolder(String name) {
|
||||||
_modifiedAfterLastCache = true;
|
|
||||||
_db.execute("""
|
_db.execute("""
|
||||||
drop table "$name";
|
drop table "$name";
|
||||||
""");
|
""");
|
||||||
@@ -760,11 +784,11 @@ class LocalFavoritesManager with ChangeNotifier {
|
|||||||
where folder_name == ?;
|
where folder_name == ?;
|
||||||
""", [name]);
|
""", [name]);
|
||||||
counts.remove(name);
|
counts.remove(name);
|
||||||
|
refreshHashedIds();
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
}
|
}
|
||||||
|
|
||||||
void deleteComicWithId(String folder, String id, ComicType type) {
|
void deleteComicWithId(String folder, String id, ComicType type) {
|
||||||
_modifiedAfterLastCache = true;
|
|
||||||
LocalFavoriteImageProvider.delete(id, type.value);
|
LocalFavoriteImageProvider.delete(id, type.value);
|
||||||
_db.execute("""
|
_db.execute("""
|
||||||
delete from "$folder"
|
delete from "$folder"
|
||||||
@@ -775,11 +799,11 @@ class LocalFavoritesManager with ChangeNotifier {
|
|||||||
} else {
|
} else {
|
||||||
counts[folder] = count(folder);
|
counts[folder] = count(folder);
|
||||||
}
|
}
|
||||||
|
reduceHashedId(id, type.value);
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
}
|
}
|
||||||
|
|
||||||
void batchDeleteComics(String folder, List<FavoriteItem> comics) {
|
void batchDeleteComics(String folder, List<FavoriteItem> comics) {
|
||||||
_modifiedAfterLastCache = true;
|
|
||||||
_db.execute("BEGIN TRANSACTION");
|
_db.execute("BEGIN TRANSACTION");
|
||||||
try {
|
try {
|
||||||
for (var comic in comics) {
|
for (var comic in comics) {
|
||||||
@@ -800,11 +824,13 @@ class LocalFavoritesManager with ChangeNotifier {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
_db.execute("COMMIT");
|
_db.execute("COMMIT");
|
||||||
|
for (var comic in comics) {
|
||||||
|
reduceHashedId(comic.id, comic.type.value);
|
||||||
|
}
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
}
|
}
|
||||||
|
|
||||||
void batchDeleteComicsInAllFolders(List<ComicID> comics) {
|
void batchDeleteComicsInAllFolders(List<ComicID> comics) {
|
||||||
_modifiedAfterLastCache = true;
|
|
||||||
_db.execute("BEGIN TRANSACTION");
|
_db.execute("BEGIN TRANSACTION");
|
||||||
var folderNames = _getFolderNamesWithDB();
|
var folderNames = _getFolderNamesWithDB();
|
||||||
try {
|
try {
|
||||||
@@ -824,6 +850,10 @@ class LocalFavoritesManager with ChangeNotifier {
|
|||||||
}
|
}
|
||||||
initCounts();
|
initCounts();
|
||||||
_db.execute("COMMIT");
|
_db.execute("COMMIT");
|
||||||
|
for (var comic in comics) {
|
||||||
|
var hash = comic.id.hashCode ^ comic.type.value;
|
||||||
|
_hashedIds.remove(hash);
|
||||||
|
}
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -908,7 +938,6 @@ class LocalFavoritesManager with ChangeNotifier {
|
|||||||
markAsRead(id, type);
|
markAsRead(id, type);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
_modifiedAfterLastCache = true;
|
|
||||||
var followUpdatesFolder = appdata.settings['followUpdatesFolder'];
|
var followUpdatesFolder = appdata.settings['followUpdatesFolder'];
|
||||||
for (final folder in folderNames) {
|
for (final folder in folderNames) {
|
||||||
var rows = _db.select("""
|
var rows = _db.select("""
|
||||||
@@ -1029,28 +1058,9 @@ class LocalFavoritesManager with ChangeNotifier {
|
|||||||
notifyListeners();
|
notifyListeners();
|
||||||
}
|
}
|
||||||
|
|
||||||
final _cachedFavoritedIds = <String, bool>{};
|
|
||||||
|
|
||||||
bool isExist(String id, ComicType type) {
|
bool isExist(String id, ComicType type) {
|
||||||
if (_modifiedAfterLastCache) {
|
var hash = id.hashCode ^ type.value;
|
||||||
_cacheFavoritedIds();
|
return _hashedIds.containsKey(hash);
|
||||||
}
|
|
||||||
return _cachedFavoritedIds.containsKey("$id@${type.value}");
|
|
||||||
}
|
|
||||||
|
|
||||||
bool _modifiedAfterLastCache = true;
|
|
||||||
|
|
||||||
void _cacheFavoritedIds() {
|
|
||||||
_modifiedAfterLastCache = false;
|
|
||||||
_cachedFavoritedIds.clear();
|
|
||||||
for (var folder in folderNames) {
|
|
||||||
var rows = _db.select("""
|
|
||||||
select id, type from "$folder";
|
|
||||||
""");
|
|
||||||
for (var row in rows) {
|
|
||||||
_cachedFavoritedIds["${row["id"]}@${row["type"]}"] = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void updateInfo(String folder, FavoriteItem comic, [bool notify = true]) {
|
void updateInfo(String folder, FavoriteItem comic, [bool notify = true]) {
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import 'dart:async';
|
|||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
import 'package:venera/foundation/favorites.dart';
|
import 'package:venera/foundation/favorites.dart';
|
||||||
import 'package:venera/foundation/log.dart';
|
import 'package:venera/foundation/log.dart';
|
||||||
|
import 'package:venera/utils/channel.dart';
|
||||||
|
|
||||||
class ComicUpdateResult {
|
class ComicUpdateResult {
|
||||||
final bool updated;
|
final bool updated;
|
||||||
@@ -62,6 +63,7 @@ Future<ComicUpdateResult> updateComic(
|
|||||||
return ComicUpdateResult(updated, null);
|
return ComicUpdateResult(updated, null);
|
||||||
} catch (e, s) {
|
} catch (e, s) {
|
||||||
Log.error("Check Updates", e, s);
|
Log.error("Check Updates", e, s);
|
||||||
|
await Future.delayed(const Duration(seconds: 2));
|
||||||
retries--;
|
retries--;
|
||||||
if (retries == 0) {
|
if (retries == 0) {
|
||||||
return ComicUpdateResult(false, e.toString());
|
return ComicUpdateResult(false, e.toString());
|
||||||
@@ -114,9 +116,36 @@ void updateFolderBase(
|
|||||||
current = 0;
|
current = 0;
|
||||||
stream.add(UpdateProgress(total, current, errors, updated));
|
stream.add(UpdateProgress(total, current, errors, updated));
|
||||||
|
|
||||||
var futures = <Future>[];
|
var channel = Channel<FavoriteItemWithUpdateInfo>(10);
|
||||||
|
|
||||||
|
// Producer
|
||||||
|
() async {
|
||||||
|
var c = 0;
|
||||||
for (var comic in comicsToUpdate) {
|
for (var comic in comicsToUpdate) {
|
||||||
var future = updateComic(comic, folder).then((result) {
|
await channel.push(comic);
|
||||||
|
c++;
|
||||||
|
// Throttle
|
||||||
|
if (c % 5 == 0) {
|
||||||
|
var delay = c % 100 + 1;
|
||||||
|
if (delay > 10) {
|
||||||
|
delay = 10;
|
||||||
|
}
|
||||||
|
await Future.delayed(Duration(seconds: delay));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
channel.close();
|
||||||
|
}();
|
||||||
|
|
||||||
|
// Consumers
|
||||||
|
var updateFutures = <Future>[];
|
||||||
|
for (var i = 0; i < 5; i++) {
|
||||||
|
var f = () async {
|
||||||
|
while (true) {
|
||||||
|
var comic = await channel.pop();
|
||||||
|
if (comic == null) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
var result = await updateComic(comic, folder);
|
||||||
current++;
|
current++;
|
||||||
if (result.updated) {
|
if (result.updated) {
|
||||||
updated++;
|
updated++;
|
||||||
@@ -124,13 +153,13 @@ void updateFolderBase(
|
|||||||
if (result.errorMessage != null) {
|
if (result.errorMessage != null) {
|
||||||
errors++;
|
errors++;
|
||||||
}
|
}
|
||||||
stream.add(
|
stream.add(UpdateProgress(total, current, errors, updated, comic, result.errorMessage));
|
||||||
UpdateProgress(total, current, errors, updated, comic, result.errorMessage));
|
}
|
||||||
});
|
}();
|
||||||
futures.add(future);
|
updateFutures.add(f);
|
||||||
}
|
}
|
||||||
|
|
||||||
await Future.wait(futures);
|
await Future.wait(updateFutures);
|
||||||
|
|
||||||
if (updated > 0) {
|
if (updated > 0) {
|
||||||
LocalFavoritesManager().notifyChanges();
|
LocalFavoritesManager().notifyChanges();
|
||||||
|
|||||||
@@ -423,6 +423,7 @@ class LocalManager with ChangeNotifier {
|
|||||||
if (comic.hasChapters) {
|
if (comic.hasChapters) {
|
||||||
var cid =
|
var cid =
|
||||||
ep is int ? comic.chapters!.ids.elementAt(ep - 1) : (ep as String);
|
ep is int ? comic.chapters!.ids.elementAt(ep - 1) : (ep as String);
|
||||||
|
cid = getChapterDirectoryName(cid);
|
||||||
directory = Directory(FilePath.join(directory.path, cid));
|
directory = Directory(FilePath.join(directory.path, cid));
|
||||||
}
|
}
|
||||||
var files = <File>[];
|
var files = <File>[];
|
||||||
@@ -600,7 +601,10 @@ class LocalManager with ChangeNotifier {
|
|||||||
}
|
}
|
||||||
var shouldRemovedDirs = <Directory>[];
|
var shouldRemovedDirs = <Directory>[];
|
||||||
for (var chapter in chapters) {
|
for (var chapter in chapters) {
|
||||||
var dir = Directory(FilePath.join(c.baseDir, chapter));
|
var dir = Directory(FilePath.join(
|
||||||
|
c.baseDir,
|
||||||
|
getChapterDirectoryName(chapter),
|
||||||
|
));
|
||||||
if (dir.existsSync()) {
|
if (dir.existsSync()) {
|
||||||
shouldRemovedDirs.add(dir);
|
shouldRemovedDirs.add(dir);
|
||||||
}
|
}
|
||||||
@@ -668,6 +672,21 @@ class LocalManager with ChangeNotifier {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static String getChapterDirectoryName(String name) {
|
||||||
|
var builder = StringBuffer();
|
||||||
|
for (var i = 0; i < name.length; i++) {
|
||||||
|
var char = name[i];
|
||||||
|
if (char == '/' || char == '\\' || char == ':' || char == '*' ||
|
||||||
|
char == '?'
|
||||||
|
|| char == '"' || char == '<' || char == '>' || char == '|') {
|
||||||
|
builder.write('_');
|
||||||
|
} else {
|
||||||
|
builder.write(char);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return builder.toString();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
enum LocalSortType {
|
enum LocalSortType {
|
||||||
|
|||||||
@@ -199,6 +199,7 @@ class _MyAppState extends State<MyApp> with WidgetsBindingObserver {
|
|||||||
tertiary = light.tertiary;
|
tertiary = light.tertiary;
|
||||||
}
|
}
|
||||||
return MaterialApp(
|
return MaterialApp(
|
||||||
|
title: "venera",
|
||||||
home: home,
|
home: home,
|
||||||
debugShowCheckedModeBanner: false,
|
debugShowCheckedModeBanner: false,
|
||||||
theme: getTheme(primary, secondary, tertiary, Brightness.light),
|
theme: getTheme(primary, secondary, tertiary, Brightness.light),
|
||||||
@@ -246,7 +247,7 @@ class _MyAppState extends State<MyApp> with WidgetsBindingObserver {
|
|||||||
/// https://github.com/flutter/flutter/issues/161086
|
/// https://github.com/flutter/flutter/issues/161086
|
||||||
var isPaddingCheckError =
|
var isPaddingCheckError =
|
||||||
MediaQuery.of(context).viewPadding.top <= 0 ||
|
MediaQuery.of(context).viewPadding.top <= 0 ||
|
||||||
MediaQuery.of(context).viewPadding.top > 50;
|
MediaQuery.of(context).viewPadding.top > 200;
|
||||||
|
|
||||||
if (isPaddingCheckError && Platform.isAndroid) {
|
if (isPaddingCheckError && Platform.isAndroid) {
|
||||||
widget = MediaQuery(
|
widget = MediaQuery(
|
||||||
|
|||||||
@@ -107,7 +107,21 @@ class ImagesDownloadTask extends DownloadTask with _TransferSpeedMixin {
|
|||||||
var local = LocalManager().find(id, comicType);
|
var local = LocalManager().find(id, comicType);
|
||||||
if (path != null) {
|
if (path != null) {
|
||||||
if (local == null) {
|
if (local == null) {
|
||||||
Directory(path!).deleteIgnoreError(recursive: true);
|
Future.sync(() async {
|
||||||
|
var tasks = this.tasks.values.toList();
|
||||||
|
for (var i = 0; i < tasks.length; i++) {
|
||||||
|
if (!tasks[i].isComplete) {
|
||||||
|
tasks[i].cancel();
|
||||||
|
await tasks[i].wait();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
await Directory(path!).delete(recursive: true);
|
||||||
|
}
|
||||||
|
catch(e) {
|
||||||
|
Log.error("Download", "Failed to delete directory: $e");
|
||||||
|
}
|
||||||
|
});
|
||||||
} else if (chapters != null) {
|
} else if (chapters != null) {
|
||||||
for (var c in chapters!) {
|
for (var c in chapters!) {
|
||||||
var dir = Directory(FilePath.join(path!, c));
|
var dir = Directory(FilePath.join(path!, c));
|
||||||
@@ -197,7 +211,9 @@ class ImagesDownloadTask extends DownloadTask with _TransferSpeedMixin {
|
|||||||
if (comic!.chapters != null) {
|
if (comic!.chapters != null) {
|
||||||
saveTo = Directory(FilePath.join(
|
saveTo = Directory(FilePath.join(
|
||||||
path!,
|
path!,
|
||||||
|
LocalManager.getChapterDirectoryName(
|
||||||
_images!.keys.elementAt(_chapter),
|
_images!.keys.elementAt(_chapter),
|
||||||
|
),
|
||||||
));
|
));
|
||||||
if (!saveTo.existsSync()) {
|
if (!saveTo.existsSync()) {
|
||||||
saveTo.createSync(recursive: true);
|
saveTo.createSync(recursive: true);
|
||||||
|
|||||||
58
lib/utils/channel.dart
Normal file
58
lib/utils/channel.dart
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
import 'dart:async';
|
||||||
|
import 'dart:collection';
|
||||||
|
|
||||||
|
class Channel<T> {
|
||||||
|
final Queue<T> _queue;
|
||||||
|
|
||||||
|
final int size;
|
||||||
|
|
||||||
|
Channel(this.size) : _queue = Queue<T>();
|
||||||
|
|
||||||
|
Completer? _releaseCompleter;
|
||||||
|
|
||||||
|
Completer? _pushCompleter;
|
||||||
|
|
||||||
|
var currentSize = 0;
|
||||||
|
|
||||||
|
var isClosed = false;
|
||||||
|
|
||||||
|
Future<void> push(T item) async {
|
||||||
|
if (currentSize >= size) {
|
||||||
|
_releaseCompleter ??= Completer();
|
||||||
|
return _releaseCompleter!.future.then((_) {
|
||||||
|
if (isClosed) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
_queue.addLast(item);
|
||||||
|
currentSize++;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
_queue.addLast(item);
|
||||||
|
currentSize++;
|
||||||
|
_pushCompleter?.complete();
|
||||||
|
_pushCompleter = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<T?> pop() async {
|
||||||
|
while (_queue.isEmpty) {
|
||||||
|
if (isClosed) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
_pushCompleter ??= Completer();
|
||||||
|
await _pushCompleter!.future;
|
||||||
|
}
|
||||||
|
var item = _queue.removeFirst();
|
||||||
|
currentSize--;
|
||||||
|
if (_releaseCompleter != null && currentSize < size) {
|
||||||
|
_releaseCompleter!.complete();
|
||||||
|
_releaseCompleter = null;
|
||||||
|
}
|
||||||
|
return item;
|
||||||
|
}
|
||||||
|
|
||||||
|
void close() {
|
||||||
|
isClosed = true;
|
||||||
|
_pushCompleter?.complete();
|
||||||
|
_releaseCompleter?.complete();
|
||||||
|
}
|
||||||
|
}
|
||||||
28
patch/font.dart
Normal file
28
patch/font.dart
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
import 'dart:io';
|
||||||
|
|
||||||
|
import 'package:archive/archive_io.dart';
|
||||||
|
import 'package:dio/dio.dart';
|
||||||
|
|
||||||
|
void main() async {
|
||||||
|
const harmonySansLink = "https://developer.huawei.com/images/download/general/HarmonyOS-Sans.zip";
|
||||||
|
|
||||||
|
var dio = Dio();
|
||||||
|
await dio.download(harmonySansLink, "HarmonyOS-Sans.zip");
|
||||||
|
await extractFileToDisk("HarmonyOS-Sans.zip", "./assets/");
|
||||||
|
File("HarmonyOS-Sans.zip").deleteSync();
|
||||||
|
|
||||||
|
var pubspec = await File("pubspec.yaml").readAsString();
|
||||||
|
pubspec = pubspec.replaceFirst("# fonts:",
|
||||||
|
""" fonts:
|
||||||
|
- family: HarmonyOS Sans
|
||||||
|
fonts:
|
||||||
|
- asset: assets/HarmonyOS Sans/HarmonyOS_Sans_SC/HarmonyOS_Sans_SC_Regular.ttf
|
||||||
|
""");
|
||||||
|
await File("pubspec.yaml").writeAsString(pubspec);
|
||||||
|
|
||||||
|
var mainDart = await File("lib/main.dart").readAsString();
|
||||||
|
mainDart = mainDart.replaceFirst("Noto Sans CJK", "HarmonyOS Sans");
|
||||||
|
await File("lib/main.dart").writeAsString(mainDart);
|
||||||
|
|
||||||
|
print("Successfully patched font.");
|
||||||
|
}
|
||||||
18
pubspec.lock
18
pubspec.lock
@@ -33,6 +33,14 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.0.4"
|
version: "1.0.4"
|
||||||
|
archive:
|
||||||
|
dependency: "direct dev"
|
||||||
|
description:
|
||||||
|
name: archive
|
||||||
|
sha256: "2fde1607386ab523f7a36bb3e7edb43bd58e6edaf2ffb29d8a6d578b297fdbbd"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "4.0.7"
|
||||||
args:
|
args:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -770,6 +778,14 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "4.0.0"
|
version: "4.0.0"
|
||||||
|
posix:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: posix
|
||||||
|
sha256: "6323a5b0fa688b6a010df4905a56b00181479e6d10534cecfecede2aa55add61"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "6.0.3"
|
||||||
rhttp:
|
rhttp:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
@@ -1116,4 +1132,4 @@ packages:
|
|||||||
version: "0.0.12"
|
version: "0.0.12"
|
||||||
sdks:
|
sdks:
|
||||||
dart: ">=3.8.0 <4.0.0"
|
dart: ">=3.8.0 <4.0.0"
|
||||||
flutter: ">=3.35.2"
|
flutter: ">=3.35.5"
|
||||||
|
|||||||
@@ -2,11 +2,11 @@ name: venera
|
|||||||
description: "A comic app."
|
description: "A comic app."
|
||||||
publish_to: 'none'
|
publish_to: 'none'
|
||||||
|
|
||||||
version: 1.5.1+151
|
version: 1.5.2+152
|
||||||
|
|
||||||
environment:
|
environment:
|
||||||
sdk: '>=3.8.0 <4.0.0'
|
sdk: '>=3.8.0 <4.0.0'
|
||||||
flutter: 3.35.3
|
flutter: 3.35.5
|
||||||
|
|
||||||
dependencies:
|
dependencies:
|
||||||
flutter:
|
flutter:
|
||||||
@@ -94,6 +94,7 @@ dev_dependencies:
|
|||||||
flutter_lints: ^5.0.0
|
flutter_lints: ^5.0.0
|
||||||
flutter_to_arch: ^1.0.1
|
flutter_to_arch: ^1.0.1
|
||||||
flutter_to_debian: ^2.0.2
|
flutter_to_debian: ^2.0.2
|
||||||
|
archive: any
|
||||||
|
|
||||||
flutter:
|
flutter:
|
||||||
uses-material-design: true
|
uses-material-design: true
|
||||||
@@ -104,6 +105,7 @@ flutter:
|
|||||||
- assets/tags.json
|
- assets/tags.json
|
||||||
- assets/tags_tw.json
|
- assets/tags_tw.json
|
||||||
- assets/opencc.txt
|
- assets/opencc.txt
|
||||||
|
# fonts:
|
||||||
|
|
||||||
flutter_to_arch:
|
flutter_to_arch:
|
||||||
name: Venera
|
name: Venera
|
||||||
|
|||||||
115
test/channel_test.dart
Normal file
115
test/channel_test.dart
Normal file
@@ -0,0 +1,115 @@
|
|||||||
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
|
import 'package:venera/utils/channel.dart';
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
test("1-1-1", () async {
|
||||||
|
var channel = Channel<int>(1);
|
||||||
|
await channel.push(1);
|
||||||
|
var item = await channel.pop();
|
||||||
|
expect(item, 1);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("1-3-1", () async {
|
||||||
|
var channel = Channel<int>(1);
|
||||||
|
|
||||||
|
// producer
|
||||||
|
() async {
|
||||||
|
await channel.push(1);
|
||||||
|
}();
|
||||||
|
() async {
|
||||||
|
await channel.push(2);
|
||||||
|
}();
|
||||||
|
() async {
|
||||||
|
await channel.push(3);
|
||||||
|
}();
|
||||||
|
|
||||||
|
// consumer
|
||||||
|
var results = <int>[];
|
||||||
|
for (var i = 0; i < 3; i++) {
|
||||||
|
var item = await channel.pop();
|
||||||
|
if (item != null) {
|
||||||
|
results.add(item);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
expect(results.length, 3);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("2-3-1", () async {
|
||||||
|
var channel = Channel<int>(2);
|
||||||
|
|
||||||
|
// producer
|
||||||
|
() async {
|
||||||
|
await channel.push(1);
|
||||||
|
}();
|
||||||
|
() async {
|
||||||
|
await channel.push(2);
|
||||||
|
}();
|
||||||
|
() async {
|
||||||
|
await channel.push(3);
|
||||||
|
}();
|
||||||
|
|
||||||
|
// consumer
|
||||||
|
var results = <int>[];
|
||||||
|
for (var i = 0; i < 3; i++) {
|
||||||
|
var item = await channel.pop();
|
||||||
|
if (item != null) {
|
||||||
|
results.add(item);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
expect(results.length, 3);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("1-1-3", () async {
|
||||||
|
var channel = Channel<int>(1);
|
||||||
|
|
||||||
|
// producer
|
||||||
|
() async {
|
||||||
|
print("push 1");
|
||||||
|
await channel.push(1);
|
||||||
|
print("push 2");
|
||||||
|
await channel.push(2);
|
||||||
|
print("push 3");
|
||||||
|
await channel.push(3);
|
||||||
|
print("push done");
|
||||||
|
channel.close();
|
||||||
|
}();
|
||||||
|
|
||||||
|
// consumer
|
||||||
|
var consumers = <Future>[];
|
||||||
|
var results = <int>[];
|
||||||
|
for (var i = 0; i < 3; i++) {
|
||||||
|
consumers.add(() async {
|
||||||
|
while (true) {
|
||||||
|
var item = await channel.pop();
|
||||||
|
if (item == null) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
print("pop $item");
|
||||||
|
results.add(item);
|
||||||
|
}
|
||||||
|
}());
|
||||||
|
}
|
||||||
|
|
||||||
|
await Future.wait(consumers);
|
||||||
|
expect(results.length, 3);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("close", () async {
|
||||||
|
var channel = Channel<int>(2);
|
||||||
|
|
||||||
|
// producer
|
||||||
|
() async {
|
||||||
|
await channel.push(1);
|
||||||
|
await channel.push(2);
|
||||||
|
await channel.push(3);
|
||||||
|
channel.close();
|
||||||
|
}();
|
||||||
|
|
||||||
|
// consumer
|
||||||
|
await channel.pop();
|
||||||
|
await channel.pop();
|
||||||
|
await channel.pop();
|
||||||
|
var item4 = await channel.pop();
|
||||||
|
expect(item4, null);
|
||||||
|
});
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user