Skip to content

Commit

Permalink
feat: download library from the internet
Browse files Browse the repository at this point in the history
  • Loading branch information
Sivan22 committed Nov 10, 2024
1 parent eeda030 commit a23f05e
Show file tree
Hide file tree
Showing 7 changed files with 448 additions and 165 deletions.
11 changes: 6 additions & 5 deletions lib/data/data_providers/file_system_data_provider.dart
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@ import 'package:otzaria/models/links.dart';
/// which every book is stored in a file, and every directory is represents a category.
/// The metadata is stored in a JSON file.
class FileSystemData {
late String libraryPath;
Map<String, String> titleToPath = {};
Map<String, dynamic> metadata = {};

Expand All @@ -38,14 +37,14 @@ class FileSystemData {
if (!Settings.isInitialized) {
await Settings.init(cacheProvider: HiveCache());
}
libraryPath = Settings.getValue<String>('key-library-path') ?? '.';

_updateTitleToPath();
}

/// Returns the library
Future<Library> getLibrary() async {
return _getLibraryFromDirectory(
'$libraryPath${Platform.pathSeparator}אוצריא');
'${Settings.getValue<String>('key-library-path') ?? '.'}${Platform.pathSeparator}אוצריא');
}

Future<Library> _getLibraryFromDirectory(String path) async {
Expand Down Expand Up @@ -283,7 +282,8 @@ class FileSystemData {

/// Updates the title to path mapping using the provided library path.
void _updateTitleToPath() {
List<String> paths = getAllBooksPathsFromDirecctory('$libraryPath/אוצריא');
List<String> paths =
getAllBooksPathsFromDirecctory(Settings.getValue('key-library-path'));
for (var path in paths) {
titleToPath[getTitleFromPath(path)] = path;
}
Expand All @@ -293,7 +293,8 @@ class FileSystemData {
void _fetchMetadata() {
String metadataString = '';
try {
File file = File('$libraryPath${Platform.pathSeparator}metadata.json');
File file = File(
'${Settings.getValue<String>('key-library-path') ?? '.'}${Platform.pathSeparator}metadata.json');
metadataString = file.readAsStringSync();
} catch (e) {
return;
Expand Down
9 changes: 0 additions & 9 deletions lib/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ 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 'dart:io';

void main() async {
Expand Down Expand Up @@ -88,14 +87,6 @@ Future<void> initLibraryPath() async {
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) {
Expand Down
4 changes: 1 addition & 3 deletions lib/models/app_model.dart
Original file line number Diff line number Diff line change
Expand Up @@ -478,9 +478,7 @@ class AppModel with ChangeNotifier {
}

Future<void> refreshLibrary() async {
if (Settings.getValue('key-library-path') != null) {
_libraryPath = Settings.getValue('key-library-path');
}
libraryPath = Settings.getValue<String>('key-library-path') ?? libraryPath;
library = data.getLibrary();
notifyListeners();
}
Expand Down
255 changes: 255 additions & 0 deletions lib/screens/empty_library_screen.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,255 @@
import 'dart:io';
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter_settings_screens/flutter_settings_screens.dart';
import 'package:path_provider/path_provider.dart';
import 'package:http/http.dart' as http;
import 'package:archive/archive.dart';
import 'package:archive/archive_io.dart';
import 'package:file_picker/file_picker.dart';

class EmptyLibraryScreen extends StatefulWidget {
final VoidCallback onLibraryLoaded;

const EmptyLibraryScreen({Key? key, required this.onLibraryLoaded})
: super(key: key);

@override
State<EmptyLibraryScreen> createState() => _EmptyLibraryScreenState();
}

class _EmptyLibraryScreenState extends State<EmptyLibraryScreen> {
bool _isDownloading = false;
double _downloadProgress = 0;
String? _selectedPath;
double _downloadedMB = 0;
double _downloadSpeed = 0;
String _currentOperation = '';
Timer? _speedTimer;
double _lastDownloadedBytes = 0;

@override
void dispose() {
_speedTimer?.cancel();
super.dispose();
}

Future<void> _downloadAndExtractLibrary() async {
if (_isDownloading) return;

setState(() {
_isDownloading = true;
_downloadProgress = 0;
_downloadedMB = 0;
_downloadSpeed = 0;
_currentOperation = 'מתחיל הורדה...';
});

final libraryPath = Settings.getValue<String>('key-library-path') ?? '';
if (libraryPath.isEmpty) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('נא לבחור תיקייה תחילה')),
);
setState(() => _isDownloading = false);
return;
}

final tempDir = await getTemporaryDirectory();
final tempFile = File('${tempDir.path}/temp_library.zip');

try {
// Start speed calculation timer
_lastDownloadedBytes = 0;
_speedTimer = Timer.periodic(const Duration(seconds: 1), (timer) {
if (!mounted) return;
final bytesPerSecond =
(_downloadedMB - _lastDownloadedBytes) * 1024 * 1024;
setState(() {
_downloadSpeed = bytesPerSecond / (1024 * 1024); // Convert to MB/s
_lastDownloadedBytes = _downloadedMB;
});
});

// Initialize the download
final request = http.Request(
'GET',
Uri.parse(
'https://github.com/Sivan22/otzaria-library/releases/download/latest/otzaria_latest.zip'),
);
final response = await http.Client().send(request);

if (response.statusCode != 200) {
throw Exception('Failed to start download: ${response.statusCode}');
}

final contentLength = response.contentLength ?? 0;
var receivedBytes = 0;

// Create file and prepare for writing
final sink = tempFile.openWrite();
final stream = response.stream;

// Download with progress
await for (final chunk in stream) {
if (!mounted) break;

sink.add(chunk);
receivedBytes += chunk.length;
final downloadedMB = receivedBytes / (1024 * 1024);

setState(() {
_downloadedMB = downloadedMB;
_downloadProgress =
contentLength > 0 ? receivedBytes / contentLength : 0;
_currentOperation = 'מוריד: ${downloadedMB.toStringAsFixed(2)} MB';
});
}

await sink.flush();
await sink.close();

// Start extraction
setState(() {
_currentOperation = 'מחלץ קבצים...';
_downloadProgress = 0;
});

// Read the zip file
final bytes = await tempFile.readAsBytes();
final archive = ZipDecoder().decodeBytes(bytes);
final totalFiles = archive.files.length;

// Extract files
for (var i = 0; i < archive.files.length; i++) {
if (!mounted) break;

final file = archive.files[i];
final filename = file.name;
final filePath = '$libraryPath/$filename';

setState(() {
_downloadProgress = i / totalFiles;
_currentOperation = 'מחלץ: $filename';
});

if (file.isFile) {
final outputFile = File(filePath);
await outputFile.parent.create(recursive: true);
await outputFile.writeAsBytes(file.content as List<int>);
} else {
await Directory(filePath).create(recursive: true);
}
}

// Cleanup
await tempFile.delete();

if (mounted) {
widget.onLibraryLoaded();
setState(() {
_isDownloading = false;
_currentOperation = '';
});
}
} catch (e) {
if (mounted) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('שגיאה: $e')),
);
setState(() {
_isDownloading = false;
_currentOperation = '';
});
}
} finally {
_speedTimer?.cancel();
if (tempFile.existsSync()) {
await tempFile.delete();
}
}
}

Future<void> _pickDirectory() async {
String? selectedDirectory = await FilePicker.platform.getDirectoryPath();

if (selectedDirectory == null || !mounted) {
return;
}

setState(() {
_selectedPath = selectedDirectory;
});

final directory = Directory(selectedDirectory);
if (!directory.existsSync()) {
if (mounted) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('נתיב לא חוקי')),
);
}
return;
}

Settings.setValue('key-library-path', selectedDirectory);
widget.onLibraryLoaded();
}

@override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Container(
constraints: const BoxConstraints(maxWidth: 400),
padding: const EdgeInsets.all(16),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Text(
'לא נמצאה ספרייה בנתיב המצוין',
style: TextStyle(fontSize: 24, fontWeight: FontWeight.bold),
textAlign: TextAlign.center,
),
const SizedBox(height: 32),
if (_selectedPath != null)
Padding(
padding: const EdgeInsets.only(bottom: 16),
child: Text(
_selectedPath!,
style: const TextStyle(fontSize: 16),
textDirection: TextDirection.ltr,
textAlign: TextAlign.center,
),
),
ElevatedButton(
onPressed: _isDownloading ? null : _pickDirectory,
child: const Text('בחר תיקייה'),
),
const SizedBox(height: 32),
const Text(
'או',
style: TextStyle(fontSize: 18),
),
const SizedBox(height: 32),
if (_isDownloading)
Column(
children: [
LinearProgressIndicator(value: _downloadProgress),
const SizedBox(height: 16),
Text(_currentOperation),
if (_downloadSpeed > 0)
Text(
'מהירות הורדה: ${_downloadSpeed.toStringAsFixed(2)} MB/s'),
],
)
else
ElevatedButton(
onPressed: _downloadAndExtractLibrary,
child: const Text('הורד את הספרייה מהאינטרנט'),
),
],
),
),
),
);
}
}
Loading

0 comments on commit a23f05e

Please sign in to comment.