voyagehandbook/lib/util/storage.dart

135 lines
4.7 KiB
Dart

import 'dart:convert';
import 'dart:io';
import 'package:dio/dio.dart';
import 'package:html/parser.dart';
import 'package:path_provider/path_provider.dart';
import 'package:voyagehandbook/api/wikimedia.dart';
/*
Voyage Handbook - The open-source WikiVoyage reader
Copyright (C) 2023 Matyáš Caras
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License version 3 as published by
the Free Software Foundation.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
/// Used to ease up accessing local files
class StorageAccess {
/// Get files in `recent` folder, which contains recently opened pages
static Future<List<Map<String, dynamic>>> get recent async {
var files =
Directory("${(await getApplicationDocumentsDirectory()).path}/recent");
if (!files.existsSync()) files.createSync();
return files
.listSync()
.whereType<File>()
.toList()
.map<Map<String, dynamic>>((e) => jsonDecode(e.readAsStringSync()))
.toList();
}
/// Get files in `offline` folder, which contains recently opened pages
static Future<List<Map<String, dynamic>>> get offline async {
var files =
Directory("${(await getApplicationDocumentsDirectory()).path}/offline");
if (!files.existsSync()) files.createSync();
return files
.listSync()
.whereType<File>()
.toList()
.map<Map<String, dynamic>>((e) => jsonDecode(e.readAsStringSync()))
.toList();
}
static void addToRecents(String pageName, String pageKey) async {
var files =
Directory("${(await getApplicationDocumentsDirectory()).path}/recent");
if (!files.existsSync()) files.createSync();
var content = files.listSync();
if (content.length > 4) {
// delete last recent
// TODO: configurable
File? f;
for (var file in content) {
if (file is Directory) continue;
var modi = (await file.stat()).modified;
if (f == null || (await f.stat()).modified.isAfter(modi)) {
f = file as File;
}
}
f!.deleteSync();
}
var recent = File("${files.path}/${pageName.replaceAll(' ', '_')}");
if (recent.existsSync()) {
// if recent already exists, simply change date
var recentContent = jsonDecode(recent.readAsStringSync());
recentContent["date"] = DateTime.now().millisecondsSinceEpoch;
recent.writeAsStringSync(jsonEncode(recentContent));
} else {
// else create files
var recentContent = {
"date": DateTime.now().millisecondsSinceEpoch,
"name": pageName,
"key": pageKey
};
recent.writeAsStringSync(jsonEncode(recentContent));
}
}
static Future<bool> isDownloaded(String pageKey) async {
var files =
Directory("${(await getApplicationDocumentsDirectory()).path}/recent");
var offlinePage = File("${files.path}/$pageKey");
return offlinePage.existsSync();
}
static Future<void> downloadArticle(String pageKey, String pageTitle) async {
var files =
Directory("${(await getApplicationDocumentsDirectory()).path}/offline");
var offlinePage = File("${files.path}/$pageKey");
var page = parse(await WikiApi.getRawPage(pageKey))
.body!
.getElementsByTagName("section");
var out = "<html><head></head><body>";
for (var el in page) {
out += el.outerHtml;
var imgMatch = RegExp(r'<img src="(.+?)">').allMatches(el.innerHtml);
if (imgMatch.isNotEmpty) {
// download images offline
for (var match in imgMatch) {
var src = match.group(1)!;
var r = await Dio().get(
src,
options: Options(
responseType: ResponseType.bytes,
followRedirects: false,
validateStatus: (status) {
return (status ?? 200) < 500;
},
),
);
var img = File("${files.path}/${src.split('/').last}");
print(img.path);
var openImg = img.openSync(mode: FileMode.write);
openImg.writeFromSync(r.data);
}
}
}
if (page.isEmpty) return Future.error("No sections to save");
offlinePage.writeAsStringSync(
jsonEncode({"title": pageTitle, "key": pageKey, "content": out}),
mode: FileMode.writeOnly);
}
}