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 . */ /// Used to ease up accessing local files class StorageAccess { /// Get files in `recent` folder, which contains recently opened pages static Future>> get recent async { var files = Directory("${(await getApplicationDocumentsDirectory()).path}/recent"); if (!files.existsSync()) files.createSync(); return files .listSync() .whereType() .toList() .map>((e) => jsonDecode(e.readAsStringSync())) .toList(); } /// Get files in `offline` folder, which contains recently opened pages static Future>> get offline async { var files = Directory("${(await getApplicationDocumentsDirectory()).path}/offline"); if (!files.existsSync()) files.createSync(); return files .listSync() .whereType() .toList() .map>((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 isDownloaded(String pageKey) async { var files = Directory("${(await getApplicationDocumentsDirectory()).path}/recent"); var offlinePage = File("${files.path}/$pageKey"); return offlinePage.existsSync(); } static Future 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 = ""; for (var el in page) { out += el.outerHtml; var imgMatch = RegExp(r'').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); } }