This commit is contained in:
@@ -796,5 +796,12 @@
|
||||
"Are you sure to delete {{count}} selected file(s)?_many": "هل أنت متأكد من حذف {{count}} ملفًا محددًا؟",
|
||||
"Are you sure to delete {{count}} selected file(s)?_other": "هل أنت متأكد من حذف {{count}} ملف محدد؟",
|
||||
"Cloud Storage Usage": "استخدام التخزين السحابي",
|
||||
"Rename Group": "إعادة تسمية المجموعة"
|
||||
"Rename Group": "إعادة تسمية المجموعة",
|
||||
"From Directory": "من الدليل",
|
||||
"Successfully imported {{count}} book(s)_zero": "لم يتم استيراد أي كتب",
|
||||
"Successfully imported {{count}} book(s)_one": "تم استيراد كتاب واحد",
|
||||
"Successfully imported {{count}} book(s)_two": "تم استيراد كتابين",
|
||||
"Successfully imported {{count}} book(s)_few": "تم استيراد {{count}} كتب",
|
||||
"Successfully imported {{count}} book(s)_many": "تم استيراد {{count}} كتابًا",
|
||||
"Successfully imported {{count}} book(s)_other": "تم استيراد {{count}} كتاب"
|
||||
}
|
||||
|
||||
@@ -760,5 +760,8 @@
|
||||
"Are you sure to delete {{count}} selected file(s)?_one": "আপনি কি নিশ্চিত যে {{count}}টি নির্বাচিত ফাইল মুছতে চান?",
|
||||
"Are you sure to delete {{count}} selected file(s)?_other": "আপনি কি নিশ্চিত যে {{count}}টি নির্বাচিত ফাইল মুছতে চান?",
|
||||
"Cloud Storage Usage": "ক্লাউড স্টোরেজ ব্যবহৃত",
|
||||
"Rename Group": "গ্রুপের নাম পরিবর্তন করুন"
|
||||
"Rename Group": "গ্রুপের নাম পরিবর্তন করুন",
|
||||
"From Directory": "ডিরেক্টরি থেকে",
|
||||
"Successfully imported {{count}} book(s)_one": "সফলভাবে ১টি বই আমদানি করা হয়েছে",
|
||||
"Successfully imported {{count}} book(s)_other": "সফলভাবে {{count}}টি বই আমদানি করা হয়েছে"
|
||||
}
|
||||
|
||||
@@ -751,5 +751,7 @@
|
||||
"Page {{current}} of {{total}}": "ཤོག་ངོས་ {{total}} ནས་ {{current}}",
|
||||
"Are you sure to delete {{count}} selected file(s)?_other": "ཁྱེད་ཀྱིས་འདེམས་པའི་ཡིག་ཆ་ {{count}} བསུབས་དགོས་པ་ངེས་ཡིན་ན?",
|
||||
"Cloud Storage Usage": "སྤྲིན་གནས་སྣོད་གསོག་ལུས་སྐོར།",
|
||||
"Rename Group": "ཚོགས་མིང་བསྒྱུར་བ།"
|
||||
"Rename Group": "ཚོགས་མིང་བསྒྱུར་བ།",
|
||||
"From Directory": "སྐོར་འདེམས་པ་ནས།",
|
||||
"Successfully imported {{count}} book(s)_other": "སྤྲིན་ནས་ཕབ་སྟེ་འབེབས་ {{count}} དེབ་འདེམས་སོང་"
|
||||
}
|
||||
|
||||
@@ -760,5 +760,8 @@
|
||||
"Are you sure to delete {{count}} selected file(s)?_one": "Möchten Sie {{count}} ausgewählte Datei wirklich löschen?",
|
||||
"Are you sure to delete {{count}} selected file(s)?_other": "Möchten Sie {{count}} ausgewählte Dateien wirklich löschen?",
|
||||
"Cloud Storage Usage": "Cloud-Speichernutzung",
|
||||
"Rename Group": "Gruppe umbenennen"
|
||||
"Rename Group": "Gruppe umbenennen",
|
||||
"From Directory": "Aus Verzeichnis",
|
||||
"Successfully imported {{count}} book(s)_one": "Erfolgreich 1 Buch importiert",
|
||||
"Successfully imported {{count}} book(s)_other": "Erfolgreich {{count}} Bücher importiert"
|
||||
}
|
||||
|
||||
@@ -760,5 +760,8 @@
|
||||
"Are you sure to delete {{count}} selected file(s)?_one": "Σίγουρα θέλετε να διαγράψετε {{count}} επιλεγμένο αρχείο;",
|
||||
"Are you sure to delete {{count}} selected file(s)?_other": "Σίγουρα θέλετε να διαγράψετε {{count}} επιλεγμένα αρχεία;",
|
||||
"Cloud Storage Usage": "Χρήση αποθήκευσης cloud",
|
||||
"Rename Group": "Μετονομασία ομάδας"
|
||||
"Rename Group": "Μετονομασία ομάδας",
|
||||
"From Directory": "Από κατάλογο",
|
||||
"Successfully imported {{count}} book(s)_one": "Επιτυχής εισαγωγή 1 βιβλίου",
|
||||
"Successfully imported {{count}} book(s)_other": "Επιτυχής εισαγωγή {{count}} βιβλίων"
|
||||
}
|
||||
|
||||
@@ -17,5 +17,7 @@
|
||||
"{{count}} selected_one": "{{count}} selected",
|
||||
"{{count}} selected_other": "{{count}} selected",
|
||||
"Are you sure to delete {{count}} selected file(s)?_one": "Are you sure to delete {{count}} selected file?",
|
||||
"Are you sure to delete {{count}} selected file(s)?_other": "Are you sure to delete {{count}} selected files?"
|
||||
"Are you sure to delete {{count}} selected file(s)?_other": "Are you sure to delete {{count}} selected files?",
|
||||
"Successfully imported {{count}} book(s)_one": "Successfully imported {{count}} book",
|
||||
"Successfully imported {{count}} book(s)_other": "Successfully imported {{count}} books"
|
||||
}
|
||||
|
||||
@@ -769,5 +769,9 @@
|
||||
"Are you sure to delete {{count}} selected file(s)?_many": "¿Seguro que deseas eliminar {{count}} archivos seleccionados?",
|
||||
"Are you sure to delete {{count}} selected file(s)?_other": "¿Seguro que deseas eliminar {{count}} archivos seleccionados?",
|
||||
"Cloud Storage Usage": "Uso de almacenamiento en la nube",
|
||||
"Rename Group": "Renombrar grupo"
|
||||
"Rename Group": "Renombrar grupo",
|
||||
"From Directory": "Desde el directorio",
|
||||
"Successfully imported {{count}} book(s)_one": "Se importó correctamente 1 libro",
|
||||
"Successfully imported {{count}} book(s)_many": "Se importaron correctamente {{count}} libros",
|
||||
"Successfully imported {{count}} book(s)_other": "Se importaron correctamente {{count}} libros"
|
||||
}
|
||||
|
||||
@@ -760,5 +760,8 @@
|
||||
"Are you sure to delete {{count}} selected file(s)?_one": "آیا از حذف {{count}} فایل انتخابشده مطمئن هستید؟",
|
||||
"Are you sure to delete {{count}} selected file(s)?_other": "آیا از حذف {{count}} فایل انتخابشده مطمئن هستید؟",
|
||||
"Cloud Storage Usage": "استفاده از فضای ذخیرهسازی ابری",
|
||||
"Rename Group": "تغییر نام گروه"
|
||||
"Rename Group": "تغییر نام گروه",
|
||||
"From Directory": "از مسیر",
|
||||
"Successfully imported {{count}} book(s)_one": "با موفقیت 1 کتاب وارد شد",
|
||||
"Successfully imported {{count}} book(s)_other": "با موفقیت {{count}} کتاب وارد شد"
|
||||
}
|
||||
|
||||
@@ -769,5 +769,9 @@
|
||||
"Are you sure to delete {{count}} selected file(s)?_many": "Voulez-vous vraiment supprimer {{count}} fichiers sélectionnés ?",
|
||||
"Are you sure to delete {{count}} selected file(s)?_other": "Voulez-vous vraiment supprimer {{count}} fichiers sélectionnés ?",
|
||||
"Cloud Storage Usage": "Utilisation du stockage cloud",
|
||||
"Rename Group": "Renommer le groupe"
|
||||
"Rename Group": "Renommer le groupe",
|
||||
"From Directory": "Depuis le répertoire",
|
||||
"Successfully imported {{count}} book(s)_one": "Importation réussie de 1 livre",
|
||||
"Successfully imported {{count}} book(s)_many": "Importation réussie de {{count}} livres",
|
||||
"Successfully imported {{count}} book(s)_other": "Importation réussie de {{count}} livres"
|
||||
}
|
||||
|
||||
@@ -760,5 +760,8 @@
|
||||
"Are you sure to delete {{count}} selected file(s)?_one": "क्या आप वाकई {{count}} चयनित फ़ाइल हटाना चाहते हैं?",
|
||||
"Are you sure to delete {{count}} selected file(s)?_other": "क्या आप वाकई {{count}} चयनित फ़ाइलें हटाना चाहते हैं?",
|
||||
"Cloud Storage Usage": "क्लाउड स्टोरेज उपयोग",
|
||||
"Rename Group": "समूह का नाम बदलें"
|
||||
"Rename Group": "समूह का नाम बदलें",
|
||||
"From Directory": "निर्देशिका से",
|
||||
"Successfully imported {{count}} book(s)_one": "सफलतापूर्वक 1 पुस्तक आयात की गई",
|
||||
"Successfully imported {{count}} book(s)_other": "सफलतापूर्वक {{count}} पुस्तकों का आयात किया गया"
|
||||
}
|
||||
|
||||
@@ -751,5 +751,7 @@
|
||||
"Page {{current}} of {{total}}": "Halaman {{current}} dari {{total}}",
|
||||
"Are you sure to delete {{count}} selected file(s)?_other": "Yakin ingin menghapus {{count}} file yang dipilih?",
|
||||
"Cloud Storage Usage": "Penggunaan Penyimpanan Cloud",
|
||||
"Rename Group": "Ganti Nama Grup"
|
||||
"Rename Group": "Ganti Nama Grup",
|
||||
"From Directory": "Dari Direktori",
|
||||
"Successfully imported {{count}} book(s)_other": "Berhasil mengimpor {{count}} buku"
|
||||
}
|
||||
|
||||
@@ -769,5 +769,9 @@
|
||||
"Are you sure to delete {{count}} selected file(s)?_many": "Sei sicuro di voler eliminare {{count}} file selezionati?",
|
||||
"Are you sure to delete {{count}} selected file(s)?_other": "Sei sicuro di voler eliminare {{count}} file selezionati?",
|
||||
"Cloud Storage Usage": "Utilizzo archiviazione cloud",
|
||||
"Rename Group": "Rinomina gruppo"
|
||||
"Rename Group": "Rinomina gruppo",
|
||||
"From Directory": "Da directory",
|
||||
"Successfully imported {{count}} book(s)_one": "Importato con successo 1 libro",
|
||||
"Successfully imported {{count}} book(s)_many": "Importati con successo {{count}} libri",
|
||||
"Successfully imported {{count}} book(s)_other": "Importati con successo {{count}} libri"
|
||||
}
|
||||
|
||||
@@ -751,5 +751,7 @@
|
||||
"Page {{current}} of {{total}}": "ページ {{current}} / {{total}}",
|
||||
"Are you sure to delete {{count}} selected file(s)?_other": "選択した {{count}} 件のファイルを削除してもよろしいですか?",
|
||||
"Cloud Storage Usage": "クラウドストレージ使用量",
|
||||
"Rename Group": "グループ名を変更"
|
||||
"Rename Group": "グループ名を変更",
|
||||
"From Directory": "ディレクトリから",
|
||||
"Successfully imported {{count}} book(s)_other": "成功裏に{{count}}冊の本をインポートしました"
|
||||
}
|
||||
|
||||
@@ -751,5 +751,7 @@
|
||||
"Page {{current}} of {{total}}": "페이지 {{current}} / {{total}}",
|
||||
"Are you sure to delete {{count}} selected file(s)?_other": "선택한 {{count}}개의 파일을 삭제하시겠습니까?",
|
||||
"Cloud Storage Usage": "클라우드 저장소 사용량",
|
||||
"Rename Group": "그룹 이름 바꾸기"
|
||||
"Rename Group": "그룹 이름 바꾸기",
|
||||
"From Directory": "디렉토리에서",
|
||||
"Successfully imported {{count}} book(s)_other": "성공적으로 {{count}} 권의 책이 가져와졌습니다"
|
||||
}
|
||||
|
||||
@@ -751,5 +751,7 @@
|
||||
"Page {{current}} of {{total}}": "Halaman {{current}} daripada {{total}}",
|
||||
"Are you sure to delete {{count}} selected file(s)?_other": "Adakah anda pasti mahu memadam {{count}} fail yang dipilih?",
|
||||
"Cloud Storage Usage": "Penggunaan Storan Awan",
|
||||
"Rename Group": "Namakan Semula Kumpulan"
|
||||
"Rename Group": "Namakan Semula Kumpulan",
|
||||
"From Directory": "Dari Direktori",
|
||||
"Successfully imported {{count}} book(s)_other": "Berjaya mengimport {{count}} buku"
|
||||
}
|
||||
|
||||
@@ -760,5 +760,8 @@
|
||||
"Are you sure to delete {{count}} selected file(s)?_one": "Weet je zeker dat je {{count}} geselecteerd bestand wilt verwijderen?",
|
||||
"Are you sure to delete {{count}} selected file(s)?_other": "Weet je zeker dat je {{count}} geselecteerde bestanden wilt verwijderen?",
|
||||
"Cloud Storage Usage": "Cloudopslaggebruik",
|
||||
"Rename Group": "Groep hernoemen"
|
||||
"Rename Group": "Groep hernoemen",
|
||||
"From Directory": "Vanuit map",
|
||||
"Successfully imported {{count}} book(s)_one": "Succesvol 1 boek geïmporteerd",
|
||||
"Successfully imported {{count}} book(s)_other": "Succesvol {{count}} boeken geïmporteerd"
|
||||
}
|
||||
|
||||
@@ -778,5 +778,10 @@
|
||||
"Are you sure to delete {{count}} selected file(s)?_many": "Czy na pewno chcesz usunąć {{count}} wybranych plików?",
|
||||
"Are you sure to delete {{count}} selected file(s)?_other": "Czy na pewno chcesz usunąć {{count}} wybrany plik?",
|
||||
"Cloud Storage Usage": "Użycie pamięci w chmurze",
|
||||
"Rename Group": "Zmień nazwę grupy"
|
||||
"Rename Group": "Zmień nazwę grupy",
|
||||
"From Directory": "Z katalogu",
|
||||
"Successfully imported {{count}} book(s)_one": "Pomyślnie zaimportowano 1 książkę",
|
||||
"Successfully imported {{count}} book(s)_few": "Pomyślnie zaimportowano {{count}} książki",
|
||||
"Successfully imported {{count}} book(s)_many": "Pomyślnie zaimportowano {{count}} książek",
|
||||
"Successfully imported {{count}} book(s)_other": "Pomyślnie zaimportowano {{count}} książek"
|
||||
}
|
||||
|
||||
@@ -769,5 +769,9 @@
|
||||
"Are you sure to delete {{count}} selected file(s)?_many": "Tem certeza de que deseja apagar {{count}} ficheiros selecionados?",
|
||||
"Are you sure to delete {{count}} selected file(s)?_other": "Tem certeza de que deseja apagar {{count}} ficheiro selecionado?",
|
||||
"Cloud Storage Usage": "Uso de Armazenamento na Nuvem",
|
||||
"Rename Group": "Renomear Grupo"
|
||||
"Rename Group": "Renomear Grupo",
|
||||
"From Directory": "Do Diretório",
|
||||
"Successfully imported {{count}} book(s)_one": "Importado com sucesso 1 livro",
|
||||
"Successfully imported {{count}} book(s)_many": "Importados com sucesso {{count}} livros",
|
||||
"Successfully imported {{count}} book(s)_other": "Importados com sucesso {{count}} livros"
|
||||
}
|
||||
|
||||
@@ -778,5 +778,10 @@
|
||||
"Are you sure to delete {{count}} selected file(s)?_many": "Вы уверены, что хотите удалить {{count}} выбранных файлов?",
|
||||
"Are you sure to delete {{count}} selected file(s)?_other": "Вы уверены, что хотите удалить {{count}} выбранный файл?",
|
||||
"Cloud Storage Usage": "Использование облачного хранилища",
|
||||
"Rename Group": "Переименовать группу"
|
||||
"Rename Group": "Переименовать группу",
|
||||
"From Directory": "Из каталога",
|
||||
"Successfully imported {{count}} book(s)_one": "Успешно импортирован 1 книга",
|
||||
"Successfully imported {{count}} book(s)_few": "Успешно импортировано {{count}} книги",
|
||||
"Successfully imported {{count}} book(s)_many": "Успешно импортировано {{count}} книг",
|
||||
"Successfully imported {{count}} book(s)_other": "Успешно импортировано {{count}} книг"
|
||||
}
|
||||
|
||||
@@ -760,5 +760,8 @@
|
||||
"Are you sure to delete {{count}} selected file(s)?_one": "තෝරාගත් ගොනුව මකන්නට විශ්වාසද?",
|
||||
"Are you sure to delete {{count}} selected file(s)?_other": "තෝරාගත් ගොනු මකන්නට විශ්වාසද?",
|
||||
"Cloud Storage Usage": "කලාප ගබඩා භාවිතය",
|
||||
"Rename Group": "කණ්ඩායම නැවත නම් කරන්න"
|
||||
"Rename Group": "කණ්ඩායම නැවත නම් කරන්න",
|
||||
"From Directory": "ෆෝල්ඩරයෙන්",
|
||||
"Successfully imported {{count}} book(s)_one": "සාර්ථකව ආයාත කළ 1 පොත",
|
||||
"Successfully imported {{count}} book(s)_other": "සාර්ථකව ආයාත කළ {{count}} පොත්"
|
||||
}
|
||||
|
||||
@@ -760,5 +760,8 @@
|
||||
"Are you sure to delete {{count}} selected file(s)?_one": "Är du säker på att du vill radera filen?",
|
||||
"Are you sure to delete {{count}} selected file(s)?_other": "Är du säker på att du vill radera filerna?",
|
||||
"Cloud Storage Usage": "Användning av molnlagring",
|
||||
"Rename Group": "Byt namn på grupp"
|
||||
"Rename Group": "Byt namn på grupp",
|
||||
"From Directory": "Från katalog",
|
||||
"Successfully imported {{count}} book(s)_one": "Importerat 1 bok",
|
||||
"Successfully imported {{count}} book(s)_other": "Importerat {{count}} böcker"
|
||||
}
|
||||
|
||||
@@ -760,5 +760,8 @@
|
||||
"Are you sure to delete {{count}} selected file(s)?_one": "இந்த கோப்பை நீக்க விரும்புகிறீர்களா?",
|
||||
"Are you sure to delete {{count}} selected file(s)?_other": "இந்த கோப்புகளை நீக்க விரும்புகிறீர்களா?",
|
||||
"Cloud Storage Usage": "மேக சேமிப்பு பயன்பாடு",
|
||||
"Rename Group": "குழுவை மறுபெயரிடவும்"
|
||||
"Rename Group": "குழுவை மறுபெயரிடவும்",
|
||||
"From Directory": "கோப்புறையிலிருந்து",
|
||||
"Successfully imported {{count}} book(s)_one": "வெற்றிகரமாக 1 புத்தகம் இறக்குமதி செய்யப்பட்டது",
|
||||
"Successfully imported {{count}} book(s)_other": "வெற்றிகரமாக {{count}} புத்தகங்கள் இறக்குமதி செய்யப்பட்டது"
|
||||
}
|
||||
|
||||
@@ -751,5 +751,7 @@
|
||||
"Page {{current}} of {{total}}": "หน้า {{current}} จาก {{total}}",
|
||||
"Are you sure to delete {{count}} selected file(s)?_other": "คุณแน่ใจหรือว่าต้องการลบไฟล์ที่เลือก?",
|
||||
"Cloud Storage Usage": "การใช้งานพื้นที่เก็บข้อมูลคลาวด์",
|
||||
"Rename Group": "เปลี่ยนชื่อกลุ่ม"
|
||||
"Rename Group": "เปลี่ยนชื่อกลุ่ม",
|
||||
"From Directory": "จากไดเรกทอรี",
|
||||
"Successfully imported {{count}} book(s)_other": "นำเข้า {{count}} หนังสือเรียบร้อยแล้ว"
|
||||
}
|
||||
|
||||
@@ -760,5 +760,8 @@
|
||||
"Are you sure to delete {{count}} selected file(s)?_one": "Seçilen {{count}} dosyayı silmek istediğinizden emin misiniz?",
|
||||
"Are you sure to delete {{count}} selected file(s)?_other": "Seçilen {{count}} dosyayı silmek istediğinizden emin misiniz?",
|
||||
"Cloud Storage Usage": "Bulut Depolama Kullanımı",
|
||||
"Rename Group": "Grubu Yeniden Adlandır"
|
||||
"Rename Group": "Grubu Yeniden Adlandır",
|
||||
"From Directory": "Dizinden",
|
||||
"Successfully imported {{count}} book(s)_one": "Başarıyla 1 kitap içe aktarıldı",
|
||||
"Successfully imported {{count}} book(s)_other": "Başarıyla {{count}} kitap içe aktarıldı"
|
||||
}
|
||||
|
||||
@@ -778,5 +778,10 @@
|
||||
"Are you sure to delete {{count}} selected file(s)?_many": "Ви впевнені, що хочете видалити {{count}} файлів?",
|
||||
"Are you sure to delete {{count}} selected file(s)?_other": "Ви впевнені, що хочете видалити {{count}} файлів?",
|
||||
"Cloud Storage Usage": "Використання хмарного сховища",
|
||||
"Rename Group": "Перейменувати групу"
|
||||
"Rename Group": "Перейменувати групу",
|
||||
"From Directory": "З каталогу",
|
||||
"Successfully imported {{count}} book(s)_one": "Успішно імпортовано 1 книгу",
|
||||
"Successfully imported {{count}} book(s)_few": "Успішно імпортовано {{count}} книги",
|
||||
"Successfully imported {{count}} book(s)_many": "Успішно імпортовано {{count}} книг",
|
||||
"Successfully imported {{count}} book(s)_other": "Успішно імпортовано {{count}} книг"
|
||||
}
|
||||
|
||||
@@ -751,5 +751,7 @@
|
||||
"Page {{current}} of {{total}}": "Trang {{current}} trên {{total}}",
|
||||
"Are you sure to delete {{count}} selected file(s)?_other": "Bạn có chắc muốn xóa {{count}} tệp đã chọn không?",
|
||||
"Cloud Storage Usage": "Sử dụng lưu trữ đám mây",
|
||||
"Rename Group": "Đổi tên nhóm"
|
||||
"Rename Group": "Đổi tên nhóm",
|
||||
"From Directory": "Từ thư mục",
|
||||
"Successfully imported {{count}} book(s)_other": "Đã nhập thành công {{count}} sách"
|
||||
}
|
||||
|
||||
@@ -751,5 +751,7 @@
|
||||
"Page {{current}} of {{total}}": "第 {{current}} 页,共 {{total}} 页",
|
||||
"Are you sure to delete {{count}} selected file(s)?_other": "确定要删除已选择的 {{count}} 个文件吗?",
|
||||
"Cloud Storage Usage": "云存储使用情况",
|
||||
"Rename Group": "重命名分组"
|
||||
"Rename Group": "重命名分组",
|
||||
"From Directory": "从文件夹导入",
|
||||
"Successfully imported {{count}} book(s)_other": "成功导入 {{count}} 本书"
|
||||
}
|
||||
|
||||
@@ -751,5 +751,7 @@
|
||||
"Page {{current}} of {{total}}": "第 {{current}} 頁,共 {{total}} 頁",
|
||||
"Are you sure to delete {{count}} selected file(s)?_other": "確定要刪除已選擇的 {{count}} 個檔案嗎?",
|
||||
"Cloud Storage Usage": "雲端儲存使用情況",
|
||||
"Rename Group": "重新命名"
|
||||
"Rename Group": "重新命名",
|
||||
"From Directory": "從目錄導入",
|
||||
"Successfully imported {{count}} book(s)_other": "成功導入 {{count}} 本書"
|
||||
}
|
||||
|
||||
@@ -44,17 +44,22 @@ fn build_windows_thumbnail() {
|
||||
}
|
||||
|
||||
let dll_name = "windows_thumbnail.dll";
|
||||
let dll_src = if !target_triple.is_empty() {
|
||||
let candidate_paths = [
|
||||
dll_crate_dir.join("target").join(&profile).join(dll_name),
|
||||
dll_crate_dir
|
||||
.join("target")
|
||||
.join(&target_triple)
|
||||
.join(&profile)
|
||||
.join(dll_name)
|
||||
} else {
|
||||
dll_crate_dir.join("target").join(&profile).join(dll_name)
|
||||
};
|
||||
let dll_dest = dll_crate_dir.join("target").join(dll_name);
|
||||
.join(dll_name),
|
||||
];
|
||||
|
||||
fs::copy(&dll_src, &dll_dest).expect("Failed to copy windows_thumbnail DLL");
|
||||
let dll_src = candidate_paths
|
||||
.iter()
|
||||
.find(|p| p.exists())
|
||||
.expect("Failed to find built windows_thumbnail DLL");
|
||||
|
||||
let dll_dest = &dll_crate_dir.join("target").join(dll_name);
|
||||
|
||||
fs::copy(dll_src, dll_dest).expect("Failed to copy windows_thumbnail DLL");
|
||||
println!("cargo:rerun-if-changed={}", dll_dest.display());
|
||||
}
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
use tauri::{command, AppHandle, Runtime};
|
||||
use std::path::PathBuf;
|
||||
use tauri::{command, AppHandle, Runtime, State};
|
||||
|
||||
use crate::models::*;
|
||||
use crate::DirectoryCallbackState;
|
||||
use crate::NativeBridgeExt;
|
||||
use crate::Result;
|
||||
|
||||
@@ -160,8 +162,21 @@ pub(crate) async fn open_external_url<R: Runtime>(
|
||||
#[command]
|
||||
pub(crate) async fn select_directory<R: Runtime>(
|
||||
app: AppHandle<R>,
|
||||
callback_state: State<'_, DirectoryCallbackState<R>>,
|
||||
) -> Result<SelectDirectoryResponse> {
|
||||
app.native_bridge().select_directory()
|
||||
let result = app.native_bridge().select_directory()?;
|
||||
|
||||
if let Some(dir_path) = &result.path {
|
||||
let path = PathBuf::from(dir_path);
|
||||
|
||||
if let Ok(callback_guard) = callback_state.callback.lock() {
|
||||
if let Some(callback) = callback_guard.as_ref() {
|
||||
callback(&app, &path);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
#[command]
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
use std::sync::{Arc, Mutex};
|
||||
use tauri::{
|
||||
plugin::{Builder, TauriPlugin},
|
||||
Manager, Runtime,
|
||||
@@ -17,6 +18,9 @@ mod platform;
|
||||
|
||||
pub use error::{Error, Result};
|
||||
|
||||
use std::path::PathBuf;
|
||||
use tauri::AppHandle;
|
||||
|
||||
#[cfg(desktop)]
|
||||
use desktop::NativeBridge;
|
||||
#[cfg(mobile)]
|
||||
@@ -33,6 +37,20 @@ impl<R: Runtime, T: Manager<R>> crate::NativeBridgeExt<R> for T {
|
||||
}
|
||||
}
|
||||
|
||||
type DirectoryCallback<R> = Box<dyn Fn(&AppHandle<R>, &PathBuf) + Send + Sync>;
|
||||
|
||||
pub struct DirectoryCallbackState<R: Runtime> {
|
||||
pub callback: Arc<Mutex<Option<DirectoryCallback<R>>>>,
|
||||
}
|
||||
|
||||
impl<R: Runtime> Default for DirectoryCallbackState<R> {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
callback: Arc::new(Mutex::new(None)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Initializes the plugin.
|
||||
pub fn init<R: Runtime>() -> TauriPlugin<R> {
|
||||
Builder::new("native-bridge")
|
||||
@@ -66,7 +84,18 @@ pub fn init<R: Runtime>() -> TauriPlugin<R> {
|
||||
#[cfg(desktop)]
|
||||
let native_bridge = desktop::init(app, api)?;
|
||||
app.manage(native_bridge);
|
||||
app.manage(DirectoryCallbackState::<R>::default());
|
||||
Ok(())
|
||||
})
|
||||
.build()
|
||||
}
|
||||
|
||||
pub fn register_select_directory_callback<R: Runtime>(
|
||||
app: &AppHandle<R>,
|
||||
callback: impl Fn(&AppHandle<R>, &PathBuf) + Send + Sync + 'static,
|
||||
) {
|
||||
if let Some(state) = app.try_state::<DirectoryCallbackState<R>>() {
|
||||
let mut cb = state.callback.lock().unwrap();
|
||||
*cb = Some(Box::new(callback));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,18 +13,19 @@ use tauri::utils::config::BackgroundThrottlingPolicy;
|
||||
#[cfg(target_os = "macos")]
|
||||
use tauri::TitleBarStyle;
|
||||
|
||||
#[cfg(desktop)]
|
||||
use std::path::PathBuf;
|
||||
#[cfg(desktop)]
|
||||
use tauri::{AppHandle, Listener, Manager, Url};
|
||||
#[cfg(desktop)]
|
||||
use tauri::{AppHandle, Manager};
|
||||
use tauri_plugin_fs::FsExt;
|
||||
|
||||
#[cfg(desktop)]
|
||||
use tauri::{Listener, Url};
|
||||
#[cfg(target_os = "macos")]
|
||||
mod macos;
|
||||
mod transfer_file;
|
||||
use tauri::{command, Emitter, WebviewUrl, WebviewWindowBuilder, Window};
|
||||
#[cfg(target_os = "android")]
|
||||
use tauri_plugin_native_bridge::register_select_directory_callback;
|
||||
#[cfg(target_os = "android")]
|
||||
use tauri_plugin_native_bridge::{NativeBridgeExt, OpenExternalUrlRequest};
|
||||
use tauri_plugin_oauth::start;
|
||||
#[cfg(not(target_os = "android"))]
|
||||
@@ -49,7 +50,6 @@ fn allow_file_in_scopes(app: &AppHandle, files: Vec<PathBuf>) {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(desktop)]
|
||||
fn allow_dir_in_scopes(app: &AppHandle, dir: &PathBuf) {
|
||||
let fs_scope = app.fs_scope();
|
||||
let asset_protocol_scope = app.asset_protocol_scope();
|
||||
@@ -223,6 +223,11 @@ pub fn run() {
|
||||
allow_dir_in_scopes(app.handle(), &PathBuf::from(get_executable_dir()));
|
||||
}
|
||||
|
||||
#[cfg(target_os = "android")]
|
||||
register_select_directory_callback(app.handle(), move |app, path| {
|
||||
allow_dir_in_scopes(app, path);
|
||||
});
|
||||
|
||||
#[cfg(desktop)]
|
||||
{
|
||||
app.handle().plugin(tauri_plugin_cli::init())?;
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import clsx from 'clsx';
|
||||
import { useEnv } from '@/context/EnvContext';
|
||||
import { useTranslation } from '@/hooks/useTranslation';
|
||||
import { IoFileTray } from 'react-icons/io5';
|
||||
import { MdRssFeed } from 'react-icons/md';
|
||||
@@ -9,20 +8,26 @@ import Menu from '@/components/Menu';
|
||||
|
||||
interface ImportMenuProps {
|
||||
setIsDropdownOpen?: (open: boolean) => void;
|
||||
onImportBooks: () => void;
|
||||
onImportBooksFromFiles: () => void;
|
||||
onImportBooksFromDirectory?: () => void;
|
||||
onOpenCatalogManager: () => void;
|
||||
}
|
||||
|
||||
const ImportMenu: React.FC<ImportMenuProps> = ({
|
||||
setIsDropdownOpen,
|
||||
onImportBooks,
|
||||
onImportBooksFromFiles,
|
||||
onImportBooksFromDirectory,
|
||||
onOpenCatalogManager,
|
||||
}) => {
|
||||
const _ = useTranslation();
|
||||
const { appService } = useEnv();
|
||||
|
||||
const handleImportBooks = () => {
|
||||
onImportBooks();
|
||||
const handleImportFromFiles = () => {
|
||||
onImportBooksFromFiles();
|
||||
setIsDropdownOpen?.(false);
|
||||
};
|
||||
|
||||
const handleImportFromDirectory = () => {
|
||||
onImportBooksFromDirectory?.();
|
||||
setIsDropdownOpen?.(false);
|
||||
};
|
||||
|
||||
@@ -33,17 +38,21 @@ const ImportMenu: React.FC<ImportMenuProps> = ({
|
||||
|
||||
return (
|
||||
<Menu
|
||||
className={clsx(
|
||||
'dropdown-content bg-base-100 rounded-box z-[1] mt-3 w-52 p-2 shadow',
|
||||
appService?.isMobile ? 'no-triangle' : 'dropdown-center',
|
||||
)}
|
||||
className={clsx('dropdown-content bg-base-100 rounded-box z-[1] mt-3 p-2 shadow')}
|
||||
onCancel={() => setIsDropdownOpen?.(false)}
|
||||
>
|
||||
<MenuItem
|
||||
label={_('From Local File')}
|
||||
Icon={<IoFileTray className='h-5 w-5' />}
|
||||
onClick={handleImportBooks}
|
||||
onClick={handleImportFromFiles}
|
||||
/>
|
||||
{onImportBooksFromDirectory && (
|
||||
<MenuItem
|
||||
label={_('From Directory')}
|
||||
Icon={<IoFileTray className='h-5 w-5' />}
|
||||
onClick={handleImportFromDirectory}
|
||||
/>
|
||||
)}
|
||||
<MenuItem
|
||||
label={_('Online Library')}
|
||||
Icon={<MdRssFeed className='h-5 w-5' />}
|
||||
|
||||
@@ -26,7 +26,8 @@ import ViewMenu from './ViewMenu';
|
||||
interface LibraryHeaderProps {
|
||||
isSelectMode: boolean;
|
||||
isSelectAll: boolean;
|
||||
onImportBooks: () => void;
|
||||
onImportBooksFromFiles: () => void;
|
||||
onImportBooksFromDirectory?: () => void;
|
||||
onOpenCatalogManager: () => void;
|
||||
onToggleSelectMode: () => void;
|
||||
onSelectAll: () => void;
|
||||
@@ -36,7 +37,8 @@ interface LibraryHeaderProps {
|
||||
const LibraryHeader: React.FC<LibraryHeaderProps> = ({
|
||||
isSelectMode,
|
||||
isSelectAll,
|
||||
onImportBooks,
|
||||
onImportBooksFromFiles,
|
||||
onImportBooksFromDirectory,
|
||||
onOpenCatalogManager,
|
||||
onToggleSelectMode,
|
||||
onSelectAll,
|
||||
@@ -152,14 +154,14 @@ const LibraryHeader: React.FC<LibraryHeaderProps> = ({
|
||||
<Dropdown
|
||||
label={_('Import Books')}
|
||||
className={clsx(
|
||||
'exclude-title-bar-mousedown dropdown-bottom flex h-6 cursor-pointer justify-center',
|
||||
isMobile ? 'dropdown-end' : 'dropdown-center',
|
||||
'exclude-title-bar-mousedown dropdown-bottom dropdown-center flex h-6 cursor-pointer justify-center',
|
||||
)}
|
||||
buttonClassName='p-0 h-6 min-h-6 w-6 flex items-center justify-center !bg-transparent'
|
||||
buttonClassName='p-0 h-6 min-h-6 w-6 flex touch-target items-center justify-center !bg-transparent'
|
||||
toggleButton={<PiPlus role='none' className='m-0.5 h-5 w-5' />}
|
||||
>
|
||||
<ImportMenu
|
||||
onImportBooks={onImportBooks}
|
||||
onImportBooksFromFiles={onImportBooksFromFiles}
|
||||
onImportBooksFromDirectory={onImportBooksFromDirectory}
|
||||
onOpenCatalogManager={onOpenCatalogManager}
|
||||
/>
|
||||
</Dropdown>
|
||||
|
||||
@@ -7,7 +7,6 @@ import {
|
||||
RiLoader2Line,
|
||||
} from 'react-icons/ri';
|
||||
import { documentDir, join } from '@tauri-apps/api/path';
|
||||
import { invoke, PermissionState } from '@tauri-apps/api/core';
|
||||
import { relaunch } from '@tauri-apps/plugin-process';
|
||||
import { useEnv } from '@/context/EnvContext';
|
||||
import { useTranslation } from '@/hooks/useTranslation';
|
||||
@@ -20,6 +19,7 @@ import { formatBytes } from '@/utils/book';
|
||||
import { getOSPlatform } from '@/utils/misc';
|
||||
import { getExternalSDCardPath } from '@/utils/bridge';
|
||||
import { FILE_REVEAL_LABELS, FILE_REVEAL_PLATFORMS } from '@/utils/os';
|
||||
import { requestStoragePermission } from '@/utils/permission';
|
||||
import Dialog from '@/components/Dialog';
|
||||
import Dropdown from '@/components/Dropdown';
|
||||
import MenuItem from '@/components/MenuItem';
|
||||
@@ -42,10 +42,6 @@ interface MigrationProgress {
|
||||
currentFile?: string;
|
||||
}
|
||||
|
||||
interface Permissions {
|
||||
manageStorage: PermissionState;
|
||||
}
|
||||
|
||||
export const MigrateDataWindow = () => {
|
||||
const _ = useTranslation();
|
||||
const { appService, envConfig } = useEnv();
|
||||
@@ -158,13 +154,7 @@ export const MigrateDataWindow = () => {
|
||||
setErrorMessage('');
|
||||
|
||||
if (!dir.includes('Android/data')) {
|
||||
let permission = await invoke<Permissions>('plugin:native-bridge|checkPermissions');
|
||||
if (permission.manageStorage !== 'granted') {
|
||||
permission = await invoke<Permissions>(
|
||||
'plugin:native-bridge|request_manage_storage_permission',
|
||||
);
|
||||
}
|
||||
if (permission.manageStorage !== 'granted') return;
|
||||
if (!(await requestStoragePermission())) return;
|
||||
}
|
||||
|
||||
try {
|
||||
|
||||
@@ -20,6 +20,7 @@ import { tauriHandleSetAlwaysOnTop, tauriHandleToggleFullScreen } from '@/utils/
|
||||
import { optInTelemetry, optOutTelemetry } from '@/utils/telemetry';
|
||||
import { setAboutDialogVisible } from '@/components/AboutWindow';
|
||||
import { setMigrateDataDirDialogVisible } from '@/app/library/components/MigrateDataWindow';
|
||||
import { requestStoragePermission } from '@/utils/permission';
|
||||
import { saveSysSettings } from '@/helpers/settings';
|
||||
import { selectDirectory } from '@/utils/bridge';
|
||||
import UserAvatar from '@/components/UserAvatar';
|
||||
@@ -175,13 +176,7 @@ const SettingsMenu: React.FC<SettingsMenuProps> = ({ setIsDropdownOpen }) => {
|
||||
};
|
||||
|
||||
const handleSetSavedBookCoverForLockScreen = async () => {
|
||||
let permission = await invoke<Permissions>('plugin:native-bridge|checkPermissions');
|
||||
if (permission.manageStorage !== 'granted') {
|
||||
permission = await invoke<Permissions>(
|
||||
'plugin:native-bridge|request_manage_storage_permission',
|
||||
);
|
||||
}
|
||||
if (permission.manageStorage !== 'granted' && appService?.distChannel === 'readest') return;
|
||||
if (!(await requestStoragePermission()) && appService?.distChannel === 'readest') return;
|
||||
|
||||
const newValue = settings.savedBookCoverForLockScreen ? '' : 'default';
|
||||
if (newValue) {
|
||||
|
||||
@@ -15,7 +15,7 @@ import { formatAuthors, formatTitle, getPrimaryLanguage, listFormater } from '@/
|
||||
import { eventDispatcher } from '@/utils/event';
|
||||
import { ProgressPayload } from '@/utils/transfer';
|
||||
import { throttle } from '@/utils/throttle';
|
||||
import { getFilename } from '@/utils/path';
|
||||
import { getDirPath, getFilename, joinPaths } from '@/utils/path';
|
||||
import { parseOpenWithFiles } from '@/helpers/openWith';
|
||||
import { isTauriAppPlatform, isWebAppPlatform } from '@/services/environment';
|
||||
import { checkForAppUpdates, checkAppReleaseNotes } from '@/helpers/updater';
|
||||
@@ -38,7 +38,9 @@ import { useBookDataStore } from '@/store/bookDataStore';
|
||||
import { useScreenWakeLock } from '@/hooks/useScreenWakeLock';
|
||||
import { useOpenWithBooks } from '@/hooks/useOpenWithBooks';
|
||||
import { SelectedFile, useFileSelector } from '@/hooks/useFileSelector';
|
||||
import { lockScreenOrientation } from '@/utils/bridge';
|
||||
import { lockScreenOrientation, selectDirectory } from '@/utils/bridge';
|
||||
import { requestStoragePermission } from '@/utils/permission';
|
||||
import { SUPPORTED_BOOK_EXTS } from '@/services/constants';
|
||||
import {
|
||||
tauriHandleClose,
|
||||
tauriHandleSetAlwaysOnTop,
|
||||
@@ -143,7 +145,7 @@ const LibraryPageContent = ({ searchParams }: { searchParams: ReadonlyURLSearchP
|
||||
setSettingsDialogOpen(true);
|
||||
},
|
||||
onOpenBooks: () => {
|
||||
handleImportBooks();
|
||||
handleImportBooksFromFiles();
|
||||
},
|
||||
});
|
||||
|
||||
@@ -366,6 +368,7 @@ const LibraryPageContent = ({ searchParams }: { searchParams: ReadonlyURLSearchP
|
||||
setLoading(true);
|
||||
const { library } = useLibraryStore.getState();
|
||||
const failedImports: Array<{ filename: string; errorMessage: string }> = [];
|
||||
const successfulImports: string[] = [];
|
||||
const errorMap: [string, string][] = [
|
||||
['No chapters detected', _('No chapters detected')],
|
||||
['Failed to parse EPUB', _('Failed to parse the EPUB file')],
|
||||
@@ -380,15 +383,25 @@ const LibraryPageContent = ({ searchParams }: { searchParams: ReadonlyURLSearchP
|
||||
if (!file) return;
|
||||
try {
|
||||
const book = await appService?.importBook(file, library);
|
||||
const { path, basePath } = selectedFile;
|
||||
if (book && groupId) {
|
||||
book.groupId = groupId;
|
||||
book.groupName = getGroupName(groupId);
|
||||
await updateBook(envConfig, book);
|
||||
} else if (book && path && basePath) {
|
||||
const rootPath = getDirPath(basePath);
|
||||
const groupName = getDirPath(path).replace(rootPath, '').replace(/^\//, '');
|
||||
book.groupName = groupName;
|
||||
book.groupId = getGroupId(groupName);
|
||||
await updateBook(envConfig, book);
|
||||
}
|
||||
if (user && book && !book.uploadedAt && settings.autoUpload) {
|
||||
console.log('Uploading book:', book.title);
|
||||
handleBookUpload(book, false);
|
||||
}
|
||||
if (book) {
|
||||
successfulImports.push(book.title);
|
||||
}
|
||||
} catch (error) {
|
||||
const filename = typeof file === 'string' ? file : file.name;
|
||||
const baseFilename = getFilename(filename);
|
||||
@@ -417,8 +430,17 @@ const LibraryPageContent = ({ searchParams }: { searchParams: ReadonlyURLSearchP
|
||||
_('Failed to import book(s): {{filenames}}', {
|
||||
filenames: listFormater(false).format(filenames),
|
||||
}) + (errorMessage ? `\n${errorMessage}` : ''),
|
||||
timeout: 5000,
|
||||
type: 'error',
|
||||
});
|
||||
} else if (successfulImports.length > 0) {
|
||||
eventDispatcher.dispatch('toast', {
|
||||
message: _('Successfully imported {{count}} book(s)', {
|
||||
count: successfulImports.length,
|
||||
}),
|
||||
timeout: 2000,
|
||||
type: 'success',
|
||||
});
|
||||
}
|
||||
|
||||
setLibrary([...library]);
|
||||
@@ -582,9 +604,9 @@ const LibraryPageContent = ({ searchParams }: { searchParams: ReadonlyURLSearchP
|
||||
await updateBook(envConfig, book);
|
||||
};
|
||||
|
||||
const handleImportBooks = async () => {
|
||||
const handleImportBooksFromFiles = async () => {
|
||||
setIsSelectMode(false);
|
||||
console.log('Importing books...');
|
||||
console.log('Importing books from files...');
|
||||
selectFiles({ type: 'books', multiple: true }).then((result) => {
|
||||
if (result.files.length === 0 || result.error) return;
|
||||
const groupId = searchParams?.get('group') || '';
|
||||
@@ -592,6 +614,40 @@ const LibraryPageContent = ({ searchParams }: { searchParams: ReadonlyURLSearchP
|
||||
});
|
||||
};
|
||||
|
||||
const handleImportBooksFromDirectory = async () => {
|
||||
if (!appService || !isTauriAppPlatform()) return;
|
||||
|
||||
setIsSelectMode(false);
|
||||
console.log('Importing books from directory...');
|
||||
let importDirectory: string | undefined = '';
|
||||
if (appService.isAndroidApp) {
|
||||
if (!(await requestStoragePermission())) return;
|
||||
const response = await selectDirectory();
|
||||
importDirectory = response.path;
|
||||
} else {
|
||||
const selectedDir = await appService.selectDirectory?.('read');
|
||||
importDirectory = selectedDir;
|
||||
}
|
||||
if (!importDirectory) {
|
||||
console.log('No directory selected');
|
||||
return;
|
||||
}
|
||||
const files = await appService.readDirectory(importDirectory, 'None');
|
||||
const supportedFiles = files.filter((file) => {
|
||||
const ext = file.path.split('.').pop()?.toLowerCase() || '';
|
||||
return SUPPORTED_BOOK_EXTS.includes(ext);
|
||||
});
|
||||
const toImportFiles = await Promise.all(
|
||||
supportedFiles.map(async (file) => {
|
||||
return {
|
||||
path: await joinPaths(importDirectory, file.path),
|
||||
basePath: importDirectory,
|
||||
};
|
||||
}),
|
||||
);
|
||||
importBooks(toImportFiles, undefined);
|
||||
};
|
||||
|
||||
const handleSetSelectMode = (selectMode: boolean) => {
|
||||
if (selectMode && appService?.hasHaptics) {
|
||||
impactFeedback('medium');
|
||||
@@ -656,7 +712,10 @@ const LibraryPageContent = ({ searchParams }: { searchParams: ReadonlyURLSearchP
|
||||
<LibraryHeader
|
||||
isSelectMode={isSelectMode}
|
||||
isSelectAll={isSelectAll}
|
||||
onImportBooks={handleImportBooks}
|
||||
onImportBooksFromFiles={handleImportBooksFromFiles}
|
||||
onImportBooksFromDirectory={
|
||||
appService?.canReadExternalDir ? handleImportBooksFromDirectory : undefined
|
||||
}
|
||||
onOpenCatalogManager={() => setShowCatalogManager(true)}
|
||||
onToggleSelectMode={() => handleSetSelectMode(!isSelectMode)}
|
||||
onSelectAll={handleSelectAll}
|
||||
@@ -742,7 +801,7 @@ const LibraryPageContent = ({ searchParams }: { searchParams: ReadonlyURLSearchP
|
||||
isSelectMode={isSelectMode}
|
||||
isSelectAll={isSelectAll}
|
||||
isSelectNone={isSelectNone}
|
||||
handleImportBooks={handleImportBooks}
|
||||
handleImportBooks={handleImportBooksFromFiles}
|
||||
handleBookUpload={handleBookUpload}
|
||||
handleBookDownload={handleBookDownload}
|
||||
handleBookDelete={handleBookDelete('both')}
|
||||
@@ -764,7 +823,7 @@ const LibraryPageContent = ({ searchParams }: { searchParams: ReadonlyURLSearchP
|
||||
'Welcome to your library. You can import your books here and read them anytime.',
|
||||
)}
|
||||
</p>
|
||||
<button className='btn btn-primary rounded-xl' onClick={handleImportBooks}>
|
||||
<button className='btn btn-primary rounded-xl' onClick={handleImportBooksFromFiles}>
|
||||
{_('Import Books')}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@@ -18,6 +18,7 @@ export interface SelectedFile {
|
||||
|
||||
// For Tauri file
|
||||
path?: string;
|
||||
basePath?: string;
|
||||
}
|
||||
|
||||
export interface FileSelectionResult {
|
||||
|
||||
@@ -98,6 +98,7 @@ export abstract class BaseAppService implements AppService {
|
||||
hasScreenBrightness = false;
|
||||
hasIAP = false;
|
||||
canCustomizeRootDir = false;
|
||||
canReadExternalDir = false;
|
||||
distChannel = 'readest' as DistChannel;
|
||||
|
||||
protected CURRENT_MIGRATION_VERSION = 20251124;
|
||||
@@ -354,7 +355,6 @@ export abstract class BaseAppService implements AppService {
|
||||
loadedBook.metadata.title = getBaseFilename(filename);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
throw new Error(`Failed to open the book: ${(error as Error).message || error}`);
|
||||
}
|
||||
|
||||
@@ -405,7 +405,14 @@ export abstract class BaseAppService implements AppService {
|
||||
} else if (typeof file === 'string' && isContentURI(file)) {
|
||||
await this.fs.copyFile(file, getLocalBookFilename(book), 'Books');
|
||||
} else if (typeof file === 'string' && !isValidURL(file)) {
|
||||
await this.fs.copyFile(file, getLocalBookFilename(book), 'Books');
|
||||
try {
|
||||
// try to copy the file directly first in case of large files to avoid memory issues
|
||||
// on desktop when reading recursively from selected directory the direct copy will fail
|
||||
// due to permission issues, then fallback to read and write files
|
||||
await this.fs.copyFile(file, getLocalBookFilename(book), 'Books');
|
||||
} catch {
|
||||
await this.fs.writeFile(getLocalBookFilename(book), 'Books', fileobj);
|
||||
}
|
||||
} else {
|
||||
await this.fs.writeFile(getLocalBookFilename(book), 'Books', fileobj);
|
||||
}
|
||||
@@ -441,6 +448,7 @@ export abstract class BaseAppService implements AppService {
|
||||
|
||||
return existingBook || book;
|
||||
} catch (error) {
|
||||
console.error('Error importing book:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -34,7 +34,7 @@ import {
|
||||
FileItem,
|
||||
DistChannel,
|
||||
} from '@/types/system';
|
||||
import { getOSPlatform, isContentURI, isValidURL } from '@/utils/misc';
|
||||
import { getOSPlatform, isContentURI, isFileURI, isValidURL } from '@/utils/misc';
|
||||
import { getDirPath, getFilename } from '@/utils/path';
|
||||
import { NativeFile, RemoteFile } from '@/utils/file';
|
||||
import { copyURIToPath } from '@/utils/bridge';
|
||||
@@ -212,17 +212,20 @@ export const nativeFileSystem: FileSystem = {
|
||||
}
|
||||
return await new NativeFile(dst, fname, baseDir ? baseDir : null).open();
|
||||
}
|
||||
} else if (isFileURI(path)) {
|
||||
return await new NativeFile(fp, fname, baseDir ? baseDir : null).open();
|
||||
} else {
|
||||
const prefix = await this.getPrefix(base);
|
||||
const absolutePath = path.startsWith('/') ? path : prefix ? await join(prefix, path) : null;
|
||||
if (absolutePath && OS_TYPE !== 'android') {
|
||||
if (OS_TYPE === 'android') {
|
||||
// NOTE: RemoteFile is not usable on Android due to a known issue of range request in Android WebView.
|
||||
// see https://issues.chromium.org/issues/40739128
|
||||
return await new NativeFile(fp, fname, baseDir ? baseDir : null).open();
|
||||
} else {
|
||||
// NOTE: RemoteFile currently performs about 2× faster than NativeFile
|
||||
// due to an unresolved performance issue in Tauri (see tauri-apps/tauri#9190).
|
||||
// Once the bug is resolved, we should switch back to using NativeFile.
|
||||
// RemoteFile is not usable on Android due to unknown issues of range fetch with Android WebView.
|
||||
const prefix = await this.getPrefix(base);
|
||||
const absolutePath = prefix ? await join(prefix, path) : path;
|
||||
return await new RemoteFile(this.getURL(absolutePath), fname).open();
|
||||
} else {
|
||||
return await new NativeFile(fp, fname, baseDir ? baseDir : null).open();
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -319,10 +322,13 @@ export const nativeFileSystem: FileSystem = {
|
||||
} else {
|
||||
const filePath = await join(parent, entry.name);
|
||||
const relativePath = relative ? await join(relative, entry.name) : entry.name;
|
||||
const fileInfo = await stat(filePath, baseDir ? { baseDir } : undefined);
|
||||
const opts = baseDir ? { baseDir } : undefined;
|
||||
const fileSize = await stat(filePath, opts)
|
||||
.then((info) => info.size)
|
||||
.catch(() => 0);
|
||||
fileList.push({
|
||||
path: relativePath,
|
||||
size: fileInfo.size,
|
||||
size: fileSize,
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -374,6 +380,7 @@ export class NativeAppService extends BaseAppService {
|
||||
// CustomizeRootDir has a blocker on macOS App Store builds due to Security Scoped Resource restrictions.
|
||||
// See: https://github.com/tauri-apps/tauri/issues/3716
|
||||
override canCustomizeRootDir = DIST_CHANNEL !== 'appstore';
|
||||
override canReadExternalDir = DIST_CHANNEL !== 'appstore' && DIST_CHANNEL !== 'playstore';
|
||||
override distChannel = DIST_CHANNEL;
|
||||
|
||||
private execDir?: string = undefined;
|
||||
|
||||
@@ -371,6 +371,16 @@ foliate-fxl {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.touch-target {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.touch-target::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
inset: -12px;
|
||||
}
|
||||
|
||||
.no-context-menu {
|
||||
-webkit-touch-callout: none;
|
||||
-webkit-user-select: none;
|
||||
|
||||
@@ -66,6 +66,7 @@ export interface AppService {
|
||||
isPortableApp: boolean;
|
||||
isDesktopApp: boolean;
|
||||
canCustomizeRootDir: boolean;
|
||||
canReadExternalDir: boolean;
|
||||
distChannel: DistChannel;
|
||||
|
||||
init(): Promise<void>;
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { join } from '@tauri-apps/api/path';
|
||||
import { isContentURI, isFileURI, isValidURL } from './misc';
|
||||
|
||||
export const getFilename = (fileOrUri: string) => {
|
||||
@@ -28,3 +29,7 @@ export const getDirPath = (filePath: string) => {
|
||||
parts.pop();
|
||||
return parts.join('/');
|
||||
};
|
||||
|
||||
export const joinPaths = async (...paths: string[]) => {
|
||||
return await join(...paths);
|
||||
};
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
import { invoke, PermissionState } from '@tauri-apps/api/core';
|
||||
|
||||
interface Permissions {
|
||||
manageStorage: PermissionState;
|
||||
}
|
||||
|
||||
export const requestStoragePermission = async (): Promise<boolean> => {
|
||||
let permission = await invoke<Permissions>('plugin:native-bridge|checkPermissions');
|
||||
if (permission.manageStorage !== 'granted') {
|
||||
permission = await invoke<Permissions>(
|
||||
'plugin:native-bridge|request_manage_storage_permission',
|
||||
);
|
||||
}
|
||||
return permission.manageStorage === 'granted';
|
||||
};
|
||||
Reference in New Issue
Block a user