Skip to content

Commit

Permalink
feat: syncing books from otzaria-library GitHub
Browse files Browse the repository at this point in the history
refactor: the main function
  • Loading branch information
Sivan22 committed Nov 6, 2024
1 parent c18583a commit fa2aebc
Show file tree
Hide file tree
Showing 7 changed files with 346 additions and 68 deletions.
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ migrate_working_dir/
.pub-cache/
.pub/
/build/

/windows/
# Symbolication related
app.*.symbols

Expand Down
97 changes: 42 additions & 55 deletions lib/main.dart
Original file line number Diff line number Diff line change
@@ -1,70 +1,18 @@
import 'package:flutter/material.dart';
import 'package:flutter_localizations/flutter_localizations.dart';
import 'package:otzaria/models/app_model.dart';
import 'package:path_provider/path_provider.dart';
import 'package:provider/provider.dart';
import 'package:search_engine/search_engine.dart';
import 'screens/main_window_screen.dart';
import 'package:flutter_settings_screens/flutter_settings_screens.dart';
import 'package:otzaria/data/data_providers/cache_provider.dart';
import 'package:otzaria/data/data_providers/hive_data_provider.dart';
import 'package:file_picker/file_picker.dart';
import 'package:permission_handler/permission_handler.dart';
import 'dart:io';

/// The main entry point of the application.
///
/// This function is responsible for initializing the application and running
/// it. It performs the following steps:
/// 1. Requests external storage permission on Android.
/// 2. Initializes all the Hive components.
/// 3. Initializes the library path.
///
/// This function does not take any parameters and does not return any values.
///
void main() async {
void createDirectoryIfNotExists(String path) {
Directory directory = Directory(path);
if (!directory.existsSync()) {
directory.createSync(recursive: true);
print('Directory created: $path');
} else {
print('Directory already exists: $path');
}
}

await RustLib.init();
await Settings.init(cacheProvider: HiveCache());

await initHiveBoxes();
WidgetsFlutterBinding.ensureInitialized();
// requesting external storage permission on android
while (
Platform.isAndroid && !await Permission.manageExternalStorage.isGranted) {
Permission.manageExternalStorage.request();
}

// initializing the library path
await () async {
//first try to get the library path from settings
String? libraryPath = Settings.getValue('key-library-path');
//on windows, if the path is not set, defaults to C:/אוצריא
if (Platform.isWindows && libraryPath == null) {
libraryPath = 'C:/אוצריא';
Settings.setValue('key-library-path', libraryPath);
}
//if faild, ask the user to choose the path
while (libraryPath == null ||
(!Directory('$libraryPath${Platform.pathSeparator}אוצריא')
.existsSync())) {
libraryPath = await FilePicker.platform
.getDirectoryPath(dialogTitle: "הגדר את מיקום ספריית אוצריא");
Settings.setValue('key-library-path', libraryPath);
}
}();
createDirectoryIfNotExists(
'${Settings.getValue('key-library-path')}${Platform.pathSeparator}index');

await initialize();
runApp(const OtzariaApp());
}

Expand All @@ -74,7 +22,8 @@ class OtzariaApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return ChangeNotifierProvider(
create: (context) => AppModel(),
create: (context) =>
AppModel(libraryPath: Settings.getValue('key-library-path') ?? '.'),
builder: (context, child) {
return Consumer<AppModel>(
builder: (context, appModel, child) => MaterialApp(
Expand Down Expand Up @@ -110,6 +59,44 @@ class OtzariaApp extends StatelessWidget {
}
}

Future<void> initialize() async {
await Settings.init(cacheProvider: HiveCache());
await initLibraryPath();
await RustLib.init();
await initHiveBoxes();
await createDirs();
}

Future<void> createDirs() async {
createDirectoryIfNotExists(
'${Settings.getValue('key-library-path')}${Platform.pathSeparator}אוצריא');
createDirectoryIfNotExists(
'${Settings.getValue('key-library-path')}${Platform.pathSeparator}index');
}

Future<void> initLibraryPath() async {
if (Platform.isAndroid) {
await Settings.setValue(
'key-library-path', (await getApplicationDocumentsDirectory()).path);
return;
}
//first try to get the library path from settings
String? libraryPath = Settings.getValue('key-library-path');
//on windows, if the path is not set, defaults to C:/אוצריא
if (Platform.isWindows && libraryPath == null) {
libraryPath = 'C:/אוצריא';
Settings.setValue('key-library-path', libraryPath);
}
//if faild, ask the user to choose the path
if (libraryPath == null ||
(!Directory('$libraryPath${Platform.pathSeparator}אוצריא')
.existsSync())) {
libraryPath = await FilePicker.platform
.getDirectoryPath(dialogTitle: "הגדר את מיקום הספרייה");
Settings.setValue('key-library-path', libraryPath);
}
}

void createDirectoryIfNotExists(String path) {
Directory directory = Directory(path);
if (!directory.existsSync()) {
Expand Down
5 changes: 4 additions & 1 deletion lib/models/app_model.dart
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,9 @@ class AppModel with ChangeNotifier {
/// The data provider for the application.
DataRepository data = DataRepository.instance;

/// The path of the library.
String libraryPath;

/// The library of books.
late Future<Library> library;

Expand Down Expand Up @@ -96,7 +99,7 @@ class AppModel with ChangeNotifier {
///
/// This constructor initializes the library and tabs list, and loads the
/// tabs list and history from disk.
AppModel() {
AppModel({required this.libraryPath}) {
library = data.getLibrary();
otzarBooks = data.getOtzarBooks();
hebrewBooks = data.getHebrewBooks();
Expand Down
33 changes: 22 additions & 11 deletions lib/screens/library_browser.dart
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,9 @@ import 'package:otzaria/models/books.dart';
import 'package:otzaria/models/library.dart';
import 'package:otzaria/utils/daf_yomi_helper.dart';
import 'package:otzaria/utils/extraction.dart';
import 'package:otzaria/utils/file_sync_service.dart';
import 'package:otzaria/widgets/daf_yomi.dart';
import 'package:otzaria/widgets/file_sync_widget.dart';
import 'package:otzaria/widgets/filter_list/src/filter_list_dialog.dart';
import 'package:otzaria/widgets/filter_list/src/theme/filter_list_theme.dart';
import 'package:otzaria/widgets/grid_items.dart';
Expand Down Expand Up @@ -63,17 +65,26 @@ class _LibraryBrowserState extends State<LibraryBrowser>
children: [
Align(
alignment: Alignment.centerRight,
child: IconButton(
icon: const Icon(Icons.home),
tooltip: 'חזרה לתיקיה הראשית',
onPressed: () => setState(() {
searchController.clear();
depth = 0;
currentTopCategory =
Provider.of<AppModel>(context, listen: false)
.library;
items = getGrids(currentTopCategory);
}),
child: Row(
children: [
IconButton(
icon: const Icon(Icons.home),
tooltip: 'חזרה לתיקיה הראשית',
onPressed: () => setState(() {
searchController.clear();
depth = 0;
currentTopCategory =
Provider.of<AppModel>(context, listen: false)
.library;
items = getGrids(currentTopCategory);
}),
),
SyncIconButton(
fileSync: FileSyncService(
githubOwner: "Sivan22",
repositoryName: "otzaria-library",
branch: "main")),
],
),
),
Expanded(
Expand Down
8 changes: 8 additions & 0 deletions lib/screens/settings_screen.dart
Original file line number Diff line number Diff line change
Expand Up @@ -243,6 +243,14 @@ class _MySettingsScreenState extends State<MySettingsScreen> {
titleAlignment: Alignment.centerRight,
titleTextStyle: const TextStyle(fontSize: 25),
children: [
SwitchSettingsTile(
title: 'סינכרון אוטומטי',
leading: Icon(Icons.sync),
settingKey: 'key-auto-sync',
defaultValue: true,
enabledLabel: 'מאגר הספרים יתעדכן אוטומטית',
disabledLabel: 'מאגר הספרים לא יתעדכן אוטומטית.',
),
SimpleSettingsTile(
title: 'מיקום הספרייה',
subtitle: Settings.getValue<String>('key-library-path') ??
Expand Down
123 changes: 123 additions & 0 deletions lib/utils/file_sync_service.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
import 'dart:convert';
import 'dart:io';
import 'package:flutter_settings_screens/flutter_settings_screens.dart';
import 'package:http/http.dart' as http;

class FileSyncService {
final String githubOwner;
final String repositoryName;
final String branch;
bool isSyncing = false;

FileSyncService({
required this.githubOwner,
required this.repositoryName,
this.branch = 'main',
});

Future<String> get _localManifestPath async {
final directory = _localDirectory;
return '${await directory}${Platform.pathSeparator}files_manifest.json';
}

Future<String> get _localDirectory async {
return Settings.getValue('key-library-path') ?? 'C:/אוצריא';
}

Future<Map<String, dynamic>> _getLocalManifest() async {
try {
final file = File(await _localManifestPath);
if (!await file.exists()) {
return {};
}
final content = await file.readAsString();
return json.decode(content);
} catch (e) {
print('Error reading local manifest: $e');
return {};
}
}

Future<Map<String, dynamic>> _getRemoteManifest() async {
final url =
'https://raw.githubusercontent.com/$githubOwner/$repositoryName/$branch/files_manifest.json';
try {
final response = await http.get(Uri.parse(url));
if (response.statusCode == 200) {
return json.decode(response.body);
}
throw Exception('Failed to fetch remote manifest');
} catch (e) {
print('Error fetching remote manifest: $e');
rethrow;
}
}

Future<void> downloadFile(String filePath) async {
final url =
'https://raw.githubusercontent.com/$githubOwner/$repositoryName/$branch/$filePath';
try {
final response = await http.get(Uri.parse(url));
if (response.statusCode == 200) {
final directory = await _localDirectory;
final file = File('$directory/$filePath');

// Create directories if they don't exist
await file.parent.create(recursive: true);
await file.writeAsBytes(response.bodyBytes);
}
} catch (e) {
print('Error downloading file $filePath: $e');
}
}

Future<List<String>> checkForUpdates() async {
final localManifest = await _getLocalManifest();
final remoteManifest = await _getRemoteManifest();

final filesToUpdate = <String>[];

remoteManifest.forEach((filePath, remoteInfo) {
if (!localManifest.containsKey(filePath) ||
localManifest[filePath]['modified'] != remoteInfo['modified']) {
filesToUpdate.add(filePath);
}
});

return filesToUpdate;
}

Future<int> syncFiles() async {
if (isSyncing) {
return 0;
}
isSyncing = true;
int count = 0;
try {
final filesToUpdate = await checkForUpdates();

for (final filePath in filesToUpdate) {
if (isSyncing == false) {
return count;
}
await downloadFile(filePath);
count++;
}

// Update local manifest
final remoteManifest = await _getRemoteManifest();
final manifestFile = File(await _localManifestPath);
await manifestFile.writeAsString(json.encode(remoteManifest));
} catch (e) {
print('Error during sync: $e');
isSyncing = false;
rethrow;
}
isSyncing = false;
return count;
}

Future<void> stopSyncing() async {
isSyncing = false;
}
}
Loading

0 comments on commit fa2aebc

Please sign in to comment.