127 lines
3.7 KiB
Dart
127 lines
3.7 KiB
Dart
import 'package:sqflite/sqflite.dart';
|
|
import 'package:path/path.dart' as p;
|
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
|
|
|
import '../models/chapter_model.dart';
|
|
|
|
class OfflineCache {
|
|
static const _dbName = 'reader_offline.db';
|
|
static const _version = 1;
|
|
|
|
Database? _db;
|
|
|
|
Future<Database> get db async {
|
|
_db ??= await _open();
|
|
return _db!;
|
|
}
|
|
|
|
Future<Database> _open() async {
|
|
final dir = await getDatabasesPath();
|
|
final path = p.join(dir, _dbName);
|
|
return openDatabase(
|
|
path,
|
|
version: _version,
|
|
onCreate: (db, _) async {
|
|
await db.execute('''
|
|
CREATE TABLE cached_chapters (
|
|
id TEXT PRIMARY KEY,
|
|
novel_id TEXT NOT NULL,
|
|
chapter_number INTEGER NOT NULL,
|
|
title TEXT,
|
|
content TEXT NOT NULL,
|
|
prev_chapter_id TEXT,
|
|
prev_chapter_number INTEGER,
|
|
next_chapter_id TEXT,
|
|
next_chapter_number INTEGER,
|
|
volume_title TEXT,
|
|
cached_at INTEGER NOT NULL
|
|
)
|
|
''');
|
|
await db.execute('''
|
|
CREATE INDEX idx_novel_chapters ON cached_chapters(novel_id, chapter_number)
|
|
''');
|
|
},
|
|
);
|
|
}
|
|
|
|
Future<void> saveChapter(ChapterModel chapter) async {
|
|
final database = await db;
|
|
await database.insert(
|
|
'cached_chapters',
|
|
{
|
|
'id': chapter.id,
|
|
'novel_id': chapter.novelId,
|
|
'chapter_number': chapter.number,
|
|
'title': chapter.title,
|
|
'content': chapter.content,
|
|
'prev_chapter_id': chapter.prevChapterId,
|
|
'prev_chapter_number': chapter.prevChapterNumber,
|
|
'next_chapter_id': chapter.nextChapterId,
|
|
'next_chapter_number': chapter.nextChapterNumber,
|
|
'volume_title': chapter.volumeTitle,
|
|
'cached_at': DateTime.now().millisecondsSinceEpoch,
|
|
},
|
|
conflictAlgorithm: ConflictAlgorithm.replace,
|
|
);
|
|
}
|
|
|
|
Future<ChapterModel?> loadChapter(String chapterId) async {
|
|
final database = await db;
|
|
final rows = await database.query(
|
|
'cached_chapters',
|
|
where: 'id = ?',
|
|
whereArgs: [chapterId],
|
|
limit: 1,
|
|
);
|
|
if (rows.isEmpty) return null;
|
|
return _rowToChapter(rows.first);
|
|
}
|
|
|
|
Future<List<String>> cachedChapterIdsForNovel(String novelId) async {
|
|
final database = await db;
|
|
final rows = await database.query(
|
|
'cached_chapters',
|
|
columns: ['id'],
|
|
where: 'novel_id = ?',
|
|
whereArgs: [novelId],
|
|
orderBy: 'chapter_number ASC',
|
|
);
|
|
return rows.map((r) => r['id'] as String).toList();
|
|
}
|
|
|
|
Future<void> deleteNovelCache(String novelId) async {
|
|
final database = await db;
|
|
await database.delete(
|
|
'cached_chapters',
|
|
where: 'novel_id = ?',
|
|
whereArgs: [novelId],
|
|
);
|
|
}
|
|
|
|
Future<int> getCacheSizeBytes() async {
|
|
final database = await db;
|
|
final result = await database.rawQuery(
|
|
'SELECT SUM(LENGTH(content)) as total FROM cached_chapters',
|
|
);
|
|
return (result.first['total'] as int?) ?? 0;
|
|
}
|
|
|
|
ChapterModel _rowToChapter(Map<String, dynamic> row) {
|
|
return ChapterModel(
|
|
id: row['id'] as String,
|
|
novelId: row['novel_id'] as String,
|
|
number: row['chapter_number'] as int,
|
|
title: (row['title'] as String?) ?? '',
|
|
content: row['content'] as String,
|
|
prevChapterId: row['prev_chapter_id'] as String?,
|
|
prevChapterNumber: row['prev_chapter_number'] as int?,
|
|
nextChapterId: row['next_chapter_id'] as String?,
|
|
nextChapterNumber: row['next_chapter_number'] as int?,
|
|
volumeTitle: row['volume_title'] as String?,
|
|
createdAt: DateTime.fromMillisecondsSinceEpoch(row['cached_at'] as int),
|
|
);
|
|
}
|
|
}
|
|
|
|
final offlineCacheProvider = Provider<OfflineCache>((_) => OfflineCache());
|