mirror of
https://github.com/kodjodevf/mangayomi.git
synced 2026-05-23 07:32:18 +00:00
Changes: - Add season-keyword regex (staffel, season, saison, temporada) and episode-keyword regex (folge, episode, ep.) to reliably extract the correct number regardless of name format - parseChapterNumber() now encodes season context into the sort key (season * 100000 + episode) so multi-season anime sort correctly across seasons without mixing episode numbers - Add parseEpisodeNumber() which strips season context and returns only the episode number within a season; use this for tracker updates (MAL/AniList/Kitsu) and AniSkip lookups, where the tracker entry is already season-specific - Switch updateTrackChapterRead and getAniSkipResults to parseEpisodeNumber to fix incorrect episode reporting for multi-season anime - Compile all RegExp objects as static finals instead of per-call instantiation - Refactor duplicated parse logic into a single private _parse() method with an applySeason flag
146 lines
4.9 KiB
Dart
146 lines
4.9 KiB
Dart
import 'dart:io';
|
|
import 'package:flutter/material.dart';
|
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
|
import 'package:isar_community/isar.dart';
|
|
import 'package:mangayomi/main.dart';
|
|
import 'package:mangayomi/models/chapter.dart';
|
|
import 'package:mangayomi/models/download.dart';
|
|
import 'package:mangayomi/models/manga.dart';
|
|
import 'package:mangayomi/models/track.dart';
|
|
import 'package:mangayomi/models/track_preference.dart';
|
|
import 'package:mangayomi/modules/manga/detail/providers/track_state_providers.dart';
|
|
import 'package:mangayomi/modules/manga/reader/providers/push_router.dart';
|
|
import 'package:mangayomi/utils/extensions/manga_extensions.dart';
|
|
import 'package:mangayomi/modules/more/settings/track/providers/track_providers.dart';
|
|
import 'package:mangayomi/providers/storage_provider.dart';
|
|
import 'package:mangayomi/services/download_manager/download_isolate_pool.dart';
|
|
import 'package:mangayomi/services/download_manager/m_downloader.dart';
|
|
import 'package:mangayomi/utils/chapter_recognition.dart';
|
|
import 'package:mangayomi/utils/extensions/string_extensions.dart';
|
|
import 'package:path/path.dart' as p;
|
|
|
|
extension ChapterExtension on Chapter {
|
|
Future<void> pushToReaderView(
|
|
BuildContext context, {
|
|
bool ignoreIsRead = false,
|
|
}) async {
|
|
if (ignoreIsRead || !isRead!) {
|
|
await pushMangaReaderView(context: context, chapter: this);
|
|
} else {
|
|
final filteredChaps = manga.value!.getChapterListForReading();
|
|
bool exist = false;
|
|
for (var filteredChap in filteredChaps) {
|
|
if (filteredChap.toJson().toString() == toJson().toString()) {
|
|
exist = true;
|
|
}
|
|
if (exist && !filteredChap.isRead!) {
|
|
await pushMangaReaderView(context: context, chapter: filteredChap);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void cancelDownloads(int? downloadId) {
|
|
// Cancel via the Isolate pool (new system)
|
|
DownloadIsolatePool.instance.cancelTask('$id');
|
|
DownloadIsolatePool.instance.cancelTask('m3u8_$id');
|
|
|
|
// Clean the map for compatibility
|
|
isolateChapsSendPorts.remove('$id');
|
|
|
|
isar.writeTxnSync(() {
|
|
isar.downloads.deleteSync(id!);
|
|
if (downloadId != null) {
|
|
isar.downloads.deleteSync(downloadId);
|
|
}
|
|
});
|
|
}
|
|
|
|
Future<void> deleteDownloadedFiles() async {
|
|
final download = isar.downloads.getSync(id!);
|
|
if (download == null) return;
|
|
|
|
final storageProvider = StorageProvider();
|
|
final mangaDir = await storageProvider.getMangaMainDirectory(this);
|
|
final chapterDir = await storageProvider.getMangaChapterDirectory(
|
|
this,
|
|
mangaMainDirectory: mangaDir,
|
|
);
|
|
|
|
try {
|
|
final cbzFile = File(p.join(mangaDir!.path, "$name.cbz"));
|
|
if (cbzFile.existsSync()) cbzFile.deleteSync();
|
|
} catch (_) {}
|
|
try {
|
|
final mp4File = File(
|
|
p.join(mangaDir!.path, "${name!.replaceForbiddenCharacters(' ')}.mp4"),
|
|
);
|
|
if (mp4File.existsSync()) mp4File.deleteSync();
|
|
} catch (_) {}
|
|
try {
|
|
final htmlFile = File(p.join(mangaDir!.path, "$name.html"));
|
|
if (htmlFile.existsSync()) htmlFile.deleteSync();
|
|
} catch (_) {}
|
|
try {
|
|
chapterDir?.deleteSync(recursive: true);
|
|
} catch (_) {}
|
|
|
|
cancelDownloads(download.id);
|
|
}
|
|
|
|
void updateTrackChapterRead(dynamic ref) {
|
|
if (!(ref is WidgetRef || ref is Ref)) return;
|
|
final updateProgressAfterReading = ref.read(
|
|
updateProgressAfterReadingStateProvider,
|
|
);
|
|
if (!updateProgressAfterReading) return;
|
|
final manga = this.manga.value!;
|
|
final chapterNumber = ChapterRecognition().parseEpisodeNumber(
|
|
manga.name!,
|
|
name!,
|
|
);
|
|
|
|
final tracks = isar.tracks
|
|
.filter()
|
|
.idIsNotNull()
|
|
.itemTypeEqualTo(manga.itemType)
|
|
.mangaIdEqualTo(manga.id!)
|
|
.findAllSync();
|
|
|
|
if (tracks.isEmpty) return;
|
|
for (var track in tracks) {
|
|
final service = isar.trackPreferences
|
|
.filter()
|
|
.syncIdIsNotNull()
|
|
.syncIdEqualTo(track.syncId)
|
|
.findFirstSync();
|
|
if (!(service == null || chapterNumber <= (track.lastChapterRead ?? 0))) {
|
|
if (track.status != TrackStatus.completed) {
|
|
track.lastChapterRead = chapterNumber;
|
|
if (track.lastChapterRead == track.totalChapter &&
|
|
(track.totalChapter ?? 0) > 0) {
|
|
track.status = TrackStatus.completed;
|
|
track.finishedReadingDate = DateTime.now().millisecondsSinceEpoch;
|
|
} else {
|
|
track.status = manga.itemType == ItemType.manga
|
|
? TrackStatus.reading
|
|
: TrackStatus.watching;
|
|
if (track.lastChapterRead == 1) {
|
|
track.startedReadingDate = DateTime.now().millisecondsSinceEpoch;
|
|
}
|
|
}
|
|
}
|
|
ref
|
|
.read(
|
|
trackStateProvider(
|
|
track: track,
|
|
itemType: manga.itemType,
|
|
widgetRef: ref,
|
|
).notifier,
|
|
)
|
|
.updateManga();
|
|
}
|
|
}
|
|
}
|
|
}
|