Compare commits
24 commits
91243b99b5
...
9bcfe7a1f5
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9bcfe7a1f5 | ||
|
|
b3af8bf211 | ||
|
|
4afacccc8a | ||
|
|
8c57060db2 | ||
|
|
62bfcc3550 | ||
|
|
9c0eb63b82 | ||
|
|
33890633b5 | ||
|
|
b895b07bb2 | ||
|
|
afca6aef73 | ||
|
|
9a89100088 | ||
|
|
908f30a571 | ||
|
|
1c4ab75814 | ||
|
|
e21989aaa3 | ||
|
|
15e40461e5 | ||
|
|
0bd20e57e7 | ||
|
|
e35d5123f0 | ||
|
|
971374f60b | ||
|
|
93dd775f3c | ||
|
|
bb15f30fe5 | ||
|
|
e2bdcf8da4 | ||
|
|
b299f1a172 | ||
|
|
c114759c9e | ||
|
|
fc25260a5f | ||
|
|
e60ae4e41f |
42 changed files with 1871 additions and 781 deletions
426
Cargo.lock
generated
426
Cargo.lock
generated
File diff suppressed because it is too large
Load diff
26
Cargo.toml
26
Cargo.toml
|
|
@ -1,6 +1,6 @@
|
||||||
[package]
|
[package]
|
||||||
name = "cosmic-files"
|
name = "cosmic-files"
|
||||||
version = "1.0.9"
|
version = "1.0.11"
|
||||||
authors = ["Jeremy Soller <jeremy@system76.com>"]
|
authors = ["Jeremy Soller <jeremy@system76.com>"]
|
||||||
edition = "2024"
|
edition = "2024"
|
||||||
license = "GPL-3.0-only"
|
license = "GPL-3.0-only"
|
||||||
|
|
@ -14,7 +14,6 @@ icu = { version = "2.1.1", features = ["compiled_data"] }
|
||||||
cctk = { git = "https://github.com/pop-os/cosmic-protocols", package = "cosmic-client-toolkit", rev = "160b086", optional = true }
|
cctk = { git = "https://github.com/pop-os/cosmic-protocols", package = "cosmic-client-toolkit", rev = "160b086", optional = true }
|
||||||
cosmic-mime-apps = { git = "https://github.com/pop-os/cosmic-mime-apps.git", optional = true }
|
cosmic-mime-apps = { git = "https://github.com/pop-os/cosmic-mime-apps.git", optional = true }
|
||||||
dirs = "6.0.0"
|
dirs = "6.0.0"
|
||||||
env_logger = "0.11"
|
|
||||||
gio = { version = "0.21", optional = true }
|
gio = { version = "0.21", optional = true }
|
||||||
glib = { version = "0.21", optional = true }
|
glib = { version = "0.21", optional = true }
|
||||||
glob = "0.3"
|
glob = "0.3"
|
||||||
|
|
@ -56,12 +55,16 @@ rust-embed = "8"
|
||||||
slotmap = "1.1.1"
|
slotmap = "1.1.1"
|
||||||
recently-used-xbel = "1.2.0"
|
recently-used-xbel = "1.2.0"
|
||||||
zip = "8"
|
zip = "8"
|
||||||
uzers = "0.12.2"
|
|
||||||
md-5 = "0.10.6"
|
md-5 = "0.10.6"
|
||||||
png = "0.18"
|
png = "0.18"
|
||||||
jxl-oxide = { version = "0.12.5", features = ["image"] }
|
jxl-oxide = { version = "0.12.5", features = ["image"] }
|
||||||
num_cpus = "1.17.0"
|
num_cpus = "1.17.0"
|
||||||
filetime = "0.2"
|
filetime = "0.2"
|
||||||
|
tracing = "0.1.44"
|
||||||
|
tracing-subscriber = { version = "0.3.22", features = ["env-filter"] }
|
||||||
|
thiserror = "2.0.18"
|
||||||
|
atomic_float = "1.1.0"
|
||||||
|
num_enum = "0.7.6"
|
||||||
|
|
||||||
# Completion-based IO runtime to enable io_uring / IOCP file IO support.
|
# Completion-based IO runtime to enable io_uring / IOCP file IO support.
|
||||||
[dependencies.compio]
|
[dependencies.compio]
|
||||||
|
|
@ -77,7 +80,6 @@ features = [
|
||||||
"about",
|
"about",
|
||||||
"advanced-shaping",
|
"advanced-shaping",
|
||||||
"autosize",
|
"autosize",
|
||||||
"desktop",
|
|
||||||
"multi-window",
|
"multi-window",
|
||||||
"tokio",
|
"tokio",
|
||||||
"winit",
|
"winit",
|
||||||
|
|
@ -109,7 +111,7 @@ default = [
|
||||||
"wgpu",
|
"wgpu",
|
||||||
]
|
]
|
||||||
dbus-config = ["libcosmic/dbus-config"]
|
dbus-config = ["libcosmic/dbus-config"]
|
||||||
desktop = ["dep:cosmic-mime-apps", "dep:xdg"]
|
desktop = ["libcosmic/desktop", "dep:cosmic-mime-apps", "dep:xdg"]
|
||||||
desktop-applet = []
|
desktop-applet = []
|
||||||
gvfs = ["dep:gio", "dep:glib"]
|
gvfs = ["dep:gio", "dep:glib"]
|
||||||
io-uring = ["compio/io-uring"]
|
io-uring = ["compio/io-uring"]
|
||||||
|
|
@ -127,6 +129,7 @@ debug = true
|
||||||
|
|
||||||
[target.'cfg(unix)'.dependencies]
|
[target.'cfg(unix)'.dependencies]
|
||||||
fork = "0.7"
|
fork = "0.7"
|
||||||
|
uzers = "0.12.2"
|
||||||
|
|
||||||
[target.'cfg(target_os = "linux")'.dependencies]
|
[target.'cfg(target_os = "linux")'.dependencies]
|
||||||
procfs = "0.18"
|
procfs = "0.18"
|
||||||
|
|
@ -144,13 +147,12 @@ tokio = { version = "1", features = ["rt", "macros"] }
|
||||||
# [patch.'https://github.com/pop-os/cosmic-text']
|
# [patch.'https://github.com/pop-os/cosmic-text']
|
||||||
# cosmic-text = { path = "../cosmic-text" }
|
# cosmic-text = { path = "../cosmic-text" }
|
||||||
|
|
||||||
# [patch.'https://github.com/pop-os/libcosmic']
|
[patch.'https://github.com/pop-os/libcosmic']
|
||||||
# libcosmic = { path = "../libcosmic" }
|
libcosmic = { path = "../libcosmic" }
|
||||||
# cosmic-config = { path = "../libcosmic/cosmic-config" }
|
cosmic-config = { path = "../libcosmic/cosmic-config" }
|
||||||
# cosmic-theme = { path = "../libcosmic/cosmic-theme" }
|
cosmic-theme = { path = "../libcosmic/cosmic-theme" }
|
||||||
# libcosmic = { git = "https://github.com/pop-os/libcosmic//", branch = "iced-rebase" }
|
iced_futures = { path = "../libcosmic/iced/futures" }
|
||||||
# cosmic-config = { git = "https://github.com/pop-os/libcosmic//", branch = "iced-rebase" }
|
iced_winit = { path = "../libcosmic/iced/winit" }
|
||||||
# cosmic-theme = { git = "https://github.com/pop-os/libcosmic//", branch = "iced-rebase" }
|
|
||||||
|
|
||||||
|
|
||||||
# [patch.'https://github.com/pop-os/smithay-clipboard']
|
# [patch.'https://github.com/pop-os/smithay-clipboard']
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
[package]
|
[package]
|
||||||
name = "cosmic-files-applet"
|
name = "cosmic-files-applet"
|
||||||
version = "1.0.9"
|
version = "1.0.11"
|
||||||
edition = "2024"
|
edition = "2024"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
|
|
||||||
6
debian/changelog
vendored
6
debian/changelog
vendored
|
|
@ -1,3 +1,9 @@
|
||||||
|
cosmic-files (1.0.11) noble; urgency=medium
|
||||||
|
|
||||||
|
* Epoch 1.0.11 version update
|
||||||
|
|
||||||
|
-- Jeremy Soller <jeremy@system76.com> Tue, 14 Apr 2026 11:09:44 -0600
|
||||||
|
|
||||||
cosmic-files (1.0.9) noble; urgency=medium
|
cosmic-files (1.0.9) noble; urgency=medium
|
||||||
|
|
||||||
* Epoch 1.0.9 version update
|
* Epoch 1.0.9 version update
|
||||||
|
|
|
||||||
|
|
@ -9,9 +9,25 @@ use cosmic_files::dialog::{
|
||||||
Dialog, DialogChoice, DialogChoiceOption, DialogFilter, DialogFilterPattern, DialogKind,
|
Dialog, DialogChoice, DialogChoiceOption, DialogFilter, DialogFilterPattern, DialogKind,
|
||||||
DialogMessage, DialogResult, DialogSettings,
|
DialogMessage, DialogResult, DialogSettings,
|
||||||
};
|
};
|
||||||
|
use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt};
|
||||||
|
|
||||||
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
env_logger::Builder::from_env(env_logger::Env::default().default_filter_or("warn")).init();
|
let log_format = tracing_subscriber::fmt::format()
|
||||||
|
.pretty()
|
||||||
|
.without_time()
|
||||||
|
.with_line_number(true)
|
||||||
|
.with_file(true)
|
||||||
|
.with_target(false)
|
||||||
|
.with_thread_names(true);
|
||||||
|
|
||||||
|
let log_layer = tracing_subscriber::fmt::Layer::default()
|
||||||
|
.with_writer(std::io::stderr)
|
||||||
|
.event_format(log_format);
|
||||||
|
|
||||||
|
tracing_subscriber::registry()
|
||||||
|
.with(tracing_subscriber::EnvFilter::from_env("RUST_LOG"))
|
||||||
|
.with(log_layer)
|
||||||
|
.init();
|
||||||
|
|
||||||
let settings = Settings::default();
|
let settings = Settings::default();
|
||||||
app::run::<App>(settings, ())?;
|
app::run::<App>(settings, ())?;
|
||||||
|
|
|
||||||
|
|
@ -22,10 +22,10 @@ empty-trash-warning = سيتم حذف العناصر الموجودة في مج
|
||||||
|
|
||||||
## New File/Folder Dialog
|
## New File/Folder Dialog
|
||||||
|
|
||||||
create-new-file = إنشاء ملف جديد
|
create-new-file = أنشئ ملف جديد
|
||||||
create-new-folder = إنشاء مجلد جديد
|
create-new-folder = أنشئ مجلَّد جديد
|
||||||
file-name = اسم الملف
|
file-name = اسم الملف
|
||||||
folder-name = اسم المجلد
|
folder-name = اسم المجلَّد
|
||||||
file-already-exists = يوجد ملف بهذا الاسم بالفعل
|
file-already-exists = يوجد ملف بهذا الاسم بالفعل
|
||||||
folder-already-exists = يوجد مجلد بهذا الاسم بالفعل
|
folder-already-exists = يوجد مجلد بهذا الاسم بالفعل
|
||||||
name-hidden = الاسماء التي تبدأ بنقطة «.» ستكون مخفية
|
name-hidden = الاسماء التي تبدأ بنقطة «.» ستكون مخفية
|
||||||
|
|
@ -34,10 +34,10 @@ name-no-slashes = لا يمكن أن يحتوي الاسم على شرطات م
|
||||||
|
|
||||||
## Open/Save Dialog
|
## Open/Save Dialog
|
||||||
|
|
||||||
cancel = إلغاء
|
cancel = ألغِ
|
||||||
open = فتح
|
open = افتح
|
||||||
open-file = فتح ملف
|
open-file = افتح ملف
|
||||||
open-folder = افتح مجلد
|
open-folder = افتح مجلَّد
|
||||||
open-in-new-tab = افتح في لسان جديد
|
open-in-new-tab = افتح في لسان جديد
|
||||||
open-in-new-window = افتح في نافذة جديدة
|
open-in-new-window = افتح في نافذة جديدة
|
||||||
open-multiple-files = افتح عدة ملفات
|
open-multiple-files = افتح عدة ملفات
|
||||||
|
|
@ -150,12 +150,12 @@ trashed-on = مهمل
|
||||||
details = التفاصيل
|
details = التفاصيل
|
||||||
pause = ألبث
|
pause = ألبث
|
||||||
resume = استئناف
|
resume = استئناف
|
||||||
create-archive = إنشاء أرشيف
|
create-archive = أنشئ أرشيف
|
||||||
extract-password-required = كلمة السر مطلوبة
|
extract-password-required = كلمة السر مطلوبة
|
||||||
extract-to = استخرِج إلى...
|
extract-to = استخرِج إلى...
|
||||||
extract-to-title = استخرِج إلى مجلّد
|
extract-to-title = استخرِج إلى مجلّد
|
||||||
mount-error = تعذر الوصول إلى القرص
|
mount-error = تعذر الوصول إلى القرص
|
||||||
create = إنشاء
|
create = أنشئ
|
||||||
open-item-location = افتح مكان العنصر
|
open-item-location = افتح مكان العنصر
|
||||||
set-executable-and-launch-description = أتريد تعيين «{ $name }» كقابل للتنفيذ وتشغيله؟
|
set-executable-and-launch-description = أتريد تعيين «{ $name }» كقابل للتنفيذ وتشغيله؟
|
||||||
favorite-path-error-description =
|
favorite-path-error-description =
|
||||||
|
|
@ -259,7 +259,7 @@ related-apps = تطبيقات ذات صلة
|
||||||
selected-items = العناصر { $items } المحدّدة
|
selected-items = العناصر { $items } المحدّدة
|
||||||
permanently-delete-question = احذف نهائيًا؟
|
permanently-delete-question = احذف نهائيًا؟
|
||||||
delete = احذف
|
delete = احذف
|
||||||
permanently-delete-warning = هل أنت متأكد من أنك تريد حذف { $target } نهائيًا؟ لا يمكن التراجع عن هذا الإجراء.
|
permanently-delete-warning = سيُحذف { $target } نهائيًا. لا يمكن التراجع عن هذا الإجراء.
|
||||||
replace-warning-operation = أتريد استبداله؟ استبداله سيكتب فوق محتواه.
|
replace-warning-operation = أتريد استبداله؟ استبداله سيكتب فوق محتواه.
|
||||||
original-file = الملف الأصلي
|
original-file = الملف الأصلي
|
||||||
replace-with = استبدل بـ
|
replace-with = استبدل بـ
|
||||||
|
|
@ -371,3 +371,4 @@ move-to = انقل إلى...
|
||||||
show-recents = مجلد الحديثة في الشريط الجانبي
|
show-recents = مجلد الحديثة في الشريط الجانبي
|
||||||
clear-recents-history = امحُ التأريخ الحديث
|
clear-recents-history = امحُ التأريخ الحديث
|
||||||
copy-path = انسخ المسار
|
copy-path = انسخ المسار
|
||||||
|
mixed = مختلط
|
||||||
|
|
|
||||||
|
|
@ -176,7 +176,7 @@ set-executable-and-launch-description = Chcete povolit spouštění souboru „{
|
||||||
set-and-launch = Povolit a spustit
|
set-and-launch = Povolit a spustit
|
||||||
open-with = Otevřít pomocí
|
open-with = Otevřít pomocí
|
||||||
other = Ostatní
|
other = Ostatní
|
||||||
none = Žádný
|
none = Žádné
|
||||||
icon-size-and-spacing = Velikost a rozestupy ikon
|
icon-size-and-spacing = Velikost a rozestupy ikon
|
||||||
grid-spacing = Rozestupy mřížky
|
grid-spacing = Rozestupy mřížky
|
||||||
deleting =
|
deleting =
|
||||||
|
|
@ -417,3 +417,4 @@ move-to-title = Vyberte cíl přesunutí
|
||||||
show-recents = Složka „Nedávné“ v postranním panelu
|
show-recents = Složka „Nedávné“ v postranním panelu
|
||||||
copy-path = Kopírovat cestu
|
copy-path = Kopírovat cestu
|
||||||
clear-recents-history = Vymazat historii „Nedávné“
|
clear-recents-history = Vymazat historii „Nedávné“
|
||||||
|
mixed = Různé
|
||||||
|
|
|
||||||
|
|
@ -330,7 +330,7 @@ light = Hell
|
||||||
|
|
||||||
type-to-search = Zum Suchen tippen
|
type-to-search = Zum Suchen tippen
|
||||||
type-to-search-recursive = Durchsucht den aktuellen Ordner und alle Unterordner
|
type-to-search-recursive = Durchsucht den aktuellen Ordner und alle Unterordner
|
||||||
type-to-search-enter-path = Gibt den Pfad zu einem Verzeichnis oder einer Datei ein
|
type-to-search-enter-path = Gib den Pfad zum Verzeichnis oder zur Datei ein
|
||||||
# Kontextmenü
|
# Kontextmenü
|
||||||
add-to-sidebar = Zur Seitenleiste hinzufügen
|
add-to-sidebar = Zur Seitenleiste hinzufügen
|
||||||
compress = Komprimieren...
|
compress = Komprimieren...
|
||||||
|
|
@ -376,9 +376,9 @@ select-all = Alles auswählen
|
||||||
|
|
||||||
## Ansicht
|
## Ansicht
|
||||||
|
|
||||||
zoom-in = Vergrößern
|
zoom-in = Hineinzoomen
|
||||||
default-size = Standardgröße
|
default-size = Standardgröße
|
||||||
zoom-out = Verkleinern
|
zoom-out = Herauszoomen
|
||||||
view = Ansicht
|
view = Ansicht
|
||||||
grid-view = Rasteransicht
|
grid-view = Rasteransicht
|
||||||
list-view = Listenansicht
|
list-view = Listenansicht
|
||||||
|
|
@ -390,7 +390,7 @@ menu-about = Über COSMIC Dateien...
|
||||||
|
|
||||||
## Sortieren
|
## Sortieren
|
||||||
|
|
||||||
sort = Sortieren
|
sort = Sortierung
|
||||||
sort-a-z = A-Z
|
sort-a-z = A-Z
|
||||||
sort-z-a = Z-A
|
sort-z-a = Z-A
|
||||||
sort-newest-first = Neueste zuerst
|
sort-newest-first = Neueste zuerst
|
||||||
|
|
@ -424,3 +424,8 @@ clear-recents-history = Verlauf zuletzt verwendeter Elemente leeren
|
||||||
comment = Dateimanager für den COSMIC Desktop
|
comment = Dateimanager für den COSMIC Desktop
|
||||||
keywords = Ordner;Manager;
|
keywords = Ordner;Manager;
|
||||||
move-to-button-label = Verschieben
|
move-to-button-label = Verschieben
|
||||||
|
move-to-title = Verschiebeziel auswählen
|
||||||
|
remove-from-recents = Aus den zuletzt verwendeten Elementen entfernen
|
||||||
|
move-to = Verschieben nach...
|
||||||
|
copy-path = Pfad kopieren
|
||||||
|
mixed = Gemischt
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@ no-results = No se encontraron resultados
|
||||||
filesystem = Sistema de archivos
|
filesystem = Sistema de archivos
|
||||||
home = Inicio
|
home = Inicio
|
||||||
networks = Redes
|
networks = Redes
|
||||||
notification-in-progress = Las operaciones de archivo están en progreso.
|
notification-in-progress = Las operaciones de archivo están en progreso
|
||||||
trash = Papelera
|
trash = Papelera
|
||||||
recents = Recientes
|
recents = Recientes
|
||||||
undo = Deshacer
|
undo = Deshacer
|
||||||
|
|
|
||||||
0
i18n/eu/cosmic_files.ftl
Normal file
0
i18n/eu/cosmic_files.ftl
Normal file
|
|
@ -5,7 +5,7 @@ no-results = Ei tuloksia
|
||||||
filesystem = Tiedostojärjestelmä
|
filesystem = Tiedostojärjestelmä
|
||||||
home = Koti
|
home = Koti
|
||||||
networks = Verkot
|
networks = Verkot
|
||||||
notification-in-progress = Tiedostotoimintoja käynnissä.
|
notification-in-progress = Tiedostotoimintoja käynnissä
|
||||||
trash = Roskakori
|
trash = Roskakori
|
||||||
recents = Viimeaikaiset
|
recents = Viimeaikaiset
|
||||||
undo = Kumoa
|
undo = Kumoa
|
||||||
|
|
@ -16,7 +16,7 @@ today = Tänään
|
||||||
desktop-view-options = Työpöytänäkymän asetukset…
|
desktop-view-options = Työpöytänäkymän asetukset…
|
||||||
show-on-desktop = Näytä työpöydällä
|
show-on-desktop = Näytä työpöydällä
|
||||||
desktop-folder-content = Työpöytäkansion sisältö
|
desktop-folder-content = Työpöytäkansion sisältö
|
||||||
mounted-drives = Tiedostojärjestelmään liitetyt kovalevyt
|
mounted-drives = Liitetyt asemat
|
||||||
trash-folder-icon = Roskakorikansion kuvake
|
trash-folder-icon = Roskakorikansion kuvake
|
||||||
icon-size-and-spacing = Kuvakkeen koko ja välistys
|
icon-size-and-spacing = Kuvakkeen koko ja välistys
|
||||||
icon-size = Kuvakkeen koko
|
icon-size = Kuvakkeen koko
|
||||||
|
|
@ -38,7 +38,7 @@ create-archive = Luo arkisto
|
||||||
## Empty Trash Dialog
|
## Empty Trash Dialog
|
||||||
|
|
||||||
empty-trash = Tyhjennä roskakori
|
empty-trash = Tyhjennä roskakori
|
||||||
empty-trash-warning = Haluatko varmasti tyhjentää koko roskakorin pysyvästi?
|
empty-trash-warning = Roskakorikansion kohteet poistetaan pysyvästi
|
||||||
|
|
||||||
## Mount Error Dialog
|
## Mount Error Dialog
|
||||||
|
|
||||||
|
|
@ -73,8 +73,8 @@ save-file = Tallenna tiedosto
|
||||||
|
|
||||||
## Open With Dialog
|
## Open With Dialog
|
||||||
|
|
||||||
open-with-title = Kuinka haluat avata kohteen "{ $name }"?
|
open-with-title = Miten haluat avata kohteen "{ $name }"?
|
||||||
browse-store = Selaa { $store }
|
browse-store = Selaa { $store }a
|
||||||
|
|
||||||
## Rename Dialog
|
## Rename Dialog
|
||||||
|
|
||||||
|
|
@ -88,15 +88,15 @@ replace-title = "{ $filename }" on jo olemassa tässä sijainnissa
|
||||||
replace-warning = Haluatko korvata sen tallentamallasi kohteella? Korvaaminen ylikirjoittaa kohteen sisällön.
|
replace-warning = Haluatko korvata sen tallentamallasi kohteella? Korvaaminen ylikirjoittaa kohteen sisällön.
|
||||||
replace-warning-operation = Haluatko korvata sen? Korvaaminen ylikirjoittaa sen sisällön.
|
replace-warning-operation = Haluatko korvata sen? Korvaaminen ylikirjoittaa sen sisällön.
|
||||||
original-file = Alkuperäinen tiedosto
|
original-file = Alkuperäinen tiedosto
|
||||||
replace-with = Korvaa kohteella
|
replace-with = Korvaa käyttäen
|
||||||
apply-to-all = Sovella kaikkiin
|
apply-to-all = Toteuta kaikkiin
|
||||||
keep-both = Pidä molemmat
|
keep-both = Pidä molemmat
|
||||||
skip = Ohita
|
skip = Ohita
|
||||||
|
|
||||||
## Set as Executable and Launch Dialog
|
## Set as Executable and Launch Dialog
|
||||||
|
|
||||||
set-executable-and-launch = Aseta käynnistettäväksi ja käynnistä
|
set-executable-and-launch = Aseta käynnistettäväksi ja käynnistä
|
||||||
set-executable-and-launch-description = Haluatko asettaa kohteen "{ $name }" käynnistettväksi ja käynnistää sen?
|
set-executable-and-launch-description = Haluatko asettaa kohteen "{ $name }" käynnistettäväksi ja käynnistää sen?
|
||||||
set-and-launch = Aseta ja käynnistä
|
set-and-launch = Aseta ja käynnistä
|
||||||
|
|
||||||
## Metadata Dialog
|
## Metadata Dialog
|
||||||
|
|
@ -116,9 +116,9 @@ other = Muut
|
||||||
add-network-drive = Lisää verkkolevy
|
add-network-drive = Lisää verkkolevy
|
||||||
connect = Yhdistä
|
connect = Yhdistä
|
||||||
connect-anonymously = Yhdistä nimettömästi
|
connect-anonymously = Yhdistä nimettömästi
|
||||||
connecting = Yhdistää…
|
connecting = Yhdistetään…
|
||||||
domain = Verkkotunnus
|
domain = Verkkotunnus
|
||||||
enter-server-address = Syötä palvelimen osoite
|
enter-server-address = Kirjoita palvelimen osoite
|
||||||
network-drive-description =
|
network-drive-description =
|
||||||
Palvelinosoitteet sisältävät protokollaetuliitteen sekä osoitteen.
|
Palvelinosoitteet sisältävät protokollaetuliitteen sekä osoitteen.
|
||||||
Esimerkkejä: ssh://192.168.0.1, ftp://[2001:db8::1]
|
Esimerkkejä: ssh://192.168.0.1, ftp://[2001:db8::1]
|
||||||
|
|
@ -126,91 +126,91 @@ network-drive-description =
|
||||||
### Make sure to keep the comma which separates the columns
|
### Make sure to keep the comma which separates the columns
|
||||||
|
|
||||||
network-drive-schemes =
|
network-drive-schemes =
|
||||||
Saatavissa olevat protokollat,Etuliite
|
Saatavilla olevat yhteyskäytännöt,Etuliite
|
||||||
AppleTalk,afp://
|
AppleTalk,afp://
|
||||||
File Transfer Protocol,ftp:// or ftps://
|
File Transfer Protocol,ftp:// tai ftps://
|
||||||
Network File System,nfs://
|
Network File System,nfs://
|
||||||
Server Message Block,smb://
|
Server Message Block,smb://
|
||||||
SSH File Transfer Protocol,sftp:// or ssh://
|
SSH File Transfer Protocol,sftp:// tai ssh://
|
||||||
WebDav,dav:// or davs://
|
WebDav,dav:// tai davs://
|
||||||
network-drive-error = Verkkolevy saavuttamattomissa
|
network-drive-error = Verkkolevy ei saatavilla
|
||||||
password = Salasana
|
password = Salasana
|
||||||
remember-password = Muista salasana
|
remember-password = Muista salasana
|
||||||
try-again = Yritä uudelleen
|
try-again = Yritä uudelleen
|
||||||
username = Käyttäjänimi
|
username = Käyttäjätunnus
|
||||||
|
|
||||||
## Operations
|
## Operations
|
||||||
|
|
||||||
edit-history = Muokkaa historiaa
|
edit-history = Muokkaa historiaa
|
||||||
history = Historia
|
history = Historia
|
||||||
no-history = Historia on tyhjä.
|
no-history = Historia on tyhjä.
|
||||||
pending = Odottaa käsittelyä
|
pending = Jonossa
|
||||||
failed = Epäonnistui
|
failed = Epäonnistuneet
|
||||||
complete = Valmis
|
complete = Valmiit
|
||||||
compressing =
|
compressing =
|
||||||
Tiivistetään { $items } { $items ->
|
Pakataan { $items } { $items ->
|
||||||
[one] kohde
|
[one] kohde
|
||||||
*[other] kohteita
|
*[other] kohdetta
|
||||||
} lähteestä "{ $from }" arkistoon "{ $to }"
|
} sijainnista "{ $from }" arkistoon "{ $to }" ({ $progress })…
|
||||||
compressed =
|
compressed =
|
||||||
Tiivistetty { $items } { $items ->
|
Pakattu { $items } { $items ->
|
||||||
[one] kohde
|
[one] kohde
|
||||||
*[other] kohteet
|
*[other] kohdetta
|
||||||
} lähteestä "{ $from }" arkistoon "{ $to }"
|
} sijainnista "{ $from }" arkistoon "{ $to }"
|
||||||
copy_noun = Kopio
|
copy_noun = Kopio
|
||||||
creating = Luodaan kohdetta "{ $name }" kohteen "{ $parent }" alle
|
creating = Luodaan "{ $name }" kohteen "{ $parent }" alle
|
||||||
created = Luotu kohde "{ $name }" kohteen "{ $parent }" alle
|
created = Luotu "{ $name }" kohteen "{ $parent }" alle
|
||||||
copying =
|
copying =
|
||||||
Kopioidaan { $items } { $items ->
|
Kopioidaan { $items } { $items ->
|
||||||
[one] kohde
|
[one] kohde
|
||||||
*[other] kohteita
|
*[other] kohdetta
|
||||||
} lähteestä "{ $from }" kohteeseen "{ $to }"
|
} sijainnista "{ $from }" kohteeseen "{ $to }" ({ $progress })…
|
||||||
copied =
|
copied =
|
||||||
Kopioitu { $items } { $items ->
|
Kopioitu { $items } { $items ->
|
||||||
[one] kohde
|
[one] kohde
|
||||||
*[other] kohteet
|
*[other] kohdetta
|
||||||
} lähteestä "{ $from }" kohteeseen "{ $to }"
|
} sijainnista "{ $from }" kohteeseen "{ $to }"
|
||||||
emptying-trash = Tyhjennetään { trash }
|
emptying-trash = Tyhjennetään { trash } ({ $progress })…
|
||||||
emptied-trash = Tyhjennetty { trash }
|
emptied-trash = Tyhjennetty { trash }
|
||||||
extracting =
|
extracting =
|
||||||
Puretaan { $items } { $items ->
|
Puretaan { $items } { $items ->
|
||||||
[one] kohde
|
[one] kohde
|
||||||
*[other] kohteet
|
*[other] kohdetta
|
||||||
} arkistosta "{ $from }" kohteeseen "{ $to }"
|
} arkistosta "{ $from }" kohteeseen "{ $to }" ({ $progress })…
|
||||||
extracted =
|
extracted =
|
||||||
Purettu { $items } { $items ->
|
Purettu { $items } { $items ->
|
||||||
[one] kohde
|
[one] kohde
|
||||||
*[other] kohteet
|
*[other] kohdetta
|
||||||
} arkistosta "{ $from }" kohteeseen "{ $to }"
|
} arkistosta "{ $from }" kohteeseen "{ $to }"
|
||||||
setting-executable-and-launching = Asetetaan "{ $name }" käynnistettäväksi ja käynnistetään
|
setting-executable-and-launching = Asetetaan "{ $name }" käynnistettäväksi ja käynnistetään
|
||||||
set-executable-and-launched = Asetettu "{ $name }" käynnistettäväksi ja käynnistetty
|
set-executable-and-launched = Asetettu "{ $name }" käynnistettäväksi ja käynnistetty
|
||||||
moving =
|
moving =
|
||||||
Siirretään { $items } { $items ->
|
Siirretään { $items } { $items ->
|
||||||
[one] kohde
|
[one] kohde
|
||||||
*[other] kohteet
|
*[other] kohdetta
|
||||||
} lähteestä "{ $from }" kohteeseen "{ $to }"
|
} sijainnista "{ $from }" kohteeseen "{ $to }" ({ $progress })…
|
||||||
moved =
|
moved =
|
||||||
Siirretty { $items } { $items ->
|
Siirretty { $items } { $items ->
|
||||||
[one] kohde
|
[one] kohde
|
||||||
*[other] kohteet
|
*[other] kohdetta
|
||||||
} lähteestä "{ $from }" kohteeseen "{ $to }"
|
} sijainnista "{ $from }" kohteeseen "{ $to }"
|
||||||
renaming = Nimetään kohde "{ $from }" muotoon "{ $to }"
|
renaming = Nimetään kohde "{ $from }" muotoon "{ $to }"
|
||||||
renamed = Nimetty kohde "{ $from }" muotoon "{ $to }"
|
renamed = Nimetty kohde "{ $from }" muotoon "{ $to }"
|
||||||
restoring =
|
restoring =
|
||||||
Palautetaan { $items } { $items ->
|
Palautetaan { $items } { $items ->
|
||||||
[one] kohde
|
[one] kohde
|
||||||
*[other] kohteet
|
*[other] kohdetta
|
||||||
} { trash }sta
|
} roskakorista ({ $progress })…
|
||||||
restored =
|
restored =
|
||||||
Palautettu { $items } { $items ->
|
Palautettu { $items } { $items ->
|
||||||
[one] kohde
|
[one] kohde
|
||||||
*[other] kohteet
|
*[other] kohdetta
|
||||||
} { trash }sta
|
} roskakorista
|
||||||
unknown-folder = Tuntematon kansio
|
unknown-folder = tuntematon kansio
|
||||||
|
|
||||||
## Open with
|
## Open with
|
||||||
|
|
||||||
menu-open-with = Avaa ohjelmalla…
|
menu-open-with = Avaa sovelluksella…
|
||||||
default-app = { $name } (oletus)
|
default-app = { $name } (oletus)
|
||||||
|
|
||||||
## Show details
|
## Show details
|
||||||
|
|
@ -225,14 +225,14 @@ settings = Asetukset
|
||||||
|
|
||||||
appearance = Ulkoasu
|
appearance = Ulkoasu
|
||||||
theme = Teema
|
theme = Teema
|
||||||
match-desktop = Sovita yhteen työpöydän kanssa
|
match-desktop = Sovita työpöytään
|
||||||
dark = Tumma
|
dark = Tumma
|
||||||
light = Vaalea
|
light = Vaalea
|
||||||
|
|
||||||
# Context menu
|
# Context menu
|
||||||
|
|
||||||
add-to-sidebar = Lisää sivupalkkiin
|
add-to-sidebar = Lisää sivupalkkiin
|
||||||
compress = Pakkaa
|
compress = Pakkaa…
|
||||||
extract-here = Pura
|
extract-here = Pura
|
||||||
new-file = Uusi tiedosto…
|
new-file = Uusi tiedosto…
|
||||||
new-folder = Uusi kansio…
|
new-folder = Uusi kansio…
|
||||||
|
|
@ -241,9 +241,9 @@ move-to-trash = Siirrä roskakoriin
|
||||||
restore-from-trash = Palauta roskakorista
|
restore-from-trash = Palauta roskakorista
|
||||||
remove-from-sidebar = Poista sivupalkista
|
remove-from-sidebar = Poista sivupalkista
|
||||||
sort-by-name = Järjestä nimen mukaan
|
sort-by-name = Järjestä nimen mukaan
|
||||||
sort-by-modified = Järjestä muokkauspäivämäärän mukaan
|
sort-by-modified = Järjestä muokkausajan mukaan
|
||||||
sort-by-size = Järjestä koon mukaan
|
sort-by-size = Järjestä koon mukaan
|
||||||
sort-by-trashed = Järjestä poistamispäivämäärän mukaan
|
sort-by-trashed = Järjestä poistamisajan mukaan
|
||||||
|
|
||||||
## Desktop
|
## Desktop
|
||||||
|
|
||||||
|
|
@ -281,9 +281,9 @@ grid-view = Ruudukkonäkymä
|
||||||
list-view = Listanäkymä
|
list-view = Listanäkymä
|
||||||
show-hidden-files = Näytä piilotetut tiedostot
|
show-hidden-files = Näytä piilotetut tiedostot
|
||||||
list-directories-first = Näytä kansiot ensin
|
list-directories-first = Näytä kansiot ensin
|
||||||
gallery-preview = Gallerian esinäkymä
|
gallery-preview = Gallerian esikatselu
|
||||||
menu-settings = Asetukset…
|
menu-settings = Asetukset…
|
||||||
menu-about = Tietoa COSMIC Tiedostoista…
|
menu-about = Tietoa COSMICin tiedostonhallinnasta…
|
||||||
|
|
||||||
## Sort
|
## Sort
|
||||||
|
|
||||||
|
|
@ -318,3 +318,90 @@ move-to-button-label = Siirrä
|
||||||
clear-recents-history = Tyhjennä viimeaikaisten historia
|
clear-recents-history = Tyhjennä viimeaikaisten historia
|
||||||
copy-path = Kopioi polku
|
copy-path = Kopioi polku
|
||||||
dismiss = Hylkää viesti
|
dismiss = Hylkää viesti
|
||||||
|
operations-running =
|
||||||
|
{ $running } { $running ->
|
||||||
|
[one] toiminto
|
||||||
|
*[other] toimintoa
|
||||||
|
} käynnissä ({ $percent } %)...
|
||||||
|
operations-running-finished =
|
||||||
|
{ $running } { $running ->
|
||||||
|
[one] toiminto
|
||||||
|
*[other] toimintoa
|
||||||
|
} käynnissä ({ $percent } %), { $finished } valmistunut…
|
||||||
|
pause = Keskeytä
|
||||||
|
extract-to = Pura sijaintiin…
|
||||||
|
permanently-delete-warning = { $target } tullaan poistamaan pysyvästi. Tätä toimintoa ei voi perua.
|
||||||
|
execute-only = Vain suoritus
|
||||||
|
write-only = Vain kirjoitus
|
||||||
|
write-execute = Kirjoita ja suorita
|
||||||
|
read-only = Vain luku
|
||||||
|
read-execute = Lue ja suorita
|
||||||
|
read-write = Lue ja kirjoita
|
||||||
|
read-write-execute = Lue, kirjoita sekä suorita
|
||||||
|
calculating = Lasketaan…
|
||||||
|
single-click = Yhden napsautuksen avaus
|
||||||
|
type-to-search = Kirjoita etsiäksesi
|
||||||
|
type-to-search-recursive = Etsii nykyisestä kansiosta ja kaikista alikansioista
|
||||||
|
remove-from-recents = Poista viimeaikaisista
|
||||||
|
selected-items = { $items } valittua kohdetta
|
||||||
|
show-recents = Viimeaikaisten kansio sivupalkissa
|
||||||
|
copy-to = Kopioi…
|
||||||
|
move-to = Siirrä…
|
||||||
|
details = Yksityiskohdat
|
||||||
|
grid-spacing = Ruudukkovälit
|
||||||
|
none = Ei mitään
|
||||||
|
favorite-path-error = Virhe avattaessa kansiota
|
||||||
|
favorite-path-error-description =
|
||||||
|
Polun { $path } avaaminen ei onnistunut
|
||||||
|
"{ $path }" ei välttämättä ole olemassa tai oikeutesi eivät riitä sen avaamiseen
|
||||||
|
|
||||||
|
Haluatko poistaa sen sivupalkista?
|
||||||
|
keep = Pidä
|
||||||
|
repository = Tietovarasto
|
||||||
|
support = Tuki
|
||||||
|
progress = { $percent } %
|
||||||
|
progress-cancelled = { $percent } %, peruttu
|
||||||
|
progress-failed = { $percent } %, epäonnistui
|
||||||
|
progress-paused = { $percent } %, keskeytetty
|
||||||
|
setting-permissions = Asetetaan kohteen "{ $name }" käyttöoikeudeksi { $mode }
|
||||||
|
set-permissions = Asetettu kohteen { $name } käyttöoikeudeksi { $mode }
|
||||||
|
permanently-deleting =
|
||||||
|
Poistetaan pysyvästi { $items } { $items ->
|
||||||
|
[one] kohde
|
||||||
|
*[other] kohdetta
|
||||||
|
}
|
||||||
|
permanently-deleted =
|
||||||
|
Poistettu pysyvästi { $items } { $items ->
|
||||||
|
[one] kohde
|
||||||
|
*[other] kohdetta
|
||||||
|
}
|
||||||
|
items = Kohteita: { $items }
|
||||||
|
item-accessed = Käytetty: { $accessed }
|
||||||
|
type-to-search-enter-path = Kirjoittaa polun kansioon tai tiedostoon
|
||||||
|
eject = Poista asemasta
|
||||||
|
copy-to-title = Valitse mihin kopioidaan
|
||||||
|
move-to-title = Valitse mihin siirretään
|
||||||
|
pasted-image = Liitetty kuva
|
||||||
|
pasted-text = Liitetty teksti
|
||||||
|
pasted-video = Liitetty video
|
||||||
|
type-to-search-select = Valitsee ensimmäisen täsmäävän tiedoston tai kansion
|
||||||
|
deleting =
|
||||||
|
Poistetaan { $items } { $items ->
|
||||||
|
[one] kohde
|
||||||
|
*[other] kohdetta
|
||||||
|
} roskakorista ({ $progress })…
|
||||||
|
deleted =
|
||||||
|
Poistettu { $items } { $items ->
|
||||||
|
[one] kohde
|
||||||
|
*[other] kohdetta
|
||||||
|
} roskakorista
|
||||||
|
removing-from-recents =
|
||||||
|
Poistetaan { $items } { $items ->
|
||||||
|
[one] kohde
|
||||||
|
*[other] kohdetta
|
||||||
|
} viimeaikaisista
|
||||||
|
removed-from-recents =
|
||||||
|
Poistettu { $items } { $items ->
|
||||||
|
[one] kohde
|
||||||
|
*[other] kohdetta
|
||||||
|
} viimeaikaisista
|
||||||
|
|
|
||||||
|
|
@ -436,3 +436,4 @@ keywords = Dossier;Gestionnaire;
|
||||||
show-recents = Dossier Récents dans la barre latérale
|
show-recents = Dossier Récents dans la barre latérale
|
||||||
copy-path = Copier le chemin
|
copy-path = Copier le chemin
|
||||||
clear-recents-history = Effacer l'historique des Récents
|
clear-recents-history = Effacer l'historique des Récents
|
||||||
|
mixed = Mixte
|
||||||
|
|
|
||||||
|
|
@ -433,3 +433,4 @@ keywords = Fillteán;Bainisteoir;
|
||||||
show-recents = Fillteán le déanaí sa bharra taoibh
|
show-recents = Fillteán le déanaí sa bharra taoibh
|
||||||
clear-recents-history = Glan stair na n-earraí le déanaí
|
clear-recents-history = Glan stair na n-earraí le déanaí
|
||||||
copy-path = Cóipeáil an chosán
|
copy-path = Cóipeáil an chosán
|
||||||
|
mixed = Measctha
|
||||||
|
|
|
||||||
|
|
@ -436,3 +436,4 @@ move-to = Áthelyezés ide…
|
||||||
show-recents = Legutóbbiak mappa megjelenítése az oldalsávban
|
show-recents = Legutóbbiak mappa megjelenítése az oldalsávban
|
||||||
copy-path = Útvonal másolása
|
copy-path = Útvonal másolása
|
||||||
clear-recents-history = Legutóbbiak előzményének törlése
|
clear-recents-history = Legutóbbiak előzményének törlése
|
||||||
|
mixed = Vegyes
|
||||||
|
|
|
||||||
|
|
@ -318,3 +318,4 @@ comment = Pengelola berkas untuk desktop COSMIC
|
||||||
show-recents = Map terbaru di bilah sisi
|
show-recents = Map terbaru di bilah sisi
|
||||||
clear-recents-history = Bersihkan riwayat Terbaru
|
clear-recents-history = Bersihkan riwayat Terbaru
|
||||||
copy-path = Salin jalur
|
copy-path = Salin jalur
|
||||||
|
mixed = Bercampur
|
||||||
|
|
|
||||||
|
|
@ -1 +1,322 @@
|
||||||
change-wallpaper = Beddel aɣrab n ugdil…
|
change-wallpaper = Beddel aɣrab n ugdil…
|
||||||
|
cosmic-files = Ifuyla COSMIC
|
||||||
|
empty-folder = Akaram d ilem
|
||||||
|
empty-folder-hidden = Akaram d ilem (yesɛa iferdisen yeffren)
|
||||||
|
no-results = Ulac igmaḍ yettwafen
|
||||||
|
home = Agejdan
|
||||||
|
networks = Iẓeḍwa
|
||||||
|
notification-in-progress = Timhalin ɣef ifuyla la tteddunt
|
||||||
|
trash = Iḍumman
|
||||||
|
recents = Melmi kan
|
||||||
|
undo = Ssemmet
|
||||||
|
today = Ass-a
|
||||||
|
desktop-view-options = Iɣewwaṛen n tmeẓri n tnarit…
|
||||||
|
show-on-desktop = Sken deg tnarit
|
||||||
|
desktop-folder-content = Agbur n ukaram n tnarit
|
||||||
|
trash-folder-icon = Tignit n ukaram n iḍumman
|
||||||
|
icon-size-and-spacing = Tiddi n tignit akked tallunt
|
||||||
|
icon-size = Tiddi n tignit
|
||||||
|
name = Isem
|
||||||
|
modified = Ittusnifel
|
||||||
|
trashed-on = Yettwakkes ɣer tqecwalt n yiḍumman
|
||||||
|
size = Tiddi
|
||||||
|
details = Talqayt
|
||||||
|
pause = Serǧu
|
||||||
|
resume = Kemmel
|
||||||
|
create-archive = Snulfu-d aɣbaṛ
|
||||||
|
extract-to = Ssef ɣer...
|
||||||
|
extract-to-title = Ssef ɣer ukaram
|
||||||
|
empty-trash = Silem iḍumman
|
||||||
|
rename-folder = Snifel isem n ukaram
|
||||||
|
filesystem = Anagraw n yifuyla
|
||||||
|
dismiss = Zgel izen
|
||||||
|
empty-trash-title = Silem iḍumman?
|
||||||
|
empty-trash-warning = Iferdisen n ukaram n iḍumman ad ttwakksen i lebda
|
||||||
|
create-new-file = Snulfu-d afaylu amaynut
|
||||||
|
create-new-folder = Snulfu-d akaram amaynut
|
||||||
|
file-name = Isem n ufaylu
|
||||||
|
folder-name = Isem n ukaram
|
||||||
|
file-already-exists = Afaylu s yisem-agi yella yakan
|
||||||
|
folder-already-exists = Akaram s yisem-agi yella yakan
|
||||||
|
name-hidden = Ismawen ibeddun s "." ad ttwaffren
|
||||||
|
name-invalid = Isem ur yezmir ara ad yili "{ $filename }"
|
||||||
|
cancel = Sefsex
|
||||||
|
create = Snulfu-d
|
||||||
|
open = Ldi
|
||||||
|
open-file = Ldi afaylu
|
||||||
|
open-folder = Ldi akaram
|
||||||
|
open-in-new-tab = Ldi deg yiccer amaynut
|
||||||
|
open-in-new-window = Ldi deg usfaylu amaynut
|
||||||
|
open-item-location = Ldi adig n uferdis
|
||||||
|
open-multiple-files = Ldi aget n ifuyla
|
||||||
|
mounted-drives = imeɣriyen yettuserkben
|
||||||
|
mount-error = Ulamek anekcum ɣer umeɣri
|
||||||
|
operations-running =
|
||||||
|
{ $running } { $running ->
|
||||||
|
[one] n temhelt la tteddu
|
||||||
|
*[other] n temhal la tteddunt
|
||||||
|
} ({ $percent }%)...
|
||||||
|
operations-running-finished =
|
||||||
|
{ $running } { $running ->
|
||||||
|
[one] n temhelt la tteddu
|
||||||
|
*[other] n temhal la tteddunt
|
||||||
|
} ({ $percent }%), { $finished } { $finished ->
|
||||||
|
[one] tfukk
|
||||||
|
*[other] fukkent
|
||||||
|
}...
|
||||||
|
copy-to-title = Fren taɣerwaḍt n unɣel
|
||||||
|
copy-to-button-label = Nɣel
|
||||||
|
move-to-title = Fren taɣerwaḍt n usmutti
|
||||||
|
move-to-button-label = Smutti
|
||||||
|
comment = Amsefrak n yifuyla i tnarit COSMIC
|
||||||
|
keywords = Akaram;Amsefrak;
|
||||||
|
delete = kkes
|
||||||
|
replace = Semselsi
|
||||||
|
support = Tallalt
|
||||||
|
settings = Iɣewwaṛen
|
||||||
|
appearance = Timeẓri
|
||||||
|
theme = Asentel
|
||||||
|
dark = Aɣmayan
|
||||||
|
light = Aceɛlal
|
||||||
|
paste = Senteḍ
|
||||||
|
select-all = Fren akk
|
||||||
|
zoom-in = Asemɣeṛ
|
||||||
|
default-size = Tiddi tamezwert
|
||||||
|
zoom-out = Asemẓi
|
||||||
|
view = Wali
|
||||||
|
menu-settings = Iɣewwaṛen…
|
||||||
|
save = Sekles
|
||||||
|
match-desktop = Amṣada d tnarit
|
||||||
|
file = Afaylu
|
||||||
|
new-window = Asfaylu amaynut
|
||||||
|
quit = Tuffɣa
|
||||||
|
edit = Ẓreg
|
||||||
|
cut = Gzem
|
||||||
|
copy = Nɣel
|
||||||
|
repository = Asarsay
|
||||||
|
pasted-image = Tettwasenṭeḍ tugna
|
||||||
|
pasted-text = Yettwasenṭeḍ uḍris
|
||||||
|
pasted-video = Tettwasenṭeḍ tvidyut
|
||||||
|
compress = Skussem…
|
||||||
|
grid-spacing = Tallunt n iẓiki
|
||||||
|
extract-password-required = Awal uffir yettwasra
|
||||||
|
permanently-delete-question = Kkes s wudem imezgi?
|
||||||
|
permanently-delete-warning = { $target } ad yettwakkes s wudem imezgi. Tigawt-agi ur tezmir ara ad tettwasefsex.
|
||||||
|
rename-file = Snifel isem n ufaylu
|
||||||
|
replace-title = "{ $filename }" yella yakan deg wadig-a
|
||||||
|
replace-warning = Tebɣiḍ ad t-tsemselsiḍ s win ara teskelseḍ? Asemselsi-ines ad yaru sennig ugbur-is.
|
||||||
|
replace-warning-operation = Tebɣiḍ ad t-tsemselsiḍ? Asemselsi-ines ad yaru sennig ugbur-is.
|
||||||
|
original-file = Afaylu aneṣli
|
||||||
|
replace-with = Semselsi s
|
||||||
|
apply-to-all = Snes i meṛṛa
|
||||||
|
keep-both = Eǧǧ-iten deg sin
|
||||||
|
skip = Zgel
|
||||||
|
set-executable-and-launch = Sbeddet am umselkam syinna senker
|
||||||
|
set-executable-and-launch-description = Tebɣiḍ ad tesbeddeḍ "{ $name }" am umselkam syinna ad tessenkreḍ?
|
||||||
|
set-and-launch = Sbadu sakin senker
|
||||||
|
add-network-drive = Rnu ameɣri n uẓeṭṭa
|
||||||
|
connect = Qqen
|
||||||
|
connect-anonymously = Qqen s wudem udrig
|
||||||
|
connecting = Tuqqna…
|
||||||
|
domain = Taɣult
|
||||||
|
enter-server-address = Sekcem tansa n uqeddac
|
||||||
|
network-drive-description =
|
||||||
|
Tansiwin n uqeddac gebrent azwir n uneggaf akked tansa.
|
||||||
|
Imedyaten: ssh://192.168.0.1, ftp://[2001:db8::1]
|
||||||
|
network-drive-error = Ur izmir ara ad yekcem ɣer umeɣri n uzeṭṭa
|
||||||
|
password = Awal uffir
|
||||||
|
remember-password = Cfu ɣef wawal uffir
|
||||||
|
try-again = ɛreḍ tikelt nniḍen
|
||||||
|
username = Isem n useqdac
|
||||||
|
cancelled = Yettwasefsex
|
||||||
|
edit-history = Ẓreg amazray
|
||||||
|
history = Amazray
|
||||||
|
no-history = Ulac iferdisen deg umazray.
|
||||||
|
pending = Yettṛaǧu
|
||||||
|
progress-cancelled = { $percent }%, yettwasefsex
|
||||||
|
progress-failed = { $percent }%, ur yeddi ara
|
||||||
|
failed = Ur yeddi ara
|
||||||
|
renaming = Asnifel n yisem "{ $from }" ɣer "{ $to }"
|
||||||
|
renamed = Yettwasenfel yisem n "{ $from }" ɣer "{ $to }"
|
||||||
|
unknown-folder = akaram arussin
|
||||||
|
menu-open-with = Ldi s…
|
||||||
|
default-app = { $name } (amezwer)
|
||||||
|
show-details = Sken talqayt
|
||||||
|
type = Anaw: { $mime }
|
||||||
|
items = Iferdisen: { $items }
|
||||||
|
item-size = Tiddi: { $size }
|
||||||
|
item-created = Yettwarna: { $created }
|
||||||
|
item-modified = Ittusnifel: { $modified }
|
||||||
|
item-accessed = Yettwakcem: { $accessed }
|
||||||
|
calculating = Asiḍen…
|
||||||
|
single-click = Asiti asuf i ulday
|
||||||
|
type-to-search = Aru iwakken ad tnadiḍ
|
||||||
|
type-to-search-recursive = Nadi akaram amiran akked ikaramen inaddawen meṛṛa
|
||||||
|
type-to-search-enter-path = Sekcem abrid ɣer ukaram neɣ afaylu
|
||||||
|
add-to-sidebar = Rnu ɣer ufeggag adisan
|
||||||
|
delete-permanently = Kkes i lebda
|
||||||
|
grid-view = Askan s iẓiki
|
||||||
|
list-view = Askan s tebdart
|
||||||
|
show-hidden-files = Sken ifuyla uffiren
|
||||||
|
gallery-preview = Taskant n temidelt
|
||||||
|
menu-about = Ɣef Ifuyla COSMIC…
|
||||||
|
sort = Asmizzwer
|
||||||
|
sort-newest-first = Amaynut d amezwaru
|
||||||
|
sort-oldest-first = Aqbur d amezwaru
|
||||||
|
sort-smallest-to-largest = Seg umeẓyan akk ɣer umeqqran
|
||||||
|
sort-largest-to-smallest = Seg umeqqran akk ɣer umeẓyan
|
||||||
|
open-multiple-folders = Ldi usgit n ikaramen
|
||||||
|
save-file = Sekles afaylu
|
||||||
|
open-with-title = Amek tebɣiḍ ad teldiḍ "{ $name }"?
|
||||||
|
browse-store = Snirem { $store }
|
||||||
|
other-apps = Isnasen-nniḍen
|
||||||
|
emptying-trash = Silem { trash } ({ $progress })…
|
||||||
|
emptied-trash = D ilem { trash }
|
||||||
|
set-permissions = Sbadu isirigen i "{ $name }" ɣer { $mode }
|
||||||
|
eject = Ḍeqqer
|
||||||
|
extract-here = Ssef
|
||||||
|
new-file = Afaylu amaynut…
|
||||||
|
new-folder = Akaram amaynut…
|
||||||
|
open-in-terminal = Ldi deg yixef
|
||||||
|
move-to-trash = Smutti ɣer tqecwalt n yiḍumman
|
||||||
|
restore-from-trash = Err-d seg tqecwalt n yiḍumman
|
||||||
|
remove-from-sidebar = Kkes seg ugalis adisan
|
||||||
|
sort-by-name = Smizzwer s yisem
|
||||||
|
sort-by-modified = Asmizzwer s usnifel
|
||||||
|
sort-by-size = Asmizzwer s tiddi
|
||||||
|
sort-by-trashed = Asmizzwer s wakud n tukksa
|
||||||
|
remove-from-recents = Kkes seg ineggura
|
||||||
|
desktop-appearance = Timeẓri n tnarit…
|
||||||
|
display-settings = Iɣewwaṛen n ubeqqeḍ...
|
||||||
|
new-tab = Iccer amaynut
|
||||||
|
reload-folder = Ales asali n ukaram
|
||||||
|
rename = Snifel isem...
|
||||||
|
close-tab = Mdel iccer
|
||||||
|
list-directories-first = Sken di tazwara ikaramen
|
||||||
|
open-with = Ldi s
|
||||||
|
owner = Bab
|
||||||
|
group = Agraw
|
||||||
|
other = Ayen nniḍen
|
||||||
|
none = Ula Yiwen
|
||||||
|
execute-only = Selkem kan
|
||||||
|
write-only = Aru kan
|
||||||
|
write-execute = Aru u selkem
|
||||||
|
read-only = I tɣuri kan
|
||||||
|
read-execute = Ɣeṛ u selkem
|
||||||
|
read-write = Ɣeṛ u aru
|
||||||
|
read-write-execute = Ɣeṛ, aru, u selkem
|
||||||
|
favorite-path-error = Tuccḍa deg ulday n ukaram
|
||||||
|
remove = Kkes
|
||||||
|
keep = Eǧǧ
|
||||||
|
progress = { $percent }%
|
||||||
|
progress-paused = { $percent }%, ibedd
|
||||||
|
complete = Immed
|
||||||
|
copy_noun = Nɣel
|
||||||
|
creating = Asnulfu n "{ $name }" deg "{ $parent }"
|
||||||
|
created = Yettwarna "{ $name }" deg "{ $parent }"
|
||||||
|
setting-executable-and-launching = Asbadu n "{ $name }" am umselkam syin ad yettwasekker
|
||||||
|
set-executable-and-launched = Sbadu "{ $name }" am umselkam syin sekker-it
|
||||||
|
setting-permissions = Asbadu n tsirag i "{ $name }" ɣer { $mode }
|
||||||
|
related-apps = Isnasen icudden
|
||||||
|
favorite-path-error-description =
|
||||||
|
Ur izmir ara ad yeldi "{ $path }"
|
||||||
|
"{ $path }" yezmer lḥal ulac-it neɣ ahat ur tesɛiḍ ara tisirag akken ad t-teldiḍ
|
||||||
|
|
||||||
|
Tebɣiḍ ad t-tekkseḍ seg ufeggag adisan?
|
||||||
|
compressing =
|
||||||
|
La issekkussum { $items } { $items ->
|
||||||
|
[one] n uferdis
|
||||||
|
*[other] n yiferdisen
|
||||||
|
} seg "{ $from }" ɣer "{ $to }" ({ $progress })...
|
||||||
|
compressed =
|
||||||
|
Yekussem { $items } { $items ->
|
||||||
|
[one] n uferdis
|
||||||
|
*[other] n yiferdisen
|
||||||
|
} seg "{ $from }" ɣer "{ $to }"
|
||||||
|
copying =
|
||||||
|
Anɣal { $items } { $items ->
|
||||||
|
[one] n uferdis
|
||||||
|
*[other] n yiferdisen
|
||||||
|
} seg "{ $from }" ɣer "{ $to }" ({ $progress })...
|
||||||
|
copied =
|
||||||
|
Yettwanɣel { $items } { $items ->
|
||||||
|
[one] n uferdis
|
||||||
|
*[other] n yiferdisen
|
||||||
|
} seg "{ $from }" ɣer "{ $to }"
|
||||||
|
deleting =
|
||||||
|
Tukksa { $items } { $items ->
|
||||||
|
[one] n uferdis
|
||||||
|
*[other] n yiferdisen
|
||||||
|
} seg { trash } ({ $progress })...
|
||||||
|
deleted =
|
||||||
|
Yettwakkes { $items } { $items ->
|
||||||
|
[one] n uferdis
|
||||||
|
*[other] n yiferdisen
|
||||||
|
} seg { trash }
|
||||||
|
extracting =
|
||||||
|
Tussfa { $items } { $items ->
|
||||||
|
[one] n uferdis
|
||||||
|
*[other] n yiferdisen
|
||||||
|
} seg "{ $from }" ɣer "{ $to }" ({ $progress })...
|
||||||
|
extracted =
|
||||||
|
Yettusef { $items } { $items ->
|
||||||
|
[one] n uferdis
|
||||||
|
*[other] n yiferdisen
|
||||||
|
} seg "{ $from }" ɣer "{ $to }"
|
||||||
|
moving =
|
||||||
|
Asmutti { $items } { $items ->
|
||||||
|
[one] n uferdis
|
||||||
|
*[other] n yiferdisen
|
||||||
|
} seg "{ $from }" ɣer "{ $to }" ({ $progress })...
|
||||||
|
moved =
|
||||||
|
Yettwasenkez { $items } { $items ->
|
||||||
|
[one] n uferdis
|
||||||
|
*[other] n yiferdisen
|
||||||
|
} seg "{ $from }" ɣer "{ $to }"
|
||||||
|
permanently-deleting =
|
||||||
|
Tukksa i lebda { $items } { $items ->
|
||||||
|
[one] n uferdis
|
||||||
|
*[other] n yiferdisen
|
||||||
|
}
|
||||||
|
permanently-deleted =
|
||||||
|
I lebda yettwakkes { $items } { $items ->
|
||||||
|
[one] n uferdis
|
||||||
|
*[other] n yiferdisen
|
||||||
|
}
|
||||||
|
removing-from-recents =
|
||||||
|
Tukksa { $items } { $items ->
|
||||||
|
[one] n uferdis
|
||||||
|
*[other] n yiferdisen
|
||||||
|
} seg { recents }
|
||||||
|
removed-from-recents =
|
||||||
|
Yettwakkes { $items } { $items ->
|
||||||
|
[one] n uferdis
|
||||||
|
*[other] n yiferdisen
|
||||||
|
} seg { recents }
|
||||||
|
restoring =
|
||||||
|
Tiririt { $items } { $items ->
|
||||||
|
[one] n uferdis
|
||||||
|
*[other] n yiferdisen
|
||||||
|
} seg { trash } ({ $progress })…
|
||||||
|
restored =
|
||||||
|
Yettwarr-d { $items } { $items ->
|
||||||
|
[one] n uferdis
|
||||||
|
*[other] n yiferdisen
|
||||||
|
} seg { trash }
|
||||||
|
network-drive-schemes =
|
||||||
|
Ineggafen iwejden, azwir
|
||||||
|
AppleTalk,afp://
|
||||||
|
Aneggaf n usiweḍ n yifuyla,ftp:// neɣ ftps://
|
||||||
|
Anagraw n yifuyla n uzeṭṭa,nfs://
|
||||||
|
Iḥder n yizen n uqeddac,smb://
|
||||||
|
SSH Aneggaf n usiweḍ n yifuyla,sftp:// neɣ ssh://
|
||||||
|
WebDAV:// neɣ davs://
|
||||||
|
sort-a-z = A-Ẓ
|
||||||
|
sort-z-a = Ẓ-A
|
||||||
|
selected-items = { $items } n yiferdisen yettwafernen
|
||||||
|
type-to-search-select = Fren afaylu amezwaru neɣ akaram yemṣadan
|
||||||
|
copy-to = Nɣel ɣer...
|
||||||
|
move-to = Smutti ɣer…
|
||||||
|
show-recents = Akaram n melmi kan deg ufeggag adisan
|
||||||
|
clear-recents-history = Sfeḍ azray n melmi kan
|
||||||
|
copy-path = Nɣel abrid
|
||||||
|
|
|
||||||
|
|
@ -256,7 +256,7 @@ type-to-search-recursive = Ағымдағы бума мен барлық ішк
|
||||||
type-to-search-enter-path = Бумаға немесе файлға жолды енгізеді
|
type-to-search-enter-path = Бумаға немесе файлға жолды енгізеді
|
||||||
type-to-search-select = Бірінші сәйкес келетін файлды немесе буманы таңдайды
|
type-to-search-select = Бірінші сәйкес келетін файлды немесе буманы таңдайды
|
||||||
add-to-sidebar = Бүйірлік панельге қосу
|
add-to-sidebar = Бүйірлік панельге қосу
|
||||||
compress = Сығу
|
compress = Сығу...
|
||||||
delete-permanently = Біржолата өшіру
|
delete-permanently = Біржолата өшіру
|
||||||
eject = Шығару
|
eject = Шығару
|
||||||
extract-here = Тарқату
|
extract-here = Тарқату
|
||||||
|
|
@ -318,3 +318,4 @@ keywords = Folder;Manager;Бума;Басқарушы;
|
||||||
show-recents = Бүйір панеліндегі «Жуырдағы құжаттар» бумасы
|
show-recents = Бүйір панеліндегі «Жуырдағы құжаттар» бумасы
|
||||||
clear-recents-history = Жуырдағылар тарихын өшіру
|
clear-recents-history = Жуырдағылар тарихын өшіру
|
||||||
copy-path = Орналасқан жолын көшіру
|
copy-path = Орналасқан жолын көшіру
|
||||||
|
mixed = Аралас
|
||||||
|
|
|
||||||
|
|
@ -110,7 +110,7 @@ dismiss = 메시지 무시
|
||||||
copy_noun = 복사
|
copy_noun = 복사
|
||||||
progress = { $percent }%
|
progress = { $percent }%
|
||||||
related-apps = 관련 앱
|
related-apps = 관련 앱
|
||||||
compress = 압축
|
compress = 압축...
|
||||||
network-drive-error = 네트워크 드라이브에 접근할 수 없음
|
network-drive-error = 네트워크 드라이브에 접근할 수 없음
|
||||||
icon-size-and-spacing = 아이콘 크기 및 간격
|
icon-size-and-spacing = 아이콘 크기 및 간격
|
||||||
password = 암호
|
password = 암호
|
||||||
|
|
@ -272,3 +272,8 @@ network-drive-schemes =
|
||||||
WebDAV,dav:// 또는 davs://
|
WebDAV,dav:// 또는 davs://
|
||||||
type-to-search-select = 일치하는 첫 번째 파일 또는 폴더를 선택합니다
|
type-to-search-select = 일치하는 첫 번째 파일 또는 폴더를 선택합니다
|
||||||
comment = COSMIC 데스크톱용 위한 파일 관리자
|
comment = COSMIC 데스크톱용 위한 파일 관리자
|
||||||
|
keywords = 폴더;관리자;
|
||||||
|
copy-to-button-label = 복사
|
||||||
|
move-to-button-label = 이동
|
||||||
|
clear-recents-history = 최근 기록 비우기
|
||||||
|
copy-path = 복사 경로
|
||||||
|
|
|
||||||
|
|
@ -252,7 +252,7 @@ type-to-search = Norint ieškoti, pradėkite rašyti
|
||||||
type-to-search-recursive = Paieška dabartiniame aplanke ir jo poaplankiuose
|
type-to-search-recursive = Paieška dabartiniame aplanke ir jo poaplankiuose
|
||||||
type-to-search-enter-path = Įvedamas aplanko ar failo kelias
|
type-to-search-enter-path = Įvedamas aplanko ar failo kelias
|
||||||
add-to-sidebar = Pridėti į šonjuostę
|
add-to-sidebar = Pridėti į šonjuostę
|
||||||
compress = Suspausti
|
compress = Suspausti...
|
||||||
delete-permanently = Ištrinti visam laikui
|
delete-permanently = Ištrinti visam laikui
|
||||||
eject = Išstumti
|
eject = Išstumti
|
||||||
extract-here = Išskleisti
|
extract-here = Išskleisti
|
||||||
|
|
|
||||||
|
|
@ -32,7 +32,7 @@ pause = ਵਿਰਾਮ
|
||||||
resume = ਮੁੜ-ਚਾਲੂ
|
resume = ਮੁੜ-ਚਾਲੂ
|
||||||
create-archive = ਅਕਾਇਵ ਬਣਾਓ
|
create-archive = ਅਕਾਇਵ ਬਣਾਓ
|
||||||
extract-password-required = ਪਾਸਵਰਡ ਚਾਹੀਦਾ ਹੈ
|
extract-password-required = ਪਾਸਵਰਡ ਚਾਹੀਦਾ ਹੈ
|
||||||
extract-to = Extract To...
|
extract-to = ਖਿਲਾਰੋ...
|
||||||
extract-to-title = ਫੋਲਡਰ ਵਿੱਚ ਖਿਲਾਰੋ
|
extract-to-title = ਫੋਲਡਰ ਵਿੱਚ ਖਿਲਾਰੋ
|
||||||
empty-trash = ਰੱਦੀ ਨੂੰ ਖਾਲੀ ਕਰੋ
|
empty-trash = ਰੱਦੀ ਨੂੰ ਖਾਲੀ ਕਰੋ
|
||||||
empty-trash-title = ਰੱਦੀ ਨੂੰ ਖਾਲੀ ਕਰਨਾ ਹੈ?
|
empty-trash-title = ਰੱਦੀ ਨੂੰ ਖਾਲੀ ਕਰਨਾ ਹੈ?
|
||||||
|
|
@ -113,7 +113,7 @@ replace-title = "{ $filename }" ਪਹਿਲਾਂ ਹੀ ਇਸ ਟਿਕਾਣ
|
||||||
favorite-path-error = ਡਾਇਰੈਕਟਰੀ ਖੋਲ੍ਹਣ ਦੌਰਾਨ ਗਲਤੀ
|
favorite-path-error = ਡਾਇਰੈਕਟਰੀ ਖੋਲ੍ਹਣ ਦੌਰਾਨ ਗਲਤੀ
|
||||||
add-network-drive = ਨੈੱਟਵਰਕ ਡਰਾਇਵ ਜੋੜੋ
|
add-network-drive = ਨੈੱਟਵਰਕ ਡਰਾਇਵ ਜੋੜੋ
|
||||||
connect-anonymously = ਅਣਪਛਾਤੇ ਵਜੋਂ ਕਨੈਕਟ ਕਰੋ
|
connect-anonymously = ਅਣਪਛਾਤੇ ਵਜੋਂ ਕਨੈਕਟ ਕਰੋ
|
||||||
connecting = Connecting...
|
connecting = ਕਨੈਕਟ ਕੀਤਾ ਜਾ ਰਿਹਾ ਹੈ...
|
||||||
domain = ਡੋਮੇਨ
|
domain = ਡੋਮੇਨ
|
||||||
enter-server-address = ਸਰਵਰ ਦਾ ਸਿਰਨਾਵਾਂ ਦਿਓ
|
enter-server-address = ਸਰਵਰ ਦਾ ਸਿਰਨਾਵਾਂ ਦਿਓ
|
||||||
password = ਪਾਸਵਰਡ
|
password = ਪਾਸਵਰਡ
|
||||||
|
|
@ -138,9 +138,9 @@ sort-by-modified = ਸੋਧ ਨਾਲ ਲੜੀਬੱਧ
|
||||||
sort-by-size = ਆਕਾਰ ਨਾਲ ਲੜੀਬੱਧ
|
sort-by-size = ਆਕਾਰ ਨਾਲ ਲੜੀਬੱਧ
|
||||||
sort-by-trashed = ਹਟਾਉਣ ਸਮੇਂ ਨਾਲ ਲੜੀਬੱਧ
|
sort-by-trashed = ਹਟਾਉਣ ਸਮੇਂ ਨਾਲ ਲੜੀਬੱਧ
|
||||||
remove-from-recents = ਸੱਜਰਿਆਂ ਵਿੱਚੋਂ ਹਟਾਓ
|
remove-from-recents = ਸੱਜਰਿਆਂ ਵਿੱਚੋਂ ਹਟਾਓ
|
||||||
change-wallpaper = Change wallpaper...
|
change-wallpaper = ਵਾਲਪੇਪਰ ਨੂੰ ਬਦਲੋ...
|
||||||
desktop-appearance = Desktop appearance...
|
desktop-appearance = ਡੈਸਕਟਾਪ ਦੀ ਦਿੱਖ...
|
||||||
display-settings = Display settings...
|
display-settings = ਡਿਸਪਲੇਅ ਸੈਟਿੰਗਾਂ...
|
||||||
file = ਫ਼ਾਇਲ
|
file = ਫ਼ਾਇਲ
|
||||||
new-tab = ਨਵੀਂ ਟੈਬ
|
new-tab = ਨਵੀਂ ਟੈਬ
|
||||||
new-window = ਨਵੀਂ ਵਿੰਡੋ
|
new-window = ਨਵੀਂ ਵਿੰਡੋ
|
||||||
|
|
@ -195,11 +195,11 @@ type-to-search = ਖੋਜਣ ਲਈ ਲਿਖੋ
|
||||||
type-to-search-recursive = ਮੌਜੂਦਾ ਫੋਲਡਰ ਅਤੇ ਸਭ ਅਧੀਨ-ਫੋਲਡਰਾਂ ਵਿੱਚ ਖੋਜੋ
|
type-to-search-recursive = ਮੌਜੂਦਾ ਫੋਲਡਰ ਅਤੇ ਸਭ ਅਧੀਨ-ਫੋਲਡਰਾਂ ਵਿੱਚ ਖੋਜੋ
|
||||||
add-to-sidebar = ਬਾਹੀ ਵਿੱਚ ਜੋੜੋ
|
add-to-sidebar = ਬਾਹੀ ਵਿੱਚ ਜੋੜੋ
|
||||||
compress = ਕੰਪਰੈਸ
|
compress = ਕੰਪਰੈਸ
|
||||||
copy-to = Copy to...
|
copy-to = ਇੱਥੇ ਕਾਪੀ ਕਰੋ...
|
||||||
delete-permanently = ਪੱਕੇ ਤੌਰ ਉੱਤੇ ਹਟਾਓ
|
delete-permanently = ਪੱਕੇ ਤੌਰ ਉੱਤੇ ਹਟਾਓ
|
||||||
eject = ਬਾਹਰ
|
eject = ਬਾਹਰ
|
||||||
extract-here = ਖਿਲਾਰੋ
|
extract-here = ਖਿਲਾਰੋ
|
||||||
move-to = Move to...
|
move-to = ਇੱਥੇ ਭੇਜੋ...
|
||||||
remove-from-sidebar = ਬਾਹੀ ਵਿੱਚੋਂ ਹਟਾਓ
|
remove-from-sidebar = ਬਾਹੀ ਵਿੱਚੋਂ ਹਟਾਓ
|
||||||
reload-folder = ਫੋਲਡਰ ਨੂੰ ਮੁੜ-ਲੋਡ ਕਰੋ
|
reload-folder = ਫੋਲਡਰ ਨੂੰ ਮੁੜ-ਲੋਡ ਕਰੋ
|
||||||
list-view = ਸੂਚੀ ਝਲਕ
|
list-view = ਸੂਚੀ ਝਲਕ
|
||||||
|
|
|
||||||
|
|
@ -437,3 +437,4 @@ move-to = Przenieś do…
|
||||||
show-recents = Ostatnie katalogi w panelu bocznym
|
show-recents = Ostatnie katalogi w panelu bocznym
|
||||||
clear-recents-history = Wyczyść bierzącą historię
|
clear-recents-history = Wyczyść bierzącą historię
|
||||||
copy-path = Skopiuj ścieżkę
|
copy-path = Skopiuj ścieżkę
|
||||||
|
mixed = Mieszane
|
||||||
|
|
|
||||||
|
|
@ -424,8 +424,8 @@ sort-largest-to-smallest = Do maior para o menor
|
||||||
empty-trash-title = Esvaziar a lixeira?
|
empty-trash-title = Esvaziar a lixeira?
|
||||||
type-to-search-select = Seleciona o primeiro arquivo ou pasta correspondente
|
type-to-search-select = Seleciona o primeiro arquivo ou pasta correspondente
|
||||||
pasted-image = Imagem colada
|
pasted-image = Imagem colada
|
||||||
pasted-text = Texto copiado
|
pasted-text = Texto colado
|
||||||
pasted-video = Vídeo copiado
|
pasted-video = Vídeo colado
|
||||||
copy-to-title = Selecione o destino da cópia
|
copy-to-title = Selecione o destino da cópia
|
||||||
copy-to-button-label = Copiar
|
copy-to-button-label = Copiar
|
||||||
move-to-title = Selecione o destino da movimentação
|
move-to-title = Selecione o destino da movimentação
|
||||||
|
|
@ -433,6 +433,7 @@ move-to-button-label = Mover
|
||||||
copy-to = Copiar para...
|
copy-to = Copiar para...
|
||||||
move-to = Mover para...
|
move-to = Mover para...
|
||||||
keywords = Pasta;Gerenciador;
|
keywords = Pasta;Gerenciador;
|
||||||
show-recents = Pasta Recentes na barra lateral
|
show-recents = Pasta de recentes na barra lateral
|
||||||
clear-recents-history = Limpar histórico de recentes
|
clear-recents-history = Limpar histórico de recentes
|
||||||
copy-path = Copiar caminho
|
copy-path = Copiar caminho
|
||||||
|
mixed = Misto
|
||||||
|
|
|
||||||
|
|
@ -195,7 +195,7 @@ dark = Тёмная
|
||||||
light = Светлая
|
light = Светлая
|
||||||
# Context menu
|
# Context menu
|
||||||
add-to-sidebar = Добавить на боковую панель
|
add-to-sidebar = Добавить на боковую панель
|
||||||
compress = Сжать
|
compress = Сжать...
|
||||||
extract-here = Распаковать
|
extract-here = Распаковать
|
||||||
new-file = Новый файл…
|
new-file = Новый файл…
|
||||||
new-folder = Новая папка…
|
new-folder = Новая папка…
|
||||||
|
|
@ -380,3 +380,4 @@ keywords = Папка;Менеджер;
|
||||||
show-recents = «Недавние документы» в бок. панели
|
show-recents = «Недавние документы» в бок. панели
|
||||||
clear-recents-history = Очистить историю недавних
|
clear-recents-history = Очистить историю недавних
|
||||||
copy-path = Копировать путь
|
copy-path = Копировать путь
|
||||||
|
mixed = Смешанные
|
||||||
|
|
|
||||||
|
|
@ -317,7 +317,7 @@ single-click = Відкривати одним клацанням
|
||||||
type-to-search = Введіть для пошуку
|
type-to-search = Введіть для пошуку
|
||||||
type-to-search-recursive = Шукає у поточній теці та всіх підтеках
|
type-to-search-recursive = Шукає у поточній теці та всіх підтеках
|
||||||
type-to-search-enter-path = Вводить шлях до каталогу або файлу
|
type-to-search-enter-path = Вводить шлях до каталогу або файлу
|
||||||
compress = Стиснути
|
compress = Стиснути...
|
||||||
delete-permanently = Остаточно видалити
|
delete-permanently = Остаточно видалити
|
||||||
eject = Безпечно вилучити
|
eject = Безпечно вилучити
|
||||||
extract-here = Видобути
|
extract-here = Видобути
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,7 @@ recents = 最近访问
|
||||||
undo = 撤销
|
undo = 撤销
|
||||||
today = 今天
|
today = 今天
|
||||||
# Desktop view options
|
# Desktop view options
|
||||||
desktop-view-options = 桌面视图选项...
|
desktop-view-options = 桌面视图选项…
|
||||||
show-on-desktop = 在桌面显示
|
show-on-desktop = 在桌面显示
|
||||||
desktop-folder-content = 桌面文件夹内容
|
desktop-folder-content = 桌面文件夹内容
|
||||||
mounted-drives = 已装载驱动器
|
mounted-drives = 已装载驱动器
|
||||||
|
|
@ -31,7 +31,7 @@ operations-running =
|
||||||
正在进行 { $running } { $running ->
|
正在进行 { $running } { $running ->
|
||||||
[one] 个操作
|
[one] 个操作
|
||||||
*[other] 个操作
|
*[other] 个操作
|
||||||
}({ $percent }%)...
|
}({ $percent }%)…
|
||||||
operations-running-finished =
|
operations-running-finished =
|
||||||
正在进行 { $running } { $running ->
|
正在进行 { $running } { $running ->
|
||||||
[one] 个操作
|
[one] 个操作
|
||||||
|
|
@ -50,7 +50,7 @@ create-archive = 创建压缩包
|
||||||
## Extract Dialog
|
## Extract Dialog
|
||||||
|
|
||||||
extract-password-required = 需要密码
|
extract-password-required = 需要密码
|
||||||
extract-to = 提取到...
|
extract-to = 提取到…
|
||||||
extract-to-title = 提取到文件夹
|
extract-to-title = 提取到文件夹
|
||||||
|
|
||||||
## Empty Trash Dialog
|
## Empty Trash Dialog
|
||||||
|
|
@ -317,7 +317,7 @@ unknown-folder = 未知文件夹
|
||||||
|
|
||||||
## Open with
|
## Open with
|
||||||
|
|
||||||
menu-open-with = 打开方式...
|
menu-open-with = 打开方式…
|
||||||
default-app = { $name }(默认)
|
default-app = { $name }(默认)
|
||||||
|
|
||||||
## Show details
|
## Show details
|
||||||
|
|
@ -329,7 +329,7 @@ item-size = 文件大小:{ $size }
|
||||||
item-created = 创建于:{ $created }
|
item-created = 创建于:{ $created }
|
||||||
item-modified = 修改于:{ $modified }
|
item-modified = 修改于:{ $modified }
|
||||||
item-accessed = 访问于:{ $accessed }
|
item-accessed = 访问于:{ $accessed }
|
||||||
calculating = 计算中...
|
calculating = 计算中…
|
||||||
|
|
||||||
## Settings
|
## Settings
|
||||||
|
|
||||||
|
|
@ -355,8 +355,8 @@ compress = 压缩…
|
||||||
delete-permanently = 永久删除
|
delete-permanently = 永久删除
|
||||||
eject = 弹出
|
eject = 弹出
|
||||||
extract-here = 解压到此处
|
extract-here = 解压到此处
|
||||||
new-file = 新建文件...
|
new-file = 新建文件…
|
||||||
new-folder = 新建文件夹...
|
new-folder = 新建文件夹…
|
||||||
open-in-terminal = 在终端模拟器中打开
|
open-in-terminal = 在终端模拟器中打开
|
||||||
move-to-trash = 移动到回收站
|
move-to-trash = 移动到回收站
|
||||||
restore-from-trash = 从回收站中还原
|
restore-from-trash = 从回收站中还原
|
||||||
|
|
@ -369,9 +369,9 @@ remove-from-recents = 从最近访问中移除
|
||||||
|
|
||||||
## Desktop
|
## Desktop
|
||||||
|
|
||||||
change-wallpaper = 更改壁纸...
|
change-wallpaper = 更改壁纸…
|
||||||
desktop-appearance = 桌面外观...
|
desktop-appearance = 桌面外观…
|
||||||
display-settings = 显示设置...
|
display-settings = 显示设置…
|
||||||
|
|
||||||
# Menu
|
# Menu
|
||||||
|
|
||||||
|
|
@ -382,7 +382,7 @@ file = 文件
|
||||||
new-tab = 新建标签
|
new-tab = 新建标签
|
||||||
new-window = 新建窗口
|
new-window = 新建窗口
|
||||||
reload-folder = 刷新文件夹
|
reload-folder = 刷新文件夹
|
||||||
rename = 重命名...
|
rename = 重命名…
|
||||||
close-tab = 关闭标签
|
close-tab = 关闭标签
|
||||||
quit = 退出
|
quit = 退出
|
||||||
|
|
||||||
|
|
@ -405,7 +405,7 @@ list-view = 列表视图
|
||||||
show-hidden-files = 显示隐藏文件
|
show-hidden-files = 显示隐藏文件
|
||||||
list-directories-first = 优先列出目录
|
list-directories-first = 优先列出目录
|
||||||
gallery-preview = 图库预览
|
gallery-preview = 图库预览
|
||||||
menu-settings = 设置...
|
menu-settings = 设置…
|
||||||
menu-about = 关于 COSMIC 文件…
|
menu-about = 关于 COSMIC 文件…
|
||||||
|
|
||||||
## Sort
|
## Sort
|
||||||
|
|
@ -435,4 +435,5 @@ comment = COSMIC 桌面的文件管理器
|
||||||
keywords = 文件夹;管理器;
|
keywords = 文件夹;管理器;
|
||||||
clear-recents-history = 清除最近访问历史
|
clear-recents-history = 清除最近访问历史
|
||||||
copy-path = 复制文件路径
|
copy-path = 复制文件路径
|
||||||
show-recents = 侧边栏显示最近访问
|
show-recents = 侧边栏中的最近访问
|
||||||
|
mixed = 混合
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,11 @@
|
||||||
cosmic-files = COSMIC 檔案總管
|
cosmic-files = COSMIC 檔案
|
||||||
empty-folder = 空資料夾
|
empty-folder = 空資料夾
|
||||||
empty-folder-hidden = 空資料夾(包含隱藏項目)
|
empty-folder-hidden = 空資料夾(包含隱藏項目)
|
||||||
no-results = 找不到結果
|
no-results = 找不到結果
|
||||||
filesystem = 檔案系統
|
filesystem = 檔案系統
|
||||||
home = 主目錄
|
home = 家目錄
|
||||||
networks = 網路
|
networks = 網路
|
||||||
notification-in-progress = 檔案操作正在進行中。
|
notification-in-progress = 檔案操作正在進行中
|
||||||
trash = 垃圾桶
|
trash = 垃圾桶
|
||||||
recents = 最近使用
|
recents = 最近使用
|
||||||
undo = 復原
|
undo = 復原
|
||||||
|
|
@ -25,7 +25,7 @@ create-archive = 建立壓縮檔案
|
||||||
## Empty Trash Dialog
|
## Empty Trash Dialog
|
||||||
|
|
||||||
empty-trash = 清空垃圾桶
|
empty-trash = 清空垃圾桶
|
||||||
empty-trash-warning = 你確定要永久刪除垃圾桶中的所有項目嗎?
|
empty-trash-warning = 垃圾桶中的項目將被永久刪除
|
||||||
|
|
||||||
## New File/Folder Dialog
|
## New File/Folder Dialog
|
||||||
|
|
||||||
|
|
@ -33,16 +33,16 @@ create-new-file = 建立新檔案
|
||||||
create-new-folder = 建立新資料夾
|
create-new-folder = 建立新資料夾
|
||||||
file-name = 檔案名稱
|
file-name = 檔案名稱
|
||||||
folder-name = 資料夾名稱
|
folder-name = 資料夾名稱
|
||||||
file-already-exists = 已存在同名檔案。
|
file-already-exists = 相同名稱的檔案已經存在
|
||||||
folder-already-exists = 已存在同名資料夾。
|
folder-already-exists = 相同名稱的資料夾已經存在
|
||||||
name-hidden = 以「.」開頭的名稱將會被隱藏。
|
name-hidden = 以「.」開頭的名稱將會被隱藏
|
||||||
name-invalid = 名稱不能是 「{ $filename }」。
|
name-invalid = 名稱不能是「{ $filename }」
|
||||||
name-no-slashes = 名稱不能包含斜線。
|
name-no-slashes = 名稱不能包含斜線
|
||||||
|
|
||||||
## Open/Save Dialog
|
## Open/Save Dialog
|
||||||
|
|
||||||
cancel = 取消
|
cancel = 取消
|
||||||
create = 建立
|
create = 創作
|
||||||
open = 開啟
|
open = 開啟
|
||||||
open-file = 開啟檔案
|
open-file = 開啟檔案
|
||||||
open-folder = 開啟資料夾
|
open-folder = 開啟資料夾
|
||||||
|
|
@ -62,12 +62,12 @@ rename-folder = 重新命名資料夾
|
||||||
## Replace Dialog
|
## Replace Dialog
|
||||||
|
|
||||||
replace = 取代
|
replace = 取代
|
||||||
replace-title = 檔案「{ $filename }」已存在於此位置。
|
replace-title = 「{ $filename }」已存在於此位置
|
||||||
replace-warning = 你要取代它嗎?取代將覆蓋其內容。
|
replace-warning = 你要取代它嗎?取代將覆蓋其內容。
|
||||||
replace-warning-operation = 你要取代它嗎?取代將覆蓋其內容。
|
replace-warning-operation = 你要取代它嗎?取代將覆蓋其內容。
|
||||||
original-file = 原始檔案
|
original-file = 原始檔案
|
||||||
replace-with = 取代為
|
replace-with = 取代為
|
||||||
apply-to-all = 套用至所有項目
|
apply-to-all = 套用至全部
|
||||||
keep-both = 保留兩者
|
keep-both = 保留兩者
|
||||||
skip = 跳過
|
skip = 跳過
|
||||||
|
|
||||||
|
|
@ -95,13 +95,13 @@ network-drive-description =
|
||||||
伺服器地址包括協定前綴和地址。
|
伺服器地址包括協定前綴和地址。
|
||||||
範例:ssh://192.168.0.1, ftp://[2001:db8::1]
|
範例:ssh://192.168.0.1, ftp://[2001:db8::1]
|
||||||
network-drive-schemes =
|
network-drive-schemes =
|
||||||
可用協定,前綴
|
可用協定,前綴
|
||||||
AppleTalk,afp://
|
AppleTalk,afp://
|
||||||
檔案傳輸協定,ftp:// 或 ftps://
|
檔案傳輸協定,ftp:// 或 ftps://
|
||||||
網路檔案系統,nfs://
|
網路檔案系統,nfs://
|
||||||
伺服器訊息區塊,smb://
|
伺服器訊息區塊,smb://
|
||||||
SSH 檔案傳輸協定,sftp:// 或 ssh://
|
SSH 檔案傳輸協定,sftp:// 或 ssh://
|
||||||
WebDav,dav:// 或 davs://
|
WebDav,dav:// 或 davs://
|
||||||
network-drive-error = 無法存取網路磁碟機
|
network-drive-error = 無法存取網路磁碟機
|
||||||
password = 密碼
|
password = 密碼
|
||||||
remember-password = 記住密碼
|
remember-password = 記住密碼
|
||||||
|
|
@ -112,7 +112,7 @@ username = 使用者名稱
|
||||||
|
|
||||||
edit-history = 編輯歷史
|
edit-history = 編輯歷史
|
||||||
history = 歷史紀錄
|
history = 歷史紀錄
|
||||||
no-history = 沒有歷史項目。
|
no-history = 無歷史記錄項目。
|
||||||
pending = 待處理
|
pending = 待處理
|
||||||
failed = 失敗
|
failed = 失敗
|
||||||
complete = 完成
|
complete = 完成
|
||||||
|
|
@ -133,51 +133,51 @@ copying =
|
||||||
正在複製 { $items } { $items ->
|
正在複製 { $items } { $items ->
|
||||||
[one] 項目
|
[one] 項目
|
||||||
*[other] 項目
|
*[other] 項目
|
||||||
} 從「{ $from }」到「{ $to }」({ $progress })...
|
}從「{ $from }」到「{ $to }」({ $progress })...
|
||||||
copied =
|
copied =
|
||||||
已複製 { $items } { $items ->
|
已複製 { $items } { $items ->
|
||||||
[one] 項目
|
[one] 項目
|
||||||
*[other] 項目
|
*[other] 項目
|
||||||
}從「{ $from }」到「{ $to }」
|
}從「{ $from }」到「{ $to }」
|
||||||
emptying-trash = 正在清空{ trash }({ $progress })…
|
emptying-trash = 正在清空 { trash }({ $progress })…
|
||||||
emptied-trash = 已清空{ trash }
|
emptied-trash = 已經清空 { trash }
|
||||||
extracting =
|
extracting =
|
||||||
正在解壓縮 { $items } 項目 { $items ->
|
正在解壓縮 { $items } 項目 { $items ->
|
||||||
[one] 項目
|
[one] 項目
|
||||||
*[other] 項目
|
*[other] 項目
|
||||||
} 從「{ $from }」到「{ $to }」({ $progress })...
|
}從「{ $from }」至「{ $to }」({ $progress })...
|
||||||
extracted =
|
extracted =
|
||||||
已解壓縮 { $items } 項目 { $items ->
|
已解壓縮 { $items } 項目 { $items ->
|
||||||
[one] 項目
|
[one] 項目
|
||||||
*[other] 項目
|
*[other] 項目
|
||||||
} 從 { $from } 到 { $to }
|
}從「{ $from }」到「{ $to }」
|
||||||
moving =
|
moving =
|
||||||
正在移動 { $items } { $items ->
|
正在移動 { $items } { $items ->
|
||||||
[one] 項目
|
[one] 項目
|
||||||
*[other] 項目
|
*[other] 項目
|
||||||
} 從「{ $from }」到「{ $to }」({ $progress })...
|
}從「{ $from }」到「{ $to }」({ $progress })...
|
||||||
moved =
|
moved =
|
||||||
已移動 { $items } { $items ->
|
已經移動 { $items } { $items ->
|
||||||
[one] 項目
|
[one] 項目
|
||||||
*[other] 項目
|
*[other] 項目
|
||||||
} 從「{ $from }」到「{ $to }」
|
} 從「{ $from }」至「{ $to }」
|
||||||
renaming = 正在重新命名「{ $from }」為「{ $to }」
|
renaming = 正在重新命名「{ $from }」至「{ $to }」
|
||||||
renamed = 已重新命名 { $from } 為 { $to }
|
renamed = 已經重新命名「{ $from }」至「{ $to }」
|
||||||
restoring =
|
restoring =
|
||||||
正在還原 { $items } 項目 { $items ->
|
正在還原 { $items } 項目 { $items ->
|
||||||
[one] 項目
|
[one] 項目
|
||||||
*[other] 項目
|
*[other] 項目
|
||||||
} 從{ trash }({ $progress })...
|
}自 { trash } ({ $progress })...
|
||||||
restored =
|
restored =
|
||||||
已還原 { $items } 項目 { $items ->
|
已經還原 { $items } 項目 { $items ->
|
||||||
[one] 項目
|
[one] 項目
|
||||||
*[other] 項目
|
*[other] 項目
|
||||||
} 從{ trash }
|
}從 { trash }
|
||||||
unknown-folder = 未知資料夾
|
unknown-folder = 不明資料夾
|
||||||
|
|
||||||
## Open with
|
## Open with
|
||||||
|
|
||||||
menu-open-with = 開啟方式...
|
menu-open-with = 開啟檔案...
|
||||||
default-app = { $name } (預設)
|
default-app = { $name } (預設)
|
||||||
|
|
||||||
## Show details
|
## Show details
|
||||||
|
|
@ -192,15 +192,15 @@ settings = 設定
|
||||||
|
|
||||||
appearance = 外觀
|
appearance = 外觀
|
||||||
theme = 主題
|
theme = 主題
|
||||||
match-desktop = 與桌面一致
|
match-desktop = 符合桌面
|
||||||
dark = 暗色模式
|
dark = 深色
|
||||||
light = 亮色模式
|
light = 淺色
|
||||||
# Context menu
|
# Context menu
|
||||||
add-to-sidebar = 加入側邊欄
|
add-to-sidebar = 添加至側邊欄
|
||||||
compress = 壓縮
|
compress = 壓縮…
|
||||||
extract-here = 解壓縮至此
|
extract-here = 解壓縮
|
||||||
new-file = 新檔案...
|
new-file = 新建檔案...
|
||||||
new-folder = 新資料夾...
|
new-folder = 新建資料夾...
|
||||||
open-in-terminal = 在終端機中開啟
|
open-in-terminal = 在終端機中開啟
|
||||||
move-to-trash = 移動至垃圾桶
|
move-to-trash = 移動至垃圾桶
|
||||||
restore-from-trash = 從垃圾桶還原
|
restore-from-trash = 從垃圾桶還原
|
||||||
|
|
@ -215,8 +215,8 @@ sort-by-size = 依大小排序
|
||||||
## File
|
## File
|
||||||
|
|
||||||
file = 檔案
|
file = 檔案
|
||||||
new-tab = 新分頁
|
new-tab = 新建分頁
|
||||||
new-window = 新視窗
|
new-window = 新建視窗
|
||||||
rename = 重新命名...
|
rename = 重新命名...
|
||||||
close-tab = 關閉分頁
|
close-tab = 關閉分頁
|
||||||
quit = 退出
|
quit = 退出
|
||||||
|
|
@ -238,36 +238,36 @@ view = 檢視
|
||||||
grid-view = 網格檢視
|
grid-view = 網格檢視
|
||||||
list-view = 列表檢視
|
list-view = 列表檢視
|
||||||
show-hidden-files = 顯示隱藏檔案
|
show-hidden-files = 顯示隱藏檔案
|
||||||
list-directories-first = 優先列出目錄
|
list-directories-first = 目錄優先列出
|
||||||
menu-settings = 設定...
|
menu-settings = 設定...
|
||||||
menu-about = 關於 COSMIC 檔案總管...
|
menu-about = 關於 COSMIC 檔案...
|
||||||
|
|
||||||
## Sort
|
## Sort
|
||||||
|
|
||||||
sort = 排序
|
sort = 排序
|
||||||
sort-a-z = A-Z
|
sort-a-z = A-Z
|
||||||
sort-z-a = Z-A
|
sort-z-a = Z-A
|
||||||
sort-newest-first = 最新的在前
|
sort-newest-first = 最新優先
|
||||||
sort-oldest-first = 最舊的在前
|
sort-oldest-first = 最舊優先
|
||||||
sort-smallest-to-largest = 由小至大
|
sort-smallest-to-largest = 從小到大
|
||||||
sort-largest-to-smallest = 由大至小
|
sort-largest-to-smallest = 從大到小
|
||||||
deleted =
|
deleted =
|
||||||
已刪除 { $items } { $items ->
|
已經刪除 { $items } { $items ->
|
||||||
[one] 項目
|
[one] 項目
|
||||||
*[other] 項目
|
*[other] 項目
|
||||||
}從{ trash }
|
}從 { trash }
|
||||||
permanently-deleting =
|
permanently-deleting =
|
||||||
正在永久刪除 { $items } { $items ->
|
正在永久刪除 { $items } { $items ->
|
||||||
[one] 项目
|
[one] 项目
|
||||||
*[other] 项目
|
*[other] 项目
|
||||||
}
|
}
|
||||||
permanently-deleted =
|
permanently-deleted =
|
||||||
已永久刪除 { $items } { $items ->
|
已經永久刪除 { $items } { $items ->
|
||||||
[one] 项目
|
[one] 项目
|
||||||
*[other] 项目
|
*[other] 项目
|
||||||
}
|
}
|
||||||
removing-from-recents =
|
removing-from-recents =
|
||||||
正在從{ recents }中移除 { $items } { $items ->
|
正在從 { recents } 中移除 { $items } { $items ->
|
||||||
[one] 项目
|
[one] 项目
|
||||||
*[other] 项目
|
*[other] 项目
|
||||||
}
|
}
|
||||||
|
|
@ -275,9 +275,112 @@ deleting =
|
||||||
正在刪除 { $items } { $items ->
|
正在刪除 { $items } { $items ->
|
||||||
[one] 项目
|
[one] 项目
|
||||||
*[other] 项目
|
*[other] 项目
|
||||||
}從{ trash }({ $progress })…
|
}從 { trash }({ $progress })…
|
||||||
removed-from-recents =
|
removed-from-recents =
|
||||||
已從{ recents }中移除 { $items } { $items ->
|
已經從 { recents } 中移除 { $items } { $items ->
|
||||||
[one] 项目
|
[one] 项目
|
||||||
*[other] 项目
|
*[other] 项目
|
||||||
}
|
}
|
||||||
|
repository = 軟體庫源
|
||||||
|
desktop-view-options = 桌面檢視選項...
|
||||||
|
show-on-desktop = 顯示在桌面
|
||||||
|
desktop-folder-content = 桌面資料夾內容
|
||||||
|
mounted-drives = 已經掛載的磁碟機
|
||||||
|
trash-folder-icon = 垃圾桶圖示
|
||||||
|
trashed-on = 遺棄時間
|
||||||
|
icon-size-and-spacing = 圖示大小與間距
|
||||||
|
icon-size = 圖示大小
|
||||||
|
grid-spacing = 網格間距
|
||||||
|
details = 詳情
|
||||||
|
dismiss = 撤停訊息
|
||||||
|
delete = 刪除
|
||||||
|
remove = 移除
|
||||||
|
support = 支援
|
||||||
|
cancelled = 已取消
|
||||||
|
keywords = 資料夾;管理器;
|
||||||
|
empty-trash-title = 清空垃圾桶?
|
||||||
|
pause = 暫停
|
||||||
|
resume = 繼續
|
||||||
|
extract-password-required = 需要密碼
|
||||||
|
extract-to = 解壓縮至...
|
||||||
|
extract-to-title = 解壓縮至資料夾
|
||||||
|
mount-error = 無法存取磁碟機
|
||||||
|
open-with-title = 您要如何開啟「{ $name }」?
|
||||||
|
browse-store = 瀏覽 { $store }
|
||||||
|
other-apps = 其他應用程式
|
||||||
|
related-apps = 相關應用程式
|
||||||
|
permanently-delete-question = 永久刪除?
|
||||||
|
set-executable-and-launch = 設定為可以執行並啟動
|
||||||
|
read-only = 唯讀
|
||||||
|
read-execute = 讀取和執行
|
||||||
|
read-write = 讀取和寫入
|
||||||
|
read-write-execute = 讀取、寫入和執行
|
||||||
|
favorite-path-error = 開啟目錄時發生錯誤
|
||||||
|
set-executable-and-launch-description = 您是否要將「{ $name }」設為可執行並啟動它?
|
||||||
|
set-and-launch = 設定並啟動
|
||||||
|
none = 無
|
||||||
|
execute-only = 僅執行
|
||||||
|
write-only = 僅寫入
|
||||||
|
write-execute = 寫入和執行
|
||||||
|
operations-running =
|
||||||
|
{ $running } { $running ->
|
||||||
|
[one] 個操作
|
||||||
|
*[other] 個操作
|
||||||
|
}正在執行({ $percent }%)...
|
||||||
|
operations-running-finished =
|
||||||
|
{ $running } { $running ->
|
||||||
|
[one] 個操作
|
||||||
|
*[other] 個操作
|
||||||
|
}正在執行({ $percent }%), { $finished } 個已經完成...
|
||||||
|
permanently-delete-warning = 「{ $target }」將被永久刪除。此操作無法復原。
|
||||||
|
open-with = 開啟檔案
|
||||||
|
selected-items = 已經選定 { $items } 個項目
|
||||||
|
copy-to-title = 選擇複製目的地
|
||||||
|
copy-to-button-label = 複製
|
||||||
|
move-to-title = 選擇移動目的地
|
||||||
|
move-to-button-label = 移動
|
||||||
|
keep = 保留
|
||||||
|
progress = { $percent }%
|
||||||
|
progress-cancelled = { $percent }%,已經取消
|
||||||
|
progress-failed = { $percent }%,失敗
|
||||||
|
progress-paused = { $percent }%,已經暫停
|
||||||
|
favorite-path-error-description =
|
||||||
|
無法開啟「{ $path }」
|
||||||
|
「{ $path }」可能不存在,或您可能沒有權限開啟它。
|
||||||
|
|
||||||
|
您是否要將它從側邊欄移除?
|
||||||
|
comment = COSMIC 桌面檔案管理器
|
||||||
|
pasted-image = 已經貼上的圖片
|
||||||
|
pasted-text = 已經貼上的文字
|
||||||
|
pasted-video = 已經貼上的影片
|
||||||
|
sort-by-trashed = 依丟入時間排序
|
||||||
|
calculating = 計算中...
|
||||||
|
single-click = 點按以開啟
|
||||||
|
type-to-search = 輸入進行搜尋
|
||||||
|
type-to-search-recursive = 搜尋目前資料夾及全部子資料夾
|
||||||
|
type-to-search-enter-path = 輸入目錄或檔案的路徑
|
||||||
|
delete-permanently = 永久刪除
|
||||||
|
eject = 彈出
|
||||||
|
remove-from-recents = 從最近項目中移除
|
||||||
|
change-wallpaper = 變更桌布...
|
||||||
|
desktop-appearance = 桌面外觀...
|
||||||
|
display-settings = 顯示設定...
|
||||||
|
reload-folder = 重新載入資料夾
|
||||||
|
gallery-preview = 圖庫預覽
|
||||||
|
type = 類型:{ $mime }
|
||||||
|
items = 項目:{ $items }
|
||||||
|
item-size = 大小:{ $size }
|
||||||
|
item-created = 建立時間:{ $created }
|
||||||
|
item-modified = 修改時間:{ $modified }
|
||||||
|
item-accessed = 存取時間:{ $accessed }
|
||||||
|
type-to-search-select = 選取第一個符合條件的檔案或資料夾
|
||||||
|
copy-to = 複製至...
|
||||||
|
move-to = 移動至...
|
||||||
|
show-recents = 側邊欄中的最近使用資料夾
|
||||||
|
clear-recents-history = 清除最近使用歷史記錄
|
||||||
|
copy-path = 複製路徑
|
||||||
|
setting-executable-and-launching = 設定「{ $name }」為可以執行並進行啟動
|
||||||
|
set-executable-and-launched = 設定「{ $name }」為可以執行並已經啟動
|
||||||
|
setting-permissions = 設定「{ $name }」的權限至 { $mode }
|
||||||
|
set-permissions = 設定「{ $name }」的權限至 { $mode }
|
||||||
|
mixed = 混合
|
||||||
|
|
|
||||||
58
src/app.rs
58
src/app.rs
|
|
@ -18,9 +18,7 @@ use cosmic::{
|
||||||
Application, ApplicationExt, Element,
|
Application, ApplicationExt, Element,
|
||||||
app::{self, Core, Task, context_drawer},
|
app::{self, Core, Task, context_drawer},
|
||||||
cosmic_config::{self, ConfigSet},
|
cosmic_config::{self, ConfigSet},
|
||||||
cosmic_theme,
|
cosmic_theme, executor,
|
||||||
desktop::fde::DesktopEntry,
|
|
||||||
executor,
|
|
||||||
iced::core::widget::operation::focusable::unfocus,
|
iced::core::widget::operation::focusable::unfocus,
|
||||||
iced::runtime::{clipboard, task},
|
iced::runtime::{clipboard, task},
|
||||||
iced::widget::{button::focus, scrollable::AbsoluteOffset},
|
iced::widget::{button::focus, scrollable::AbsoluteOffset},
|
||||||
|
|
@ -99,6 +97,7 @@ use crate::{
|
||||||
self, HOVER_DURATION, HeadingOptions, ItemMetadata, Location, SORT_OPTION_FALLBACK,
|
self, HOVER_DURATION, HeadingOptions, ItemMetadata, Location, SORT_OPTION_FALLBACK,
|
||||||
SearchLocation, Tab,
|
SearchLocation, Tab,
|
||||||
},
|
},
|
||||||
|
trash::{Trash, TrashExt},
|
||||||
zoom::{zoom_in_view, zoom_out_view, zoom_to_default},
|
zoom::{zoom_in_view, zoom_out_view, zoom_to_default},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -834,9 +833,12 @@ impl App {
|
||||||
|
|
||||||
// First launch apps that can be launched directly
|
// First launch apps that can be launched directly
|
||||||
if mime == "application/x-desktop" {
|
if mime == "application/x-desktop" {
|
||||||
// Try opening desktop application
|
#[cfg(feature = "desktop")]
|
||||||
Self::launch_desktop_entries(&paths);
|
{
|
||||||
continue;
|
// Try opening desktop application
|
||||||
|
Self::launch_desktop_entries(&paths);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
} else if mime == "application/x-executable" || mime == "application/vnd.appimage" {
|
} else if mime == "application/x-executable" || mime == "application/vnd.appimage" {
|
||||||
// Try opening executable
|
// Try opening executable
|
||||||
for path in paths {
|
for path in paths {
|
||||||
|
|
@ -897,7 +899,10 @@ impl App {
|
||||||
Task::batch(tasks)
|
Task::batch(tasks)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "desktop")]
|
||||||
fn launch_desktop_entries(paths: &[impl AsRef<Path>]) {
|
fn launch_desktop_entries(paths: &[impl AsRef<Path>]) {
|
||||||
|
use cosmic::desktop::fde::DesktopEntry;
|
||||||
|
|
||||||
for path in paths.iter().map(AsRef::as_ref) {
|
for path in paths.iter().map(AsRef::as_ref) {
|
||||||
match DesktopEntry::from_path::<&str>(path, None) {
|
match DesktopEntry::from_path::<&str>(path, None) {
|
||||||
Ok(entry) => match entry.exec() {
|
Ok(entry) => match entry.exec() {
|
||||||
|
|
@ -1794,7 +1799,7 @@ impl App {
|
||||||
|
|
||||||
nav_model = nav_model.insert(|b| {
|
nav_model = nav_model.insert(|b| {
|
||||||
b.text(fl!("trash"))
|
b.text(fl!("trash"))
|
||||||
.icon(icon::icon(tab::trash_helpers::trash_icon_symbolic(16)))
|
.icon(icon::icon(Trash::icon_symbolic(16)))
|
||||||
.data(Location::Trash)
|
.data(Location::Trash)
|
||||||
.divider_above()
|
.divider_above()
|
||||||
});
|
});
|
||||||
|
|
@ -2655,9 +2660,7 @@ impl Application for App {
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
if matches!(location_opt, Some(Location::Trash))
|
if matches!(location_opt, Some(Location::Trash)) && !Trash::is_empty() {
|
||||||
&& !trash::os_limited::is_empty().unwrap_or(true)
|
|
||||||
{
|
|
||||||
items.push(cosmic::widget::menu::Item::Button(
|
items.push(cosmic::widget::menu::Item::Button(
|
||||||
fl!("empty-trash"),
|
fl!("empty-trash"),
|
||||||
None,
|
None,
|
||||||
|
|
@ -4237,10 +4240,8 @@ impl Application for App {
|
||||||
.is_some_and(|loc| matches!(loc, Location::Trash))
|
.is_some_and(|loc| matches!(loc, Location::Trash))
|
||||||
});
|
});
|
||||||
if let Some(entity) = maybe_entity {
|
if let Some(entity) = maybe_entity {
|
||||||
self.nav_model.icon_set(
|
self.nav_model
|
||||||
entity,
|
.icon_set(entity, icon::icon(Trash::icon_symbolic(16)));
|
||||||
icon::icon(tab::trash_helpers::trash_icon_symbolic(16)),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return Task::batch([self.rescan_trash(), self.update_desktop()]);
|
return Task::batch([self.rescan_trash(), self.update_desktop()]);
|
||||||
|
|
@ -4262,12 +4263,14 @@ impl Application for App {
|
||||||
.collect();
|
.collect();
|
||||||
if !selected.is_empty() {
|
if !selected.is_empty() {
|
||||||
//TODO: batch rename
|
//TODO: batch rename
|
||||||
let tasks = selected
|
let mut last_name = String::new();
|
||||||
|
let tasks: Vec<_> = selected
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.filter_map(|path| {
|
.filter_map(|path| {
|
||||||
let parent = path.parent()?.to_path_buf();
|
let parent = path.parent()?.to_path_buf();
|
||||||
let name = path.file_name()?.to_str()?.to_string();
|
let name = path.file_name()?.to_str()?.to_string();
|
||||||
let dir = path.is_dir();
|
let dir = path.is_dir();
|
||||||
|
last_name = name.clone();
|
||||||
Some(self.dialog_pages.push_back(DialogPage::RenameItem {
|
Some(self.dialog_pages.push_back(DialogPage::RenameItem {
|
||||||
from: path,
|
from: path,
|
||||||
parent,
|
parent,
|
||||||
|
|
@ -4275,9 +4278,15 @@ impl Application for App {
|
||||||
dir,
|
dir,
|
||||||
}))
|
}))
|
||||||
})
|
})
|
||||||
.chain(std::iter::once(widget::text_input::focus(
|
.collect();
|
||||||
|
let tasks = tasks.into_iter().chain([
|
||||||
|
widget::text_input::focus(self.dialog_text_input.clone()),
|
||||||
|
widget::text_input::select_until_last(
|
||||||
self.dialog_text_input.clone(),
|
self.dialog_text_input.clone(),
|
||||||
)));
|
&last_name,
|
||||||
|
'.',
|
||||||
|
),
|
||||||
|
]);
|
||||||
return Task::batch(tasks);
|
return Task::batch(tasks);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -6151,6 +6160,7 @@ impl Application for App {
|
||||||
.into(),
|
.into(),
|
||||||
widget::text_input("", name.as_str())
|
widget::text_input("", name.as_str())
|
||||||
.id(self.dialog_text_input.clone())
|
.id(self.dialog_text_input.clone())
|
||||||
|
.double_click_select_delimiter('.')
|
||||||
.on_input(move |name| {
|
.on_input(move |name| {
|
||||||
Message::DialogUpdate(DialogPage::RenameItem {
|
Message::DialogUpdate(DialogPage::RenameItem {
|
||||||
from: from.clone(),
|
from: from.clone(),
|
||||||
|
|
@ -6794,14 +6804,7 @@ impl Application for App {
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
// TODO: Trash watching support for Windows, macOS, and other OSes
|
match (watcher_res, Trash::folders()) {
|
||||||
#[cfg(all(
|
|
||||||
unix,
|
|
||||||
not(target_os = "macos"),
|
|
||||||
not(target_os = "ios"),
|
|
||||||
not(target_os = "android")
|
|
||||||
))]
|
|
||||||
match (watcher_res, trash::os_limited::trash_folders()) {
|
|
||||||
(Ok(mut watcher), Ok(trash_bins)) => {
|
(Ok(mut watcher), Ok(trash_bins)) => {
|
||||||
// Watch the "bins" themselves as well as the files folder where
|
// Watch the "bins" themselves as well as the files folder where
|
||||||
// trashed items are placed. This allows us to avoid recursively
|
// trashed items are placed. This allows us to avoid recursively
|
||||||
|
|
@ -6958,8 +6961,7 @@ impl Application for App {
|
||||||
|_| {
|
|_| {
|
||||||
stream::channel(
|
stream::channel(
|
||||||
1,
|
1,
|
||||||
move |msg_tx: futures::channel::mpsc::Sender<_>| async move {
|
move |mut msg_tx: futures::channel::mpsc::Sender<_>| async move {
|
||||||
let msg_tx = Arc::new(tokio::sync::Mutex::new(msg_tx));
|
|
||||||
tokio::task::spawn_blocking(move || {
|
tokio::task::spawn_blocking(move || {
|
||||||
match notify_rust::Notification::new()
|
match notify_rust::Notification::new()
|
||||||
.summary(&fl!("notification-in-progress"))
|
.summary(&fl!("notification-in-progress"))
|
||||||
|
|
@ -6969,8 +6971,6 @@ impl Application for App {
|
||||||
Ok(notification) => {
|
Ok(notification) => {
|
||||||
let _ = futures::executor::block_on(async {
|
let _ = futures::executor::block_on(async {
|
||||||
msg_tx
|
msg_tx
|
||||||
.lock()
|
|
||||||
.await
|
|
||||||
.send(Message::Notification(Arc::new(
|
.send(Message::Notification(Arc::new(
|
||||||
Mutex::new(notification),
|
Mutex::new(notification),
|
||||||
)))
|
)))
|
||||||
|
|
|
||||||
|
|
@ -189,6 +189,8 @@ fn zip_extract<R: io::Read + io::Seek, P: AsRef<Path>>(
|
||||||
if file.is_symlink() && (cfg!(unix) || cfg!(windows)) {
|
if file.is_symlink() && (cfg!(unix) || cfg!(windows)) {
|
||||||
let mut target = Vec::with_capacity(file.size() as usize);
|
let mut target = Vec::with_capacity(file.size() as usize);
|
||||||
file.read_to_end(&mut target)?;
|
file.read_to_end(&mut target)?;
|
||||||
|
// File no longer needed, drop to allow reading target on windows
|
||||||
|
drop(file);
|
||||||
|
|
||||||
#[cfg(unix)]
|
#[cfg(unix)]
|
||||||
{
|
{
|
||||||
|
|
@ -199,11 +201,15 @@ fn zip_extract<R: io::Read + io::Seek, P: AsRef<Path>>(
|
||||||
#[cfg(windows)]
|
#[cfg(windows)]
|
||||||
{
|
{
|
||||||
let Ok(target) = String::from_utf8(target) else {
|
let Ok(target) = String::from_utf8(target) else {
|
||||||
return Err(ZipError::InvalidArchive("Invalid UTF-8 as symlink target"));
|
return Err(ZipError::InvalidArchive(
|
||||||
|
"Invalid UTF-8 as symlink target".into(),
|
||||||
|
));
|
||||||
};
|
};
|
||||||
let target = target.into_boxed_str();
|
let target_is_dir_from_archive = match password {
|
||||||
let target_is_dir_from_archive =
|
None => archive.by_name(&target),
|
||||||
archive.shared.files.contains_key(&target) && is_dir(&target);
|
Some(pwd) => archive.by_name_decrypt(&target, pwd.as_bytes()),
|
||||||
|
}
|
||||||
|
.map_or(false, |x| x.is_dir());
|
||||||
let target_path = directory.as_ref().join(OsString::from(target.to_string()));
|
let target_path = directory.as_ref().join(OsString::from(target.to_string()));
|
||||||
let target_is_dir = if target_is_dir_from_archive {
|
let target_is_dir = if target_is_dir_from_archive {
|
||||||
true
|
true
|
||||||
|
|
|
||||||
77
src/channel.rs
Normal file
77
src/channel.rs
Normal file
|
|
@ -0,0 +1,77 @@
|
||||||
|
// Copyright 2025 System76 <info@system76.com>
|
||||||
|
// SPDX-License-Identifier: MPL-2.0
|
||||||
|
|
||||||
|
use std::{
|
||||||
|
collections::VecDeque,
|
||||||
|
sync::{
|
||||||
|
Arc, Mutex,
|
||||||
|
atomic::{AtomicBool, Ordering},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Create a channel backed by `tokio::sync::Notify` and a sync mutex with a vec deque.
|
||||||
|
pub fn channel<Message>() -> (Sender<Message>, Receiver<Message>) {
|
||||||
|
let channel = Arc::new(Channel {
|
||||||
|
queue: Mutex::new(VecDeque::default()),
|
||||||
|
notify: tokio::sync::Notify::const_new(),
|
||||||
|
closed: AtomicBool::new(false),
|
||||||
|
});
|
||||||
|
|
||||||
|
(Sender(channel.clone()), Receiver(channel))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A channel backed by `tokio::sync::Notify` and a sync mutex with a vec deque.
|
||||||
|
struct Channel<Message> {
|
||||||
|
pub(self) queue: Mutex<VecDeque<Message>>,
|
||||||
|
/// Set when a new message has been stored.
|
||||||
|
pub(self) notify: tokio::sync::Notify,
|
||||||
|
/// Set when the receiver is dropped.
|
||||||
|
pub(self) closed: AtomicBool,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Sender<Message>(Arc<Channel<Message>>);
|
||||||
|
|
||||||
|
impl<Message> Sender<Message> {
|
||||||
|
pub fn send(&self, message: Message) {
|
||||||
|
self.0.queue.lock().unwrap().push_back(message);
|
||||||
|
self.0.notify.notify_one();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<Message> Drop for Sender<Message> {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
self.0.closed.store(true, Ordering::SeqCst);
|
||||||
|
self.0.notify.notify_one();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Receiver<Message>(Arc<Channel<Message>>);
|
||||||
|
|
||||||
|
impl<Message> Receiver<Message> {
|
||||||
|
/// Returns a value until the sender is dropped.
|
||||||
|
pub async fn recv(&self) -> Option<Message> {
|
||||||
|
loop {
|
||||||
|
{
|
||||||
|
let mut queue = self.0.queue.lock().unwrap();
|
||||||
|
if let Some(value) = queue.pop_front() {
|
||||||
|
if queue.capacity() - queue.len() > 32 {
|
||||||
|
let capacity = queue.len().next_power_of_two();
|
||||||
|
queue.shrink_to(capacity);
|
||||||
|
}
|
||||||
|
drop(queue);
|
||||||
|
return Some(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if self.0.closed.load(Ordering::SeqCst) {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
self.0.notify.notified().await;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn try_recv(&self) -> Option<Message> {
|
||||||
|
self.0.queue.lock().unwrap().pop_front()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -441,13 +441,8 @@ impl<M: Send + 'static> Dialog<M> {
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
enum DialogPage {
|
enum DialogPage {
|
||||||
NewFolder {
|
NewFolder { parent: PathBuf, name: String },
|
||||||
parent: PathBuf,
|
Replace { filename: String },
|
||||||
name: String,
|
|
||||||
},
|
|
||||||
Replace {
|
|
||||||
filename: String,
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
|
|
@ -600,6 +595,7 @@ impl App {
|
||||||
col = col.push(
|
col = col.push(
|
||||||
widget::text_input("", filename)
|
widget::text_input("", filename)
|
||||||
.id(self.filename_id.clone())
|
.id(self.filename_id.clone())
|
||||||
|
.double_click_select_delimiter('.')
|
||||||
.on_input(Message::Filename)
|
.on_input(Message::Filename)
|
||||||
.on_submit(|_| Message::Save(false)),
|
.on_submit(|_| Message::Save(false)),
|
||||||
);
|
);
|
||||||
|
|
@ -1962,6 +1958,16 @@ impl Application for App {
|
||||||
if self.search_get().is_some() {
|
if self.search_get().is_some() {
|
||||||
return widget::text_input::focus(self.search_id.clone());
|
return widget::text_input::focus(self.search_id.clone());
|
||||||
}
|
}
|
||||||
|
if let DialogKind::SaveFile { filename } = &self.flags.kind {
|
||||||
|
return Task::batch([
|
||||||
|
widget::text_input::focus(self.filename_id.clone()),
|
||||||
|
widget::text_input::select_until_last(
|
||||||
|
self.filename_id.clone(),
|
||||||
|
filename,
|
||||||
|
'.',
|
||||||
|
),
|
||||||
|
]);
|
||||||
|
}
|
||||||
return widget::text_input::focus(self.filename_id.clone());
|
return widget::text_input::focus(self.filename_id.clone());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -2031,7 +2037,7 @@ impl Application for App {
|
||||||
}
|
}
|
||||||
|
|
||||||
col = col.push(
|
col = col.push(
|
||||||
self.tab
|
self.tab
|
||||||
.view(&self.key_binds, &self.modifiers, false, &[])
|
.view(&self.key_binds, &self.modifiers, false, &[])
|
||||||
.map(Message::TabMessage),
|
.map(Message::TabMessage),
|
||||||
);
|
);
|
||||||
|
|
|
||||||
53
src/lib.rs
53
src/lib.rs
|
|
@ -3,14 +3,20 @@
|
||||||
|
|
||||||
use cosmic::{app::Settings, iced::Limits};
|
use cosmic::{app::Settings, iced::Limits};
|
||||||
use std::{env, fs, path::PathBuf, process};
|
use std::{env, fs, path::PathBuf, process};
|
||||||
|
use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt};
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
app::{App, Flags},
|
||||||
|
config::{Config, State},
|
||||||
|
tab::Location,
|
||||||
|
};
|
||||||
|
|
||||||
use app::{App, Flags};
|
|
||||||
pub mod app;
|
pub mod app;
|
||||||
mod archive;
|
mod archive;
|
||||||
|
pub mod channel;
|
||||||
pub mod clipboard;
|
pub mod clipboard;
|
||||||
mod context_action;
|
|
||||||
use config::Config;
|
|
||||||
pub mod config;
|
pub mod config;
|
||||||
|
mod context_action;
|
||||||
pub mod dialog;
|
pub mod dialog;
|
||||||
mod key_bind;
|
mod key_bind;
|
||||||
pub(crate) mod large_image;
|
pub(crate) mod large_image;
|
||||||
|
|
@ -23,13 +29,11 @@ mod mounter;
|
||||||
mod mouse_area;
|
mod mouse_area;
|
||||||
pub mod operation;
|
pub mod operation;
|
||||||
mod spawn_detached;
|
mod spawn_detached;
|
||||||
use tab::Location;
|
|
||||||
mod zoom;
|
|
||||||
|
|
||||||
use crate::config::State;
|
|
||||||
pub mod tab;
|
pub mod tab;
|
||||||
mod thumbnail_cacher;
|
mod thumbnail_cacher;
|
||||||
mod thumbnailer;
|
mod thumbnailer;
|
||||||
|
pub(crate) mod trash;
|
||||||
|
mod zoom;
|
||||||
|
|
||||||
pub(crate) type FxOrderMap<K, V> = ordermap::OrderMap<K, V, rustc_hash::FxBuildHasher>;
|
pub(crate) type FxOrderMap<K, V> = ordermap::OrderMap<K, V, rustc_hash::FxBuildHasher>;
|
||||||
|
|
||||||
|
|
@ -73,7 +77,22 @@ pub fn is_wayland() -> bool {
|
||||||
/// Runs application in desktop mode
|
/// Runs application in desktop mode
|
||||||
#[rustfmt::skip]
|
#[rustfmt::skip]
|
||||||
pub fn desktop() -> Result<(), Box<dyn std::error::Error>> {
|
pub fn desktop() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
env_logger::Builder::from_env(env_logger::Env::default().default_filter_or("warn")).init();
|
let log_format = tracing_subscriber::fmt::format()
|
||||||
|
.pretty()
|
||||||
|
.without_time()
|
||||||
|
.with_line_number(true)
|
||||||
|
.with_file(true)
|
||||||
|
.with_target(false)
|
||||||
|
.with_thread_names(true);
|
||||||
|
|
||||||
|
let log_layer = tracing_subscriber::fmt::Layer::default()
|
||||||
|
.with_writer(std::io::stderr)
|
||||||
|
.event_format(log_format);
|
||||||
|
|
||||||
|
tracing_subscriber::registry()
|
||||||
|
.with(tracing_subscriber::EnvFilter::from_env("RUST_LOG"))
|
||||||
|
.with(log_layer)
|
||||||
|
.init();
|
||||||
|
|
||||||
localize::localize();
|
localize::localize();
|
||||||
|
|
||||||
|
|
@ -108,7 +127,21 @@ pub fn desktop() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
/// Runs application with these settings
|
/// Runs application with these settings
|
||||||
#[rustfmt::skip]
|
#[rustfmt::skip]
|
||||||
pub fn main() -> Result<(), Box<dyn std::error::Error>> {
|
pub fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
env_logger::Builder::from_env(env_logger::Env::default().default_filter_or("warn")).init();
|
let log_format = tracing_subscriber::fmt::format()
|
||||||
|
.pretty()
|
||||||
|
.with_line_number(true)
|
||||||
|
.with_file(true)
|
||||||
|
.with_target(false)
|
||||||
|
.with_thread_names(true);
|
||||||
|
|
||||||
|
let log_layer = tracing_subscriber::fmt::Layer::default()
|
||||||
|
.with_writer(std::io::stderr)
|
||||||
|
.event_format(log_format);
|
||||||
|
|
||||||
|
tracing_subscriber::registry()
|
||||||
|
.with(tracing_subscriber::EnvFilter::from_default_env())
|
||||||
|
.with(log_layer)
|
||||||
|
.init();
|
||||||
|
|
||||||
localize::localize();
|
localize::localize();
|
||||||
|
|
||||||
|
|
@ -158,7 +191,7 @@ pub fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
}
|
}
|
||||||
|
|
||||||
if daemonize {
|
if daemonize {
|
||||||
#[cfg(all(unix, not(target_os = "redox")))]
|
#[cfg(all(unix, not(any(target_os = "macos", target_os = "redox"))))]
|
||||||
match fork::daemon(true, true) {
|
match fork::daemon(true, true) {
|
||||||
Ok(fork::Fork::Child) => (),
|
Ok(fork::Fork::Child) => (),
|
||||||
Ok(fork::Fork::Parent(_child_pid)) => process::exit(0),
|
Ok(fork::Fork::Parent(_child_pid)) => process::exit(0),
|
||||||
|
|
|
||||||
|
|
@ -23,6 +23,7 @@ use crate::{
|
||||||
config::{Config, ContextActionPreset},
|
config::{Config, ContextActionPreset},
|
||||||
fl,
|
fl,
|
||||||
tab::{self, HeadingOptions, Location, LocationMenuAction, SearchLocation, Tab},
|
tab::{self, HeadingOptions, Location, LocationMenuAction, SearchLocation, Tab},
|
||||||
|
trash::{Trash, TrashExt},
|
||||||
};
|
};
|
||||||
|
|
||||||
static MENU_ID: LazyLock<cosmic::widget::Id> =
|
static MENU_ID: LazyLock<cosmic::widget::Id> =
|
||||||
|
|
@ -192,7 +193,7 @@ pub fn context_menu<'a>(
|
||||||
) => {
|
) => {
|
||||||
if selected_trash_only {
|
if selected_trash_only {
|
||||||
children.push(menu_item(fl!("open"), Action::Open).into());
|
children.push(menu_item(fl!("open"), Action::Open).into());
|
||||||
if !trash::os_limited::is_empty().unwrap_or(true) {
|
if !Trash::is_empty() {
|
||||||
children.push(menu_item(fl!("empty-trash"), Action::EmptyTrash).into());
|
children.push(menu_item(fl!("empty-trash"), Action::EmptyTrash).into());
|
||||||
}
|
}
|
||||||
} else if let Some(entry) = selected_desktop_entry {
|
} else if let Some(entry) = selected_desktop_entry {
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,7 @@ struct MimeIconKey {
|
||||||
|
|
||||||
struct MimeIconCache {
|
struct MimeIconCache {
|
||||||
cache: FxHashMap<MimeIconKey, Option<icon::Handle>>,
|
cache: FxHashMap<MimeIconKey, Option<icon::Handle>>,
|
||||||
|
#[cfg(unix)]
|
||||||
shared_mime_info: xdg_mime::SharedMimeInfo,
|
shared_mime_info: xdg_mime::SharedMimeInfo,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -26,10 +27,17 @@ impl MimeIconCache {
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
Self {
|
Self {
|
||||||
cache: FxHashMap::default(),
|
cache: FxHashMap::default(),
|
||||||
|
#[cfg(unix)]
|
||||||
shared_mime_info: xdg_mime::SharedMimeInfo::new(),
|
shared_mime_info: xdg_mime::SharedMimeInfo::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(not(unix))]
|
||||||
|
pub fn get(&mut self, _key: MimeIconKey) -> Option<icon::Handle> {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(unix)]
|
||||||
pub fn get(&mut self, key: MimeIconKey) -> Option<icon::Handle> {
|
pub fn get(&mut self, key: MimeIconKey) -> Option<icon::Handle> {
|
||||||
self.cache
|
self.cache
|
||||||
.entry(key)
|
.entry(key)
|
||||||
|
|
@ -53,6 +61,16 @@ impl MimeIconCache {
|
||||||
static MIME_ICON_CACHE: LazyLock<Mutex<MimeIconCache>> =
|
static MIME_ICON_CACHE: LazyLock<Mutex<MimeIconCache>> =
|
||||||
LazyLock::new(|| Mutex::new(MimeIconCache::new()));
|
LazyLock::new(|| Mutex::new(MimeIconCache::new()));
|
||||||
|
|
||||||
|
#[cfg(not(unix))]
|
||||||
|
pub fn mime_for_path(
|
||||||
|
path: impl AsRef<Path>,
|
||||||
|
metadata_opt: Option<&fs::Metadata>,
|
||||||
|
remote: bool,
|
||||||
|
) -> Mime {
|
||||||
|
mime_guess::from_path(path).first_or_octet_stream()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(unix)]
|
||||||
pub fn mime_for_path(
|
pub fn mime_for_path(
|
||||||
path: impl AsRef<Path>,
|
path: impl AsRef<Path>,
|
||||||
metadata_opt: Option<&fs::Metadata>,
|
metadata_opt: Option<&fs::Metadata>,
|
||||||
|
|
@ -100,8 +118,13 @@ pub fn mime_icon(mime: Mime, size: u16) -> icon::Handle {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(not(unix))]
|
||||||
|
pub fn parent_mime_types(_mime: &Mime) -> Option<Vec<Mime>> {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(unix)]
|
||||||
pub fn parent_mime_types(mime: &Mime) -> Option<Vec<Mime>> {
|
pub fn parent_mime_types(mime: &Mime) -> Option<Vec<Mime>> {
|
||||||
let mime_icon_cache = MIME_ICON_CACHE.lock().unwrap();
|
let mime_icon_cache = MIME_ICON_CACHE.lock().unwrap();
|
||||||
|
|
||||||
mime_icon_cache.shared_mime_info.get_parents_aliased(mime)
|
mime_icon_cache.shared_mime_info.get_parents_aliased(mime)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@ use cosmic::{
|
||||||
};
|
};
|
||||||
use gio::{glib, prelude::*};
|
use gio::{glib, prelude::*};
|
||||||
use std::{any::TypeId, cell::Cell, future::pending, hash::Hash, path::PathBuf, sync::Arc};
|
use std::{any::TypeId, cell::Cell, future::pending, hash::Hash, path::PathBuf, sync::Arc};
|
||||||
use tokio::sync::{Mutex, mpsc};
|
use tokio::sync::mpsc;
|
||||||
|
|
||||||
use super::{Mounter, MounterAuth, MounterItem, MounterItems, MounterMessage};
|
use super::{Mounter, MounterAuth, MounterItem, MounterItems, MounterMessage};
|
||||||
use crate::{
|
use crate::{
|
||||||
|
|
@ -199,6 +199,7 @@ fn network_scan(uri: &str, sizes: IconSizes) -> Result<Vec<tab::Item>, String> {
|
||||||
metadata,
|
metadata,
|
||||||
hidden,
|
hidden,
|
||||||
location_opt: Some(location),
|
location_opt: Some(location),
|
||||||
|
image_dimensions: None,
|
||||||
mime,
|
mime,
|
||||||
icon_handle_grid,
|
icon_handle_grid,
|
||||||
icon_handle_list,
|
icon_handle_list,
|
||||||
|
|
@ -229,7 +230,10 @@ fn dir_info(uri: &str) -> Result<(String, String, Option<PathBuf>), glib::Error>
|
||||||
Ok((resolved_uri, info.display_name().into(), file.path()))
|
Ok((resolved_uri, info.display_name().into(), file.path()))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn mount_op(uri: String, event_tx: mpsc::UnboundedSender<Event>) -> gio::MountOperation {
|
fn mount_op(
|
||||||
|
uri: String,
|
||||||
|
event_tx: std::sync::Weak<crate::channel::Sender<Event>>,
|
||||||
|
) -> gio::MountOperation {
|
||||||
let mount_op = gio::MountOperation::new();
|
let mount_op = gio::MountOperation::new();
|
||||||
mount_op.connect_ask_password(
|
mount_op.connect_ask_password(
|
||||||
move |mount_op, message, default_user, default_domain, flags| {
|
move |mount_op, message, default_user, default_domain, flags| {
|
||||||
|
|
@ -252,9 +256,9 @@ fn mount_op(uri: String, event_tx: mpsc::UnboundedSender<Event>) -> gio::MountOp
|
||||||
.then_some(false),
|
.then_some(false),
|
||||||
};
|
};
|
||||||
let (auth_tx, mut auth_rx) = mpsc::channel(1);
|
let (auth_tx, mut auth_rx) = mpsc::channel(1);
|
||||||
event_tx
|
if let Some(event_tx) = event_tx.upgrade() {
|
||||||
.send(Event::NetworkAuth(uri.clone(), auth, auth_tx))
|
event_tx.send(Event::NetworkAuth(uri.clone(), auth, auth_tx));
|
||||||
.unwrap();
|
}
|
||||||
//TODO: async recv?
|
//TODO: async recv?
|
||||||
if let Some(auth) = auth_rx.blocking_recv() {
|
if let Some(auth) = auth_rx.blocking_recv() {
|
||||||
if auth.anonymous_opt == Some(true) {
|
if auth.anonymous_opt == Some(true) {
|
||||||
|
|
@ -357,37 +361,45 @@ impl Item {
|
||||||
|
|
||||||
pub struct Gvfs {
|
pub struct Gvfs {
|
||||||
command_tx: mpsc::UnboundedSender<Cmd>,
|
command_tx: mpsc::UnboundedSender<Cmd>,
|
||||||
event_rx: Arc<Mutex<mpsc::UnboundedReceiver<Event>>>,
|
event_rx: Arc<crate::channel::Receiver<Event>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Gvfs {
|
impl Gvfs {
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
//TODO: switch to using gvfs-zbus which will better integrate with async rust
|
//TODO: switch to using gvfs-zbus which will better integrate with async rust
|
||||||
let (command_tx, mut command_rx) = mpsc::unbounded_channel();
|
let (command_tx, mut command_rx) = mpsc::unbounded_channel();
|
||||||
let (event_tx, event_rx) = mpsc::unbounded_channel();
|
let (event_tx, event_rx) = crate::channel::channel();
|
||||||
|
let event_tx = Arc::new(event_tx);
|
||||||
std::thread::spawn(move || {
|
std::thread::spawn(move || {
|
||||||
let main_loop = glib::MainLoop::new(None, false);
|
let main_loop = glib::MainLoop::new(None, false);
|
||||||
main_loop.context().spawn_local(async move {
|
main_loop.context().spawn_local(async move {
|
||||||
|
let event_tx = Arc::downgrade(&event_tx);
|
||||||
let monitor = gio::VolumeMonitor::get();
|
let monitor = gio::VolumeMonitor::get();
|
||||||
{
|
{
|
||||||
let event_tx = event_tx.clone();
|
let event_tx = event_tx.clone();
|
||||||
monitor.connect_mount_changed(move |_monitor, mount| {
|
monitor.connect_mount_changed(move |_monitor, mount| {
|
||||||
log::info!("mount changed {}", MountExt::name(mount));
|
log::info!("mount changed {}", MountExt::name(mount));
|
||||||
event_tx.send(Event::Changed).unwrap();
|
if let Some(event_tx) = event_tx.upgrade() {
|
||||||
|
event_tx.send(Event::Changed);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
let event_tx = event_tx.clone();
|
let event_tx = event_tx.clone();
|
||||||
monitor.connect_mount_added(move |_monitor, mount| {
|
monitor.connect_mount_added(move |_monitor, mount| {
|
||||||
log::info!("mount added {}", MountExt::name(mount));
|
log::info!("mount added {}", MountExt::name(mount));
|
||||||
event_tx.send(Event::Changed).unwrap();
|
if let Some(event_tx) = event_tx.upgrade() {
|
||||||
|
event_tx.send(Event::Changed);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
let event_tx = event_tx.clone();
|
let event_tx = event_tx.clone();
|
||||||
monitor.connect_mount_removed(move |_monitor, mount| {
|
monitor.connect_mount_removed(move |_monitor, mount| {
|
||||||
log::info!("mount removed {}", MountExt::name(mount));
|
log::info!("mount removed {}", MountExt::name(mount));
|
||||||
event_tx.send(Event::Changed).unwrap();
|
if let Some(event_tx) = event_tx.upgrade() {
|
||||||
|
event_tx.send(Event::Changed);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -395,21 +407,27 @@ impl Gvfs {
|
||||||
let event_tx = event_tx.clone();
|
let event_tx = event_tx.clone();
|
||||||
monitor.connect_volume_changed(move |_monitor, volume| {
|
monitor.connect_volume_changed(move |_monitor, volume| {
|
||||||
log::info!("volume changed {}", VolumeExt::name(volume));
|
log::info!("volume changed {}", VolumeExt::name(volume));
|
||||||
event_tx.send(Event::Changed).unwrap();
|
if let Some(event_tx) = event_tx.upgrade() {
|
||||||
|
event_tx.send(Event::Changed);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
let event_tx = event_tx.clone();
|
let event_tx = event_tx.clone();
|
||||||
monitor.connect_volume_added(move |_monitor, volume| {
|
monitor.connect_volume_added(move |_monitor, volume| {
|
||||||
log::info!("volume added {}", VolumeExt::name(volume));
|
log::info!("volume added {}", VolumeExt::name(volume));
|
||||||
event_tx.send(Event::Changed).unwrap();
|
if let Some(event_tx) = event_tx.upgrade() {
|
||||||
|
event_tx.send(Event::Changed);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
let event_tx = event_tx.clone();
|
let event_tx = event_tx.clone();
|
||||||
monitor.connect_volume_removed(move |_monitor, volume| {
|
monitor.connect_volume_removed(move |_monitor, volume| {
|
||||||
log::info!("volume removed {}", VolumeExt::name(volume));
|
log::info!("volume removed {}", VolumeExt::name(volume));
|
||||||
event_tx.send(Event::Changed).unwrap();
|
if let Some(event_tx) = event_tx.upgrade() {
|
||||||
|
event_tx.send(Event::Changed);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -419,7 +437,11 @@ impl Gvfs {
|
||||||
items_tx.send(items(&monitor, sizes)).await.unwrap();
|
items_tx.send(items(&monitor, sizes)).await.unwrap();
|
||||||
}
|
}
|
||||||
Cmd::Rescan => {
|
Cmd::Rescan => {
|
||||||
event_tx.send(Event::Items(items(&monitor, IconSizes::default()))).unwrap();
|
let Some(event_tx) = event_tx.upgrade() else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
event_tx.send(Event::Items(items(&monitor, IconSizes::default())));
|
||||||
}
|
}
|
||||||
Cmd::Mount(mounter_item, complete_tx) => {
|
Cmd::Mount(mounter_item, complete_tx) => {
|
||||||
let MounterItem::Gvfs(ref item) = mounter_item else {
|
let MounterItem::Gvfs(ref item) = mounter_item else {
|
||||||
|
|
@ -471,6 +493,9 @@ impl Gvfs {
|
||||||
.ok().map(|info| info.boolean(gio::FILE_ATTRIBUTE_FILESYSTEM_REMOTE))
|
.ok().map(|info| info.boolean(gio::FILE_ATTRIBUTE_FILESYSTEM_REMOTE))
|
||||||
.unwrap_or(true);
|
.unwrap_or(true);
|
||||||
}
|
}
|
||||||
|
let Some(event_tx) = event_tx.upgrade() else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
event_tx.send(Event::MountResult(updated_item, match res {
|
event_tx.send(Event::MountResult(updated_item, match res {
|
||||||
Ok(()) => {
|
Ok(()) => {
|
||||||
_ = complete_tx.send(Ok(()));
|
_ = complete_tx.send(Ok(()));
|
||||||
|
|
@ -482,7 +507,7 @@ impl Gvfs {
|
||||||
Some(gio::IOErrorEnum::FailedHandled) => Ok(false),
|
Some(gio::IOErrorEnum::FailedHandled) => Ok(false),
|
||||||
_ => Err(format!("{err}"))
|
_ => Err(format!("{err}"))
|
||||||
}}
|
}}
|
||||||
})).unwrap();
|
}));
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
break;
|
break;
|
||||||
|
|
@ -498,6 +523,9 @@ impl Gvfs {
|
||||||
gio::Cancellable::NONE,
|
gio::Cancellable::NONE,
|
||||||
move |res| {
|
move |res| {
|
||||||
log::info!("network drive {uri}: result {res:?}");
|
log::info!("network drive {uri}: result {res:?}");
|
||||||
|
let Some(event_tx) = event_tx.upgrade() else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
event_tx.send(Event::NetworkResult(uri, match res {
|
event_tx.send(Event::NetworkResult(uri, match res {
|
||||||
Ok(()) => {
|
Ok(()) => {
|
||||||
_ = result_tx.send(Ok(()));
|
_ = result_tx.send(Ok(()));
|
||||||
|
|
@ -508,7 +536,7 @@ impl Gvfs {
|
||||||
Some(gio::IOErrorEnum::FailedHandled) => Ok(false),
|
Some(gio::IOErrorEnum::FailedHandled) => Ok(false),
|
||||||
_ => Err(format!("{err}"))
|
_ => Err(format!("{err}"))
|
||||||
}}
|
}}
|
||||||
})).unwrap();
|
}));
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -532,6 +560,9 @@ impl Gvfs {
|
||||||
// FIXME sometimes a uri can be mounted and then not recognized as mounted...
|
// FIXME sometimes a uri can be mounted and then not recognized as mounted...
|
||||||
// seems to be related to uri with a path
|
// seems to be related to uri with a path
|
||||||
items_tx.blocking_send(network_scan(&uri, sizes)).unwrap();
|
items_tx.blocking_send(network_scan(&uri, sizes)).unwrap();
|
||||||
|
let Some(event_tx) = event_tx.upgrade() else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
event_tx.send(Event::NetworkResult(resolved_uri, match res {
|
event_tx.send(Event::NetworkResult(resolved_uri, match res {
|
||||||
Ok(()) => {
|
Ok(()) => {
|
||||||
Ok(true)
|
Ok(true)
|
||||||
|
|
@ -540,7 +571,7 @@ impl Gvfs {
|
||||||
Some(gio::IOErrorEnum::FailedHandled) => Ok(false),
|
Some(gio::IOErrorEnum::FailedHandled) => Ok(false),
|
||||||
_ => Err(format!("{err}"))
|
_ => Err(format!("{err}"))
|
||||||
}
|
}
|
||||||
})).unwrap();
|
}));
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -596,7 +627,7 @@ impl Gvfs {
|
||||||
});
|
});
|
||||||
Self {
|
Self {
|
||||||
command_tx,
|
command_tx,
|
||||||
event_rx: Arc::new(Mutex::new(event_rx)),
|
event_rx: Arc::new(event_rx),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -670,7 +701,7 @@ impl Mounter for Gvfs {
|
||||||
let event_rx = self.event_rx.clone();
|
let event_rx = self.event_rx.clone();
|
||||||
struct Wrapper {
|
struct Wrapper {
|
||||||
command_tx: mpsc::UnboundedSender<Cmd>,
|
command_tx: mpsc::UnboundedSender<Cmd>,
|
||||||
event_rx: Arc<Mutex<mpsc::UnboundedReceiver<Event>>>,
|
event_rx: Arc<crate::channel::Receiver<Event>>,
|
||||||
}
|
}
|
||||||
impl Hash for Wrapper {
|
impl Hash for Wrapper {
|
||||||
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
|
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
|
||||||
|
|
@ -694,7 +725,7 @@ impl Mounter for Gvfs {
|
||||||
MounterMessage,
|
MounterMessage,
|
||||||
>| async move {
|
>| async move {
|
||||||
command_tx.send(Cmd::Rescan).unwrap();
|
command_tx.send(Cmd::Rescan).unwrap();
|
||||||
while let Some(event) = event_rx.lock().await.recv().await {
|
while let Some(event) = event_rx.recv().await {
|
||||||
match event {
|
match event {
|
||||||
Event::Changed => command_tx.send(Cmd::Rescan).unwrap(),
|
Event::Changed => command_tx.send(Cmd::Rescan).unwrap(),
|
||||||
Event::Items(items) => {
|
Event::Items(items) => {
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,11 @@
|
||||||
use std::sync::{Arc, Mutex};
|
use atomic_float::AtomicF32;
|
||||||
|
use num_enum::{IntoPrimitive, TryFromPrimitive};
|
||||||
|
use std::sync::Arc;
|
||||||
|
use std::sync::atomic::{self, AtomicU16};
|
||||||
use tokio::sync::Notify;
|
use tokio::sync::Notify;
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
#[derive(Clone, Copy, Debug, PartialEq, Eq, IntoPrimitive, TryFromPrimitive)]
|
||||||
|
#[repr(u16)]
|
||||||
pub enum ControllerState {
|
pub enum ControllerState {
|
||||||
Cancelled,
|
Cancelled,
|
||||||
Failed,
|
Failed,
|
||||||
|
|
@ -11,8 +15,8 @@ pub enum ControllerState {
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
struct ControllerInner {
|
struct ControllerInner {
|
||||||
state: Mutex<ControllerState>,
|
state: AtomicU16,
|
||||||
progress: Mutex<f32>,
|
progress: AtomicF32,
|
||||||
notify: Notify,
|
notify: Notify,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -27,8 +31,8 @@ impl Default for Controller {
|
||||||
Self {
|
Self {
|
||||||
primary: true,
|
primary: true,
|
||||||
inner: Arc::new(ControllerInner {
|
inner: Arc::new(ControllerInner {
|
||||||
state: Mutex::new(ControllerState::Running),
|
state: AtomicU16::new(ControllerState::Running.into()),
|
||||||
progress: Mutex::new(0.0),
|
progress: AtomicF32::new(0.0),
|
||||||
notify: Notify::new(),
|
notify: Notify::new(),
|
||||||
}),
|
}),
|
||||||
}
|
}
|
||||||
|
|
@ -50,19 +54,24 @@ impl Controller {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn progress(&self) -> f32 {
|
pub fn progress(&self) -> f32 {
|
||||||
*self.inner.progress.lock().unwrap()
|
self.inner.progress.load(atomic::Ordering::Relaxed)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_progress(&self, progress: f32) {
|
pub fn set_progress(&self, progress: f32) {
|
||||||
*self.inner.progress.lock().unwrap() = progress;
|
self.inner
|
||||||
|
.progress
|
||||||
|
.swap(progress, atomic::Ordering::Relaxed);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn state(&self) -> ControllerState {
|
pub fn state(&self) -> ControllerState {
|
||||||
*self.inner.state.lock().unwrap()
|
ControllerState::try_from(self.inner.state.load(atomic::Ordering::Relaxed))
|
||||||
|
.unwrap_or(ControllerState::Failed)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_state(&self, state: ControllerState) {
|
pub fn set_state(&self, state: ControllerState) {
|
||||||
*self.inner.state.lock().unwrap() = state;
|
self.inner
|
||||||
|
.state
|
||||||
|
.store(state.into(), atomic::Ordering::Relaxed);
|
||||||
self.inner.notify.notify_waiters();
|
self.inner.notify.notify_waiters();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -86,6 +95,35 @@ impl Controller {
|
||||||
self.set_state(ControllerState::Paused);
|
self.set_state(ControllerState::Paused);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns when the state is paused.
|
||||||
|
///
|
||||||
|
/// Use this to pause futures.
|
||||||
|
pub async fn until_paused(&self) {
|
||||||
|
loop {
|
||||||
|
if matches!(self.state(), ControllerState::Paused) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
self.inner.notify.notified().await;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns when state is neither paused, cancelled, nor failed.
|
||||||
|
///
|
||||||
|
/// Use this to resume futures.
|
||||||
|
pub async fn until_unpaused(&self) {
|
||||||
|
loop {
|
||||||
|
if !matches!(
|
||||||
|
self.state(),
|
||||||
|
ControllerState::Paused | ControllerState::Cancelled | ControllerState::Failed
|
||||||
|
) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
self.inner.notify.notified().await;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn unpause(&self) {
|
pub fn unpause(&self) {
|
||||||
if !self.is_cancelled() | !self.is_failed() {
|
if !self.is_cancelled() | !self.is_failed() {
|
||||||
self.set_state(ControllerState::Running);
|
self.set_state(ControllerState::Running);
|
||||||
|
|
|
||||||
|
|
@ -22,6 +22,9 @@ use zip::AesMode::Aes256;
|
||||||
pub use self::controller::{Controller, ControllerState};
|
pub use self::controller::{Controller, ControllerState};
|
||||||
pub mod controller;
|
pub mod controller;
|
||||||
|
|
||||||
|
pub use notifiers::*;
|
||||||
|
mod notifiers;
|
||||||
|
|
||||||
pub use self::reader::OpReader;
|
pub use self::reader::OpReader;
|
||||||
pub mod reader;
|
pub mod reader;
|
||||||
|
|
||||||
|
|
@ -111,7 +114,7 @@ async fn copy_or_move(
|
||||||
);
|
);
|
||||||
|
|
||||||
// Handle duplicate file names by renaming paths
|
// Handle duplicate file names by renaming paths
|
||||||
let mut from_to_pairs: Vec<(PathBuf, PathBuf)> = paths
|
let from_to_pairs_iter = paths
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.zip(std::iter::repeat(to.as_path()))
|
.zip(std::iter::repeat(to.as_path()))
|
||||||
.filter_map(|(from, to)| {
|
.filter_map(|(from, to)| {
|
||||||
|
|
@ -129,36 +132,46 @@ async fn copy_or_move(
|
||||||
//TODO: how to handle from missing file name?
|
//TODO: how to handle from missing file name?
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
})
|
});
|
||||||
.collect();
|
|
||||||
|
|
||||||
// Attempt quick and simple renames
|
// Attempt quick and simple renames
|
||||||
//TODO: allow rename to be used for directories in recursive context?
|
//TODO: allow rename to be used for directories in recursive context?
|
||||||
if matches!(method, Method::Move { .. }) {
|
|
||||||
from_to_pairs.retain(|(from, to)| {
|
|
||||||
//TODO: show replace dialog here?
|
|
||||||
if to.exists() {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
//TODO: use compio::fs::rename?
|
let from_to_pairs: Vec<(PathBuf, PathBuf)> = if matches!(method, Method::Move { .. }) {
|
||||||
match fs::rename(from, to) {
|
from_to_pairs_iter
|
||||||
Ok(()) => {
|
.map(|(from, to)| async move {
|
||||||
log::info!("renamed {} to {}", from.display(), to.display());
|
//TODO: show replace dialog here?
|
||||||
false
|
if to.exists() {
|
||||||
|
return Some((from, to));
|
||||||
}
|
}
|
||||||
Err(err) => {
|
|
||||||
log::info!(
|
match compio::fs::rename(&from, &to).await {
|
||||||
"failed to rename {} to {}, fallback to recursive move: {}",
|
Ok(()) => {
|
||||||
from.display(),
|
log::info!("renamed {} to {}", from.display(), to.display());
|
||||||
to.display(),
|
None
|
||||||
err
|
}
|
||||||
);
|
Err(err) => {
|
||||||
true
|
log::info!(
|
||||||
|
"failed to rename {} to {}, fallback to recursive move: {}",
|
||||||
|
from.display(),
|
||||||
|
to.display(),
|
||||||
|
err
|
||||||
|
);
|
||||||
|
Some((from, to))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
})
|
||||||
});
|
.collect::<cosmic::iced::futures::stream::FuturesOrdered<_>>()
|
||||||
}
|
.fold(Vec::new(), |mut pairs, pair| async move {
|
||||||
|
if let Some(pair) = pair {
|
||||||
|
pairs.push(pair);
|
||||||
|
}
|
||||||
|
pairs
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
} else {
|
||||||
|
from_to_pairs_iter.collect()
|
||||||
|
};
|
||||||
|
|
||||||
let mut context = Context::new(controller.clone());
|
let mut context = Context::new(controller.clone());
|
||||||
|
|
||||||
|
|
@ -216,7 +229,7 @@ pub async fn sync_to_disk(
|
||||||
}
|
}
|
||||||
}))
|
}))
|
||||||
.buffer_unordered(32)
|
.buffer_unordered(32)
|
||||||
.collect::<Vec<_>>()
|
.collect::<()>()
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
// Sync directories to disk
|
// Sync directories to disk
|
||||||
|
|
@ -226,7 +239,7 @@ pub async fn sync_to_disk(
|
||||||
}
|
}
|
||||||
}))
|
}))
|
||||||
.buffer_unordered(16)
|
.buffer_unordered(16)
|
||||||
.collect::<Vec<_>>()
|
.collect::<()>()
|
||||||
.await;
|
.await;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1112,7 +1125,9 @@ impl Operation {
|
||||||
#[cfg(target_os = "macos")]
|
#[cfg(target_os = "macos")]
|
||||||
Self::Restore { .. } => {
|
Self::Restore { .. } => {
|
||||||
// TODO: add support for macos
|
// TODO: add support for macos
|
||||||
return OperationError::from_msg("Restoring from trash is not supported on macos");
|
return Err(OperationError::from_msg(
|
||||||
|
"Restoring from trash is not supported on macos",
|
||||||
|
));
|
||||||
}
|
}
|
||||||
#[cfg(not(target_os = "macos"))]
|
#[cfg(not(target_os = "macos"))]
|
||||||
Self::Restore { items } => {
|
Self::Restore { items } => {
|
||||||
|
|
|
||||||
58
src/operation/notifiers.rs
Normal file
58
src/operation/notifiers.rs
Normal file
|
|
@ -0,0 +1,58 @@
|
||||||
|
// Copyright 2026 System76 <info@system76.com>
|
||||||
|
// SPDX-License-Identifier: GPL-3.0-only
|
||||||
|
|
||||||
|
use std::path::{Path, PathBuf};
|
||||||
|
use std::sync::{Arc, LazyLock, Mutex};
|
||||||
|
use tokio::sync::Notify;
|
||||||
|
|
||||||
|
/// Monitor files which are being written to.
|
||||||
|
pub struct FileWritingNotifier {
|
||||||
|
data: Vec<PathBuf>,
|
||||||
|
notify: Arc<Notify>,
|
||||||
|
}
|
||||||
|
|
||||||
|
static ACTIVELY_WRITING: LazyLock<Mutex<FileWritingNotifier>> = LazyLock::new(|| {
|
||||||
|
Mutex::new(FileWritingNotifier {
|
||||||
|
data: Vec::new(),
|
||||||
|
notify: Arc::new(Notify::new()),
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
/// Append path that is being written to.
|
||||||
|
pub fn actively_writing_add(path: PathBuf) {
|
||||||
|
ACTIVELY_WRITING.lock().unwrap().data.push(path);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Remove path to file that has finished writing and notify waiters.
|
||||||
|
pub fn actively_writing_remove(path: &Path) {
|
||||||
|
let mut guard = ACTIVELY_WRITING.lock().unwrap();
|
||||||
|
guard.data.retain(|p| p != path);
|
||||||
|
guard.notify.notify_waiters();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Wait until the actively-writing queue is empty or a file has been removed.
|
||||||
|
pub async fn actively_writing_tick() {
|
||||||
|
let notify = (|| {
|
||||||
|
let guard = ACTIVELY_WRITING.lock().unwrap();
|
||||||
|
|
||||||
|
if !guard.data.is_empty() {
|
||||||
|
return Some(guard.notify.clone());
|
||||||
|
}
|
||||||
|
|
||||||
|
None
|
||||||
|
})();
|
||||||
|
|
||||||
|
if let Some(notify) = notify {
|
||||||
|
notify.notified().await
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Check if a file is being written to. Avoid thumbnail generation until after it is finished.
|
||||||
|
pub fn is_actively_writing_to(path: &Path) -> bool {
|
||||||
|
ACTIVELY_WRITING
|
||||||
|
.lock()
|
||||||
|
.unwrap()
|
||||||
|
.data
|
||||||
|
.iter()
|
||||||
|
.any(|p| p == path)
|
||||||
|
}
|
||||||
|
|
@ -1,15 +1,33 @@
|
||||||
use compio::BufResult;
|
// Copyright 2023 System76 <info@system76.com>
|
||||||
use compio::buf::{IntoInner, IoBuf};
|
// SPDX-License-Identifier: GPL-3.0-only
|
||||||
use compio::io::{AsyncReadAt, AsyncWriteAt};
|
|
||||||
use std::future::Future;
|
|
||||||
use std::pin::Pin;
|
|
||||||
use std::time::Instant;
|
|
||||||
use std::{cell::Cell, error::Error, fs, ops::ControlFlow, path::PathBuf, rc::Rc};
|
|
||||||
use walkdir::WalkDir;
|
|
||||||
|
|
||||||
use crate::operation::{OperationError, sync_to_disk};
|
|
||||||
|
|
||||||
use super::{Controller, OperationSelection, ReplaceResult, copy_unique_path};
|
use super::{Controller, OperationSelection, ReplaceResult, copy_unique_path};
|
||||||
|
use crate::operation::{OperationError, sync_to_disk};
|
||||||
|
use anyhow::Context as AnyhowContext;
|
||||||
|
use compio::BufResult;
|
||||||
|
use compio::buf::{IntoInner, IoBuf};
|
||||||
|
use compio::driver::{ToSharedFd, op::AsyncifyFd};
|
||||||
|
use compio::io::{AsyncReadAt, AsyncWriteAt};
|
||||||
|
use cosmic::iced::futures;
|
||||||
|
use futures::{FutureExt, StreamExt};
|
||||||
|
use std::future::Future;
|
||||||
|
use std::pin::Pin;
|
||||||
|
use std::rc::Rc;
|
||||||
|
use std::time::Instant;
|
||||||
|
use std::{cell::Cell, error::Error, fs, ops::ControlFlow, path::PathBuf};
|
||||||
|
use walkdir::WalkDir;
|
||||||
|
|
||||||
|
#[cfg(feature = "gvfs")]
|
||||||
|
use gio::prelude::FileExtManual;
|
||||||
|
|
||||||
|
#[derive(thiserror::Error, Debug)]
|
||||||
|
pub enum GioCopyError {
|
||||||
|
#[error("controller state")]
|
||||||
|
Controller(OperationError),
|
||||||
|
#[cfg(feature = "gvfs")]
|
||||||
|
#[error("gio copy failed")]
|
||||||
|
GLib(#[from] glib::Error),
|
||||||
|
}
|
||||||
|
|
||||||
pub enum Method {
|
pub enum Method {
|
||||||
Copy,
|
Copy,
|
||||||
|
|
@ -313,136 +331,28 @@ impl Op {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn run(
|
async fn run(&mut self, ctx: &mut Context, progress: Progress) -> Result<bool, Box<dyn Error>> {
|
||||||
&mut self,
|
|
||||||
ctx: &mut Context,
|
|
||||||
mut progress: Progress,
|
|
||||||
) -> Result<bool, Box<dyn Error>> {
|
|
||||||
if self.skipped.normal.get() || (self.is_cleanup && self.skipped.cleanup.get()) {
|
if self.skipped.normal.get() || (self.is_cleanup && self.skipped.cleanup.get()) {
|
||||||
return Ok(true);
|
return Ok(true);
|
||||||
}
|
}
|
||||||
match self.kind {
|
match self.kind {
|
||||||
OpKind::Copy => {
|
OpKind::Copy => {
|
||||||
// Remove `to` if overwriting and it is an existing file
|
crate::operation::actively_writing_add(self.to.clone());
|
||||||
if self.to.is_file() {
|
let result = self.copy(ctx, progress).await;
|
||||||
match ctx.replace(self).await? {
|
|
||||||
ControlFlow::Continue(to) => {
|
if result.is_err() {
|
||||||
self.to = to;
|
_ = compio::fs::remove_file(&self.to).await;
|
||||||
}
|
|
||||||
ControlFlow::Break(ret) => {
|
|
||||||
return Ok(ret);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let (from_file, metadata, mut to_file) = cosmic::iced::futures::try_join!(
|
crate::operation::actively_writing_remove(&self.to);
|
||||||
async {
|
return result;
|
||||||
compio::fs::OpenOptions::new()
|
|
||||||
.read(true)
|
|
||||||
.open(&self.from)
|
|
||||||
.await
|
|
||||||
},
|
|
||||||
compio::fs::metadata(&self.from),
|
|
||||||
// This is atomic and ensures `to` is not created by any other process
|
|
||||||
async {
|
|
||||||
compio::fs::OpenOptions::new()
|
|
||||||
.create_new(true)
|
|
||||||
.write(true)
|
|
||||||
.open(&self.to)
|
|
||||||
.await
|
|
||||||
}
|
|
||||||
)?;
|
|
||||||
|
|
||||||
progress.total_bytes = Some(metadata.len());
|
|
||||||
(ctx.on_progress)(self, &progress);
|
|
||||||
if let Err(err) = to_file.set_permissions(metadata.permissions()).await {
|
|
||||||
// This error is not propagated upwards as some filesystems do not support setting permissions
|
|
||||||
log::warn!(
|
|
||||||
"failed to set permissions for {}: {}",
|
|
||||||
self.to.display(),
|
|
||||||
err
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Prevent spamming the progress callbacks.
|
|
||||||
let mut last_progress_update = Instant::now();
|
|
||||||
// io_uring/IOCP requires transferring ownership of the buffer to the kernel.
|
|
||||||
let mut buf_in = std::mem::take(&mut ctx.buf);
|
|
||||||
// Track where the current read/write position is at.
|
|
||||||
let mut pos = 0;
|
|
||||||
|
|
||||||
loop {
|
|
||||||
let BufResult(result, buf_out) = from_file.read_at(buf_in, pos).await;
|
|
||||||
|
|
||||||
let count = match result {
|
|
||||||
Ok(0) => {
|
|
||||||
ctx.buf = buf_out;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
Ok(count) => count,
|
|
||||||
Err(why) => {
|
|
||||||
ctx.buf = buf_out;
|
|
||||||
return Err(why.into());
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
let BufResult(result, buf_out_slice) =
|
|
||||||
to_file.write_at(buf_out.slice(..count), pos).await;
|
|
||||||
let buf_out = buf_out_slice.into_inner();
|
|
||||||
|
|
||||||
if let Err(why) = result {
|
|
||||||
ctx.buf = buf_out;
|
|
||||||
return Err(why.into());
|
|
||||||
}
|
|
||||||
|
|
||||||
progress.current_bytes += count as u64;
|
|
||||||
pos += count as u64;
|
|
||||||
|
|
||||||
// Avoid spamming progress messages too early.
|
|
||||||
let current = Instant::now();
|
|
||||||
if current.duration_since(last_progress_update).as_millis() > 49 {
|
|
||||||
last_progress_update = current;
|
|
||||||
(ctx.on_progress)(self, &progress);
|
|
||||||
|
|
||||||
// Also check if the progress was cancelled.
|
|
||||||
if let Err(state) = ctx.controller.check().await {
|
|
||||||
ctx.buf = buf_out;
|
|
||||||
return Err(OperationError::from_state(state, &ctx.controller).into());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
buf_in = buf_out;
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut times = fs::FileTimes::new();
|
|
||||||
{
|
|
||||||
use std::os::unix::prelude::MetadataExt;
|
|
||||||
log::info!("{}", metadata.mtime());
|
|
||||||
}
|
|
||||||
if let Ok(time) = metadata.modified() {
|
|
||||||
times = times.set_modified(time);
|
|
||||||
}
|
|
||||||
if let Ok(time) = metadata.accessed() {
|
|
||||||
times = times.set_accessed(time);
|
|
||||||
}
|
|
||||||
//TODO: upstream set_times implementation to compio?
|
|
||||||
{
|
|
||||||
use compio::driver::{ToSharedFd, op::AsyncifyFd};
|
|
||||||
let op =
|
|
||||||
AsyncifyFd::new(to_file.to_shared_fd(), move |file: &std::fs::File| {
|
|
||||||
BufResult(file.set_times(times).map(|_| 0), ())
|
|
||||||
});
|
|
||||||
match compio::runtime::submit(op).await.0.map(|_| ()) {
|
|
||||||
Ok(()) => {
|
|
||||||
log::info!("set times for {} to {:?}", self.to.display(), times);
|
|
||||||
}
|
|
||||||
Err(err) => {
|
|
||||||
log::warn!("failed to set times for {}: {}", self.to.display(), err);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
OpKind::Move { cross_device_copy } => {
|
OpKind::Move { cross_device_copy } => {
|
||||||
|
// Do not clean up if cross_device_copy is set
|
||||||
|
if cross_device_copy {
|
||||||
|
self.skipped.cleanup.set(true);
|
||||||
|
}
|
||||||
|
|
||||||
// Remove `to` if overwriting and it is an existing file
|
// Remove `to` if overwriting and it is an existing file
|
||||||
if self.to.is_file() {
|
if self.to.is_file() {
|
||||||
match ctx.replace(self).await? {
|
match ctx.replace(self).await? {
|
||||||
|
|
@ -520,4 +430,268 @@ impl Op {
|
||||||
}
|
}
|
||||||
Ok(true)
|
Ok(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn copy(
|
||||||
|
&mut self,
|
||||||
|
ctx: &mut Context,
|
||||||
|
mut progress: Progress,
|
||||||
|
) -> Result<bool, Box<dyn Error>> {
|
||||||
|
// Remove `to` if overwriting and it is an existing file
|
||||||
|
if self.to.is_file() {
|
||||||
|
match ctx.replace(self).await? {
|
||||||
|
ControlFlow::Continue(to) => {
|
||||||
|
self.to = to;
|
||||||
|
}
|
||||||
|
ControlFlow::Break(ret) => {
|
||||||
|
return Ok(ret);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let (from_file, metadata, to_file) = cosmic::iced::futures::join!(
|
||||||
|
async {
|
||||||
|
compio::fs::OpenOptions::new()
|
||||||
|
.read(true)
|
||||||
|
.open(&self.from)
|
||||||
|
.await
|
||||||
|
.with_context(|| format!("failed to open {} for reading", self.from.display(),))
|
||||||
|
},
|
||||||
|
async { compio::fs::metadata(&self.from).await.ok() },
|
||||||
|
// This is atomic and ensures `to` is not created by any other process
|
||||||
|
async {
|
||||||
|
compio::fs::OpenOptions::new()
|
||||||
|
.create_new(true)
|
||||||
|
.write(true)
|
||||||
|
.open(&self.to)
|
||||||
|
.await
|
||||||
|
.with_context(|| format!("failed to open {} for writing", self.to.display()))
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
let from_file = from_file?;
|
||||||
|
let mut to_file = to_file?;
|
||||||
|
progress.total_bytes = metadata.as_ref().map(|m| m.len());
|
||||||
|
(ctx.on_progress)(self, &progress);
|
||||||
|
|
||||||
|
if let Some(metadata) = metadata.as_ref() {
|
||||||
|
if let Err(why) = to_file.set_permissions(metadata.permissions()).await {
|
||||||
|
// This error is not propagated upwards as some filesystems do not support setting permissions
|
||||||
|
if !matches!(why.kind(), std::io::ErrorKind::Unsupported) {
|
||||||
|
tracing::warn!(?why, "failed to set permissions for {}", self.to.display(),);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prevent spamming the progress callbacks.
|
||||||
|
let mut last_progress_update = Instant::now();
|
||||||
|
// io_uring/IOCP requires transferring ownership of the buffer to the kernel.
|
||||||
|
let mut buf_in = std::mem::take(&mut ctx.buf);
|
||||||
|
// Track where the current read/write position is at.
|
||||||
|
let mut pos = 0;
|
||||||
|
|
||||||
|
loop {
|
||||||
|
let BufResult(result, buf_out) = from_file.read_at(buf_in, pos).await;
|
||||||
|
|
||||||
|
let count = match result {
|
||||||
|
Ok(0) => {
|
||||||
|
buf_in = buf_out;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
Ok(count) => count,
|
||||||
|
Err(why) => {
|
||||||
|
ctx.buf = buf_out;
|
||||||
|
tracing::error!("failed to read: {:?}", why);
|
||||||
|
_ = futures::future::join(from_file.close(), to_file.close()).await;
|
||||||
|
return Err(why).context("failed to read")?;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let BufResult(result, buf_out_slice) =
|
||||||
|
to_file.write_at(buf_out.slice(..count), pos).await;
|
||||||
|
let buf_out = buf_out_slice.into_inner();
|
||||||
|
|
||||||
|
if let Err(why) = result {
|
||||||
|
#[cfg(feature = "gvfs")]
|
||||||
|
if let std::io::ErrorKind::Unsupported = why.kind() {
|
||||||
|
ctx.buf = buf_out;
|
||||||
|
_ = futures::future::join(from_file.close(), to_file.close()).await;
|
||||||
|
return self
|
||||||
|
.gio_file_copy(ctx, progress)
|
||||||
|
.await
|
||||||
|
.map(|_| true)
|
||||||
|
.map_err(Into::into);
|
||||||
|
}
|
||||||
|
|
||||||
|
tracing::error!("failed to write: {:?}", why);
|
||||||
|
ctx.buf = buf_out;
|
||||||
|
_ = futures::future::join(from_file.close(), to_file.close()).await;
|
||||||
|
return Err(why).context("failed to write")?;
|
||||||
|
}
|
||||||
|
|
||||||
|
progress.current_bytes += count as u64;
|
||||||
|
pos += count as u64;
|
||||||
|
|
||||||
|
// Avoid spamming progress messages too early.
|
||||||
|
let current = Instant::now();
|
||||||
|
if current.duration_since(last_progress_update).as_millis() > 49 {
|
||||||
|
last_progress_update = current;
|
||||||
|
(ctx.on_progress)(self, &progress);
|
||||||
|
|
||||||
|
// Also check if the progress was cancelled.
|
||||||
|
if let Err(state) = ctx.controller.check().await {
|
||||||
|
ctx.buf = buf_out;
|
||||||
|
tracing::warn!(
|
||||||
|
"operation to copy from {:?} to {:?} cancelled",
|
||||||
|
self.from,
|
||||||
|
self.to
|
||||||
|
);
|
||||||
|
_ = futures::future::join(from_file.close(), to_file.close()).await;
|
||||||
|
return Err(OperationError::from_state(state, &ctx.controller).into());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
buf_in = buf_out;
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.buf = buf_in;
|
||||||
|
|
||||||
|
if let Some(metadata) = metadata.as_ref() {
|
||||||
|
let mut times = fs::FileTimes::new();
|
||||||
|
if let Ok(time) = metadata.modified() {
|
||||||
|
times = times.set_modified(time);
|
||||||
|
}
|
||||||
|
if let Ok(time) = metadata.accessed() {
|
||||||
|
times = times.set_accessed(time);
|
||||||
|
}
|
||||||
|
//TODO: upstream set_times implementation to compio?
|
||||||
|
let op = AsyncifyFd::new(to_file.to_shared_fd(), move |file: &std::fs::File| {
|
||||||
|
BufResult(file.set_times(times).map(|_| 0), ())
|
||||||
|
});
|
||||||
|
match compio::runtime::submit(op).await.0.map(|_| ()) {
|
||||||
|
Ok(()) => {
|
||||||
|
tracing::info!("set times for {} to {:?}", self.to.display(), times);
|
||||||
|
}
|
||||||
|
Err(why) => {
|
||||||
|
if !matches!(why.kind(), std::io::ErrorKind::Unsupported) {
|
||||||
|
tracing::error!(?why, "failed to set times for {}", self.to.display());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_ = to_file.close().await;
|
||||||
|
|
||||||
|
Ok(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Fallback mechanism in the event that unsupported I/O error errors occur.
|
||||||
|
/// Fixes unsupported errors when copying large files over MTP.
|
||||||
|
/// TODO: Find what Gio.File does to work around this.
|
||||||
|
#[cfg(feature = "gvfs")]
|
||||||
|
async fn gio_file_copy(
|
||||||
|
&self,
|
||||||
|
ctx: &mut Context,
|
||||||
|
mut progress: Progress,
|
||||||
|
) -> Result<(), GioCopyError> {
|
||||||
|
_ = compio::fs::remove_file(&self.to).await;
|
||||||
|
|
||||||
|
let from = gio::File::for_path(&self.from);
|
||||||
|
let to = gio::File::for_path(&self.to);
|
||||||
|
let (progress_tx, mut progress_rx) = tokio::sync::mpsc::unbounded_channel();
|
||||||
|
let (tx, rx) = tokio::sync::oneshot::channel();
|
||||||
|
let (pause_tx, mut pause_rx) = tokio::sync::watch::channel(false);
|
||||||
|
|
||||||
|
let task = compio::runtime::spawn_blocking(move || {
|
||||||
|
let glib_context = glib::MainContext::new();
|
||||||
|
let glib_loop = glib::MainLoop::new(Some(&glib_context), false);
|
||||||
|
glib_context.with_thread_default(move || {
|
||||||
|
let glib_loop2 = glib_loop.clone();
|
||||||
|
glib::MainContext::ref_thread_default().spawn_local(async move {
|
||||||
|
// Create a future for copying the file with `gio::File`. This also creates a progress stream.
|
||||||
|
let (gio_copy_fut, mut progress_stream) = from.copy_future(
|
||||||
|
&to,
|
||||||
|
gio::FileCopyFlags::OVERWRITE | gio::FileCopyFlags::ALL_METADATA,
|
||||||
|
glib::Priority::LOW,
|
||||||
|
);
|
||||||
|
|
||||||
|
let mut copy_fut = gio_copy_fut
|
||||||
|
.map(|result| result.map_err(GioCopyError::GLib))
|
||||||
|
.fuse();
|
||||||
|
|
||||||
|
let progress_fut = std::pin::pin!(async {
|
||||||
|
while let Some((current_bytes, _)) = progress_stream.next().await {
|
||||||
|
_ = progress_tx.send(current_bytes);
|
||||||
|
}
|
||||||
|
|
||||||
|
drop(progress_tx);
|
||||||
|
futures::future::pending::<()>().await;
|
||||||
|
});
|
||||||
|
|
||||||
|
let mut progress_fut = progress_fut.fuse();
|
||||||
|
let mut pause_rx2 = pause_rx.clone();
|
||||||
|
|
||||||
|
loop {
|
||||||
|
let until_paused = std::pin::pin!(pause_rx.wait_for(|paused| *paused));
|
||||||
|
futures::select! {
|
||||||
|
_ = &mut progress_fut => {},
|
||||||
|
|
||||||
|
result = &mut copy_fut => {
|
||||||
|
_ = tx.send(result.map(|_| ()));
|
||||||
|
glib_loop2.quit();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_ = until_paused.fuse() => {
|
||||||
|
_ = pause_rx2.wait_for(|paused| !*paused).await;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
glib_loop.run();
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
let mut last_progress_update = Instant::now();
|
||||||
|
let mut task = task.fuse();
|
||||||
|
let mut rx = rx.fuse();
|
||||||
|
|
||||||
|
loop {
|
||||||
|
let until_paused = std::pin::pin!(ctx.controller.until_paused());
|
||||||
|
futures::select! {
|
||||||
|
value = progress_rx.recv().fuse() => {
|
||||||
|
if let Some(current_bytes) = value {
|
||||||
|
progress.current_bytes = current_bytes as u64;
|
||||||
|
let current = Instant::now();
|
||||||
|
if current.duration_since(last_progress_update).as_millis() > 49 {
|
||||||
|
last_progress_update = current;
|
||||||
|
(ctx.on_progress)(self, &progress);
|
||||||
|
// Also check if the progress was cancelled.
|
||||||
|
if let Err(state) = ctx.controller.check().await {
|
||||||
|
tracing::warn!(
|
||||||
|
"operation to copy from {:?} to {:?} cancelled",
|
||||||
|
self.from,
|
||||||
|
self.to
|
||||||
|
);
|
||||||
|
return Err::<(), GioCopyError>(GioCopyError::Controller(
|
||||||
|
OperationError::from_state(state, &ctx.controller),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
result = rx => return result.unwrap(),
|
||||||
|
|
||||||
|
_ = task => (),
|
||||||
|
|
||||||
|
_ = until_paused.fuse() => {
|
||||||
|
// Pauses an active copy while the controller state is paused.
|
||||||
|
_ = pause_tx.send(true);
|
||||||
|
ctx.controller.until_unpaused().await;
|
||||||
|
_ = pause_tx.send(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
241
src/tab.rs
241
src/tab.rs
|
|
@ -1,7 +1,7 @@
|
||||||
|
#[cfg(feature = "desktop")]
|
||||||
|
use cosmic::desktop::fde::{DesktopEntry, get_languages_from_env};
|
||||||
use cosmic::{
|
use cosmic::{
|
||||||
Apply, Element, cosmic_theme,
|
Apply, Element, cosmic_theme, font,
|
||||||
desktop::fde::{DesktopEntry, get_languages_from_env},
|
|
||||||
font,
|
|
||||||
iced::core::{mouse::ScrollDelta, widget::tree},
|
iced::core::{mouse::ScrollDelta, widget::tree},
|
||||||
iced::{
|
iced::{
|
||||||
Alignment, Border, Color, ContentFit, Length, Point, Rectangle, Size, Subscription, Vector,
|
Alignment, Border, Color, ContentFit, Length, Point, Rectangle, Size, Subscription, Vector,
|
||||||
|
|
@ -39,9 +39,10 @@ use icu::{
|
||||||
use image::{DynamicImage, ImageReader};
|
use image::{DynamicImage, ImageReader};
|
||||||
use jiff_icu::ConvertFrom;
|
use jiff_icu::ConvertFrom;
|
||||||
use mime_guess::{Mime, mime};
|
use mime_guess::{Mime, mime};
|
||||||
use regex::Regex;
|
|
||||||
use rustc_hash::FxHashMap;
|
use rustc_hash::FxHashMap;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
#[cfg(unix)]
|
||||||
|
use std::os::unix::fs::MetadataExt;
|
||||||
use std::{
|
use std::{
|
||||||
borrow::Cow,
|
borrow::Cow,
|
||||||
cell::Cell,
|
cell::Cell,
|
||||||
|
|
@ -52,7 +53,6 @@ use std::{
|
||||||
fs::{self, File, Metadata},
|
fs::{self, File, Metadata},
|
||||||
hash::Hash,
|
hash::Hash,
|
||||||
io::{BufRead, BufReader},
|
io::{BufRead, BufReader},
|
||||||
os::unix::fs::MetadataExt,
|
|
||||||
path::{self, Path, PathBuf},
|
path::{self, Path, PathBuf},
|
||||||
sync::{Arc, LazyLock, RwLock, atomic},
|
sync::{Arc, LazyLock, RwLock, atomic},
|
||||||
time::{Duration, Instant, SystemTime},
|
time::{Duration, Instant, SystemTime},
|
||||||
|
|
@ -84,8 +84,8 @@ use crate::{
|
||||||
operation::{Controller, OperationError},
|
operation::{Controller, OperationError},
|
||||||
thumbnail_cacher::{CachedThumbnail, ThumbnailCacher, ThumbnailSize},
|
thumbnail_cacher::{CachedThumbnail, ThumbnailCacher, ThumbnailSize},
|
||||||
thumbnailer::thumbnailer,
|
thumbnailer::thumbnailer,
|
||||||
|
trash::{Trash, TrashExt},
|
||||||
};
|
};
|
||||||
use uzers::{get_group_by_gid, get_user_by_uid};
|
|
||||||
|
|
||||||
pub const DOUBLE_CLICK_DURATION: Duration = Duration::from_millis(500);
|
pub const DOUBLE_CLICK_DURATION: Duration = Duration::from_millis(500);
|
||||||
pub const HOVER_DURATION: Duration = Duration::from_millis(1600);
|
pub const HOVER_DURATION: Duration = Duration::from_millis(1600);
|
||||||
|
|
@ -557,6 +557,12 @@ pub fn fs_kind(_metadata: &Metadata) -> FsKind {
|
||||||
FsKind::Local
|
FsKind::Local
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(not(feature = "desktop"))]
|
||||||
|
fn get_desktop_file_display_name(path: &Path) -> Option<String> {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "desktop")]
|
||||||
fn get_desktop_file_display_name(path: &Path) -> Option<String> {
|
fn get_desktop_file_display_name(path: &Path) -> Option<String> {
|
||||||
let locales = get_languages_from_env();
|
let locales = get_languages_from_env();
|
||||||
let entry = match DesktopEntry::from_path(path, Some(&locales)) {
|
let entry = match DesktopEntry::from_path(path, Some(&locales)) {
|
||||||
|
|
@ -570,6 +576,12 @@ fn get_desktop_file_display_name(path: &Path) -> Option<String> {
|
||||||
entry.name(&locales).map(|s| s.into_owned())
|
entry.name(&locales).map(|s| s.into_owned())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(not(feature = "desktop"))]
|
||||||
|
fn get_desktop_file_icon(path: &Path) -> Option<String> {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "desktop")]
|
||||||
fn get_desktop_file_icon(path: &Path) -> Option<String> {
|
fn get_desktop_file_icon(path: &Path) -> Option<String> {
|
||||||
let entry = match DesktopEntry::from_path::<&str>(path, None) {
|
let entry = match DesktopEntry::from_path::<&str>(path, None) {
|
||||||
Ok(ok) => ok,
|
Ok(ok) => ok,
|
||||||
|
|
@ -593,6 +605,7 @@ fn desktop_icon_handle(icon: &str, size: u16) -> widget::icon::Handle {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "desktop")]
|
||||||
pub fn parse_desktop_file(path: &Path) -> (Option<String>, Option<String>) {
|
pub fn parse_desktop_file(path: &Path) -> (Option<String>, Option<String>) {
|
||||||
let locales = get_languages_from_env();
|
let locales = get_languages_from_env();
|
||||||
let entry = match DesktopEntry::from_path(path, Some(&locales)) {
|
let entry = match DesktopEntry::from_path(path, Some(&locales)) {
|
||||||
|
|
@ -708,6 +721,9 @@ pub fn item_from_gvfs_info(path: PathBuf, file_info: gio::FileInfo, sizes: IconS
|
||||||
children_opt,
|
children_opt,
|
||||||
},
|
},
|
||||||
hidden,
|
hidden,
|
||||||
|
image_dimensions: (!remote && mime.type_() == mime::IMAGE)
|
||||||
|
.then(|| image::image_dimensions(&path).ok())
|
||||||
|
.flatten(),
|
||||||
location_opt: Some(Location::Path(path)),
|
location_opt: Some(Location::Path(path)),
|
||||||
mime,
|
mime,
|
||||||
icon_handle_grid,
|
icon_handle_grid,
|
||||||
|
|
@ -843,6 +859,7 @@ pub fn item_from_entry(
|
||||||
},
|
},
|
||||||
hidden,
|
hidden,
|
||||||
location_opt: Some(Location::Path(path)),
|
location_opt: Some(Location::Path(path)),
|
||||||
|
image_dimensions: None,
|
||||||
mime,
|
mime,
|
||||||
icon_handle_grid,
|
icon_handle_grid,
|
||||||
icon_handle_list,
|
icon_handle_list,
|
||||||
|
|
@ -896,6 +913,9 @@ pub fn item_from_trash_entry(
|
||||||
metadata: ItemMetadata::Trash { metadata, entry },
|
metadata: ItemMetadata::Trash { metadata, entry },
|
||||||
hidden: false,
|
hidden: false,
|
||||||
location_opt: None,
|
location_opt: None,
|
||||||
|
image_dimensions: (mime.type_() == mime::IMAGE)
|
||||||
|
.then(|| image::image_dimensions(&original_path).ok())
|
||||||
|
.flatten(),
|
||||||
mime,
|
mime,
|
||||||
icon_handle_grid,
|
icon_handle_grid,
|
||||||
icon_handle_list,
|
icon_handle_list,
|
||||||
|
|
@ -1161,133 +1181,7 @@ pub fn scan_search<F: Fn(SearchItem) -> bool + Sync>(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
SearchLocation::Trash => {
|
SearchLocation::Trash => {
|
||||||
trash_helpers::scan_search_trash(callback, ®ex);
|
Trash::scan_search(callback, ®ex);
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// This config statement is from trash::os_limited, inverted
|
|
||||||
#[cfg(not(any(
|
|
||||||
target_os = "windows",
|
|
||||||
all(
|
|
||||||
unix,
|
|
||||||
not(target_os = "macos"),
|
|
||||||
not(target_os = "ios"),
|
|
||||||
not(target_os = "android")
|
|
||||||
)
|
|
||||||
)))]
|
|
||||||
mod trash_helpers {
|
|
||||||
use super::*;
|
|
||||||
|
|
||||||
pub fn trash_entries() -> usize {
|
|
||||||
0
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn trash_icon(icon_size: u16) -> widget::icon::Handle {
|
|
||||||
widget::icon::from_name("user-trash")
|
|
||||||
.size(icon_size)
|
|
||||||
.handle()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn trash_icon_symbolic(icon_size: u16) -> widget::icon::Handle {
|
|
||||||
widget::icon::from_name("user-trash-symbolic")
|
|
||||||
.size(icon_size)
|
|
||||||
.handle()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn scan_trash(_sizes: IconSizes) -> Vec<Item> {
|
|
||||||
log::warn!("viewing trash not supported on this platform");
|
|
||||||
Vec::new()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn scan_search_trash<F: Fn(SearchItem) -> bool + Sync>(callback: F, regex: &Regex) {}
|
|
||||||
}
|
|
||||||
|
|
||||||
// This config statement is from trash::os_limited
|
|
||||||
#[cfg(any(
|
|
||||||
target_os = "windows",
|
|
||||||
all(
|
|
||||||
unix,
|
|
||||||
not(target_os = "macos"),
|
|
||||||
not(target_os = "ios"),
|
|
||||||
not(target_os = "android")
|
|
||||||
)
|
|
||||||
))]
|
|
||||||
pub mod trash_helpers {
|
|
||||||
use super::*;
|
|
||||||
|
|
||||||
pub fn trash_entries() -> usize {
|
|
||||||
match trash::os_limited::list() {
|
|
||||||
Ok(entries) => entries.len(),
|
|
||||||
Err(_err) => 0,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn trash_icon(icon_size: u16) -> widget::icon::Handle {
|
|
||||||
widget::icon::from_name(if trash::os_limited::is_empty().unwrap_or(true) {
|
|
||||||
"user-trash"
|
|
||||||
} else {
|
|
||||||
"user-trash-full"
|
|
||||||
})
|
|
||||||
.size(icon_size)
|
|
||||||
.handle()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn trash_icon_symbolic(icon_size: u16) -> widget::icon::Handle {
|
|
||||||
widget::icon::from_name(if trash::os_limited::is_empty().unwrap_or(true) {
|
|
||||||
"user-trash-symbolic"
|
|
||||||
} else {
|
|
||||||
"user-trash-full-symbolic"
|
|
||||||
})
|
|
||||||
.size(icon_size)
|
|
||||||
.handle()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn scan_trash(sizes: IconSizes) -> Vec<Item> {
|
|
||||||
let entries = match trash::os_limited::list() {
|
|
||||||
Ok(entry) => entry,
|
|
||||||
Err(err) => {
|
|
||||||
log::warn!("failed to read trash items: {err}");
|
|
||||||
return Vec::new();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
let mut items: Vec<_> = entries
|
|
||||||
.into_iter()
|
|
||||||
.filter_map(|entry| {
|
|
||||||
let metadata = trash::os_limited::metadata(&entry)
|
|
||||||
.inspect_err(|err| {
|
|
||||||
log::warn!("failed to get metadata for trash item {entry:?}: {err}")
|
|
||||||
})
|
|
||||||
.ok()?;
|
|
||||||
Some(item_from_trash_entry(entry, metadata, sizes))
|
|
||||||
})
|
|
||||||
.collect();
|
|
||||||
items.sort_by(|a, b| match (a.metadata.is_dir(), b.metadata.is_dir()) {
|
|
||||||
(true, false) => Ordering::Less,
|
|
||||||
(false, true) => Ordering::Greater,
|
|
||||||
_ => LANGUAGE_SORTER.compare(&a.display_name, &b.display_name),
|
|
||||||
});
|
|
||||||
items
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn scan_search_trash<F: Fn(SearchItem) -> bool + Sync>(callback: F, regex: &Regex) {
|
|
||||||
let entries = match trash::os_limited::list() {
|
|
||||||
Ok(entries) => entries,
|
|
||||||
Err(err) => {
|
|
||||||
log::warn!("failed to read trash items: {err}");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
for entry in entries {
|
|
||||||
if let Ok(metadata) = trash::os_limited::metadata(&entry).inspect_err(|err| {
|
|
||||||
log::warn!("failed to get metadata for trash item {entry:?}: {err}")
|
|
||||||
}) {
|
|
||||||
let name = entry.name.to_string_lossy();
|
|
||||||
if regex.is_match(&name) && !callback(SearchItem::Trash(entry, metadata)) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1425,15 +1319,15 @@ pub fn scan_desktop(
|
||||||
let display_name = Item::display_name(&name);
|
let display_name = Item::display_name(&name);
|
||||||
|
|
||||||
let metadata = ItemMetadata::SimpleDir {
|
let metadata = ItemMetadata::SimpleDir {
|
||||||
entries: trash_helpers::trash_entries() as u64,
|
entries: Trash::entries() as u64,
|
||||||
};
|
};
|
||||||
|
|
||||||
let (mime, icon_handle_grid, icon_handle_list, icon_handle_list_condensed) = {
|
let (mime, icon_handle_grid, icon_handle_list, icon_handle_list_condensed) = {
|
||||||
(
|
(
|
||||||
"inode/directory".parse().unwrap(),
|
"inode/directory".parse().unwrap(),
|
||||||
trash_helpers::trash_icon(sizes.grid()),
|
Trash::icon(sizes.grid()),
|
||||||
trash_helpers::trash_icon(sizes.list()),
|
Trash::icon(sizes.list()),
|
||||||
trash_helpers::trash_icon(sizes.list_condensed()),
|
Trash::icon(sizes.list_condensed()),
|
||||||
)
|
)
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -1444,6 +1338,7 @@ pub fn scan_desktop(
|
||||||
metadata,
|
metadata,
|
||||||
hidden: false,
|
hidden: false,
|
||||||
location_opt: Some(Location::Trash),
|
location_opt: Some(Location::Trash),
|
||||||
|
image_dimensions: None,
|
||||||
mime,
|
mime,
|
||||||
icon_handle_grid,
|
icon_handle_grid,
|
||||||
icon_handle_list,
|
icon_handle_list,
|
||||||
|
|
@ -1673,7 +1568,7 @@ impl Location {
|
||||||
// Search is done incrementally
|
// Search is done incrementally
|
||||||
Vec::new()
|
Vec::new()
|
||||||
}
|
}
|
||||||
Self::Trash => trash_helpers::scan_trash(sizes),
|
Self::Trash => Trash::scan(sizes),
|
||||||
Self::Recents => scan_recents(sizes),
|
Self::Recents => scan_recents(sizes),
|
||||||
Self::Network(uri, _, _) => scan_network(uri, sizes),
|
Self::Network(uri, _, _) => scan_network(uri, sizes),
|
||||||
};
|
};
|
||||||
|
|
@ -2319,6 +2214,7 @@ pub struct Item {
|
||||||
pub hidden: bool,
|
pub hidden: bool,
|
||||||
pub location_opt: Option<Location>,
|
pub location_opt: Option<Location>,
|
||||||
pub mime: Mime,
|
pub mime: Mime,
|
||||||
|
pub image_dimensions: Option<(u32, u32)>,
|
||||||
pub icon_handle_grid: widget::icon::Handle,
|
pub icon_handle_grid: widget::icon::Handle,
|
||||||
pub icon_handle_list: widget::icon::Handle,
|
pub icon_handle_list: widget::icon::Handle,
|
||||||
pub icon_handle_list_condensed: widget::icon::Handle,
|
pub icon_handle_list_condensed: widget::icon::Handle,
|
||||||
|
|
@ -2533,7 +2429,7 @@ impl Item {
|
||||||
|
|
||||||
let mode = metadata.mode();
|
let mode = metadata.mode();
|
||||||
|
|
||||||
let user_name = get_user_by_uid(metadata.uid())
|
let user_name = uzers::get_user_by_uid(metadata.uid())
|
||||||
.and_then(|user| user.name().to_str().map(ToOwned::to_owned))
|
.and_then(|user| user.name().to_str().map(ToOwned::to_owned))
|
||||||
.unwrap_or_default();
|
.unwrap_or_default();
|
||||||
let user_path = path.clone();
|
let user_path = path.clone();
|
||||||
|
|
@ -2556,7 +2452,7 @@ impl Item {
|
||||||
)),
|
)),
|
||||||
);
|
);
|
||||||
|
|
||||||
let group_name = get_group_by_gid(metadata.gid())
|
let group_name = uzers::get_group_by_gid(metadata.gid())
|
||||||
.and_then(|group| group.name().to_str().map(ToOwned::to_owned))
|
.and_then(|group| group.name().to_str().map(ToOwned::to_owned))
|
||||||
.unwrap_or_default();
|
.unwrap_or_default();
|
||||||
let group_path = path.clone();
|
let group_path = path.clone();
|
||||||
|
|
@ -4444,6 +4340,7 @@ impl Tab {
|
||||||
for item in self.items_opt().map_or(Vec::new(), |items| {
|
for item in self.items_opt().map_or(Vec::new(), |items| {
|
||||||
items.iter().filter(|item| item.selected).collect()
|
items.iter().filter(|item| item.selected).collect()
|
||||||
}) {
|
}) {
|
||||||
|
#[cfg(unix)]
|
||||||
if let (Some(path), Some(mode)) = (
|
if let (Some(path), Some(mode)) = (
|
||||||
item.path_opt(),
|
item.path_opt(),
|
||||||
item.file_metadata()
|
item.file_metadata()
|
||||||
|
|
@ -6461,20 +6358,23 @@ impl Tab {
|
||||||
} else {
|
} else {
|
||||||
total_size = total_size.saturating_add(metadata.len());
|
total_size = total_size.saturating_add(metadata.len());
|
||||||
}
|
}
|
||||||
let mode = metadata.mode();
|
#[cfg(unix)]
|
||||||
user_name.insert(
|
{
|
||||||
get_user_by_uid(metadata.uid())
|
let mode = metadata.mode();
|
||||||
.and_then(|user| user.name().to_str().map(ToOwned::to_owned))
|
user_name.insert(
|
||||||
.unwrap_or_default(),
|
uzers::get_user_by_uid(metadata.uid())
|
||||||
);
|
.and_then(|user| user.name().to_str().map(ToOwned::to_owned))
|
||||||
mode_user.insert(get_mode_part(mode, MODE_SHIFT_USER));
|
.unwrap_or_default(),
|
||||||
group_name.insert(
|
);
|
||||||
get_group_by_gid(metadata.gid())
|
mode_user.insert(get_mode_part(mode, MODE_SHIFT_USER));
|
||||||
.and_then(|group| group.name().to_str().map(ToOwned::to_owned))
|
group_name.insert(
|
||||||
.unwrap_or_default(),
|
uzers::get_group_by_gid(metadata.gid())
|
||||||
);
|
.and_then(|group| group.name().to_str().map(ToOwned::to_owned))
|
||||||
mode_group.insert(get_mode_part(mode, MODE_SHIFT_GROUP));
|
.unwrap_or_default(),
|
||||||
mode_other.insert(get_mode_part(mode, MODE_SHIFT_OTHER));
|
);
|
||||||
|
mode_group.insert(get_mode_part(mode, MODE_SHIFT_GROUP));
|
||||||
|
mode_other.insert(get_mode_part(mode, MODE_SHIFT_OTHER));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let mut mime_types: Vec<(String, u64)> = mime_type_counts.into_iter().collect();
|
let mut mime_types: Vec<(String, u64)> = mime_type_counts.into_iter().collect();
|
||||||
|
|
@ -6713,13 +6613,13 @@ impl Tab {
|
||||||
|
|
||||||
// Determine effective memory budget based on image size
|
// Determine effective memory budget based on image size
|
||||||
let (effective_max_mb, effective_jobs) = if mime.type_() == mime::IMAGE {
|
let (effective_max_mb, effective_jobs) = if mime.type_() == mime::IMAGE {
|
||||||
match image::image_dimensions(&path) {
|
match item.image_dimensions {
|
||||||
Ok((width, height)) => {
|
Some((width, height)) => {
|
||||||
let (_use_dedicated, eff_mb, eff_jobs) =
|
let (_use_dedicated, eff_mb, eff_jobs) =
|
||||||
should_use_dedicated_worker(width, height, max_mb, max_jobs);
|
should_use_dedicated_worker(width, height, max_mb, max_jobs);
|
||||||
(eff_mb, eff_jobs)
|
(eff_mb, eff_jobs)
|
||||||
}
|
}
|
||||||
Err(_) => (max_mb, max_jobs),
|
None => (max_mb, max_jobs),
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
(max_mb, max_jobs)
|
(max_mb, max_jobs)
|
||||||
|
|
@ -6762,6 +6662,10 @@ impl Tab {
|
||||||
stream::channel(
|
stream::channel(
|
||||||
1,
|
1,
|
||||||
move |mut output: futures::channel::mpsc::Sender<_>| async move {
|
move |mut output: futures::channel::mpsc::Sender<_>| async move {
|
||||||
|
while crate::operation::is_actively_writing_to(&path) {
|
||||||
|
crate::operation::actively_writing_tick().await;
|
||||||
|
}
|
||||||
|
|
||||||
let message = {
|
let message = {
|
||||||
let path = path.clone();
|
let path = path.clone();
|
||||||
|
|
||||||
|
|
@ -6946,9 +6850,8 @@ impl Tab {
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
let output = Arc::new(tokio::sync::Mutex::new(output));
|
let (watch_tx, mut watch_rx) = tokio::sync::watch::channel(true);
|
||||||
{
|
{
|
||||||
let output = output.clone();
|
|
||||||
tokio::task::spawn_blocking(move || {
|
tokio::task::spawn_blocking(move || {
|
||||||
scan_search(
|
scan_search(
|
||||||
&search_location,
|
&search_location,
|
||||||
|
|
@ -6976,14 +6879,7 @@ impl Tab {
|
||||||
true
|
true
|
||||||
} else {
|
} else {
|
||||||
// Wake up update method
|
// Wake up update method
|
||||||
futures::executor::block_on(async {
|
watch_tx.send(false).is_ok()
|
||||||
output
|
|
||||||
.lock()
|
|
||||||
.await
|
|
||||||
.send(Message::SearchReady(false))
|
|
||||||
.await
|
|
||||||
})
|
|
||||||
.is_ok()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Err(_) => false,
|
Err(_) => false,
|
||||||
|
|
@ -6996,13 +6892,16 @@ impl Tab {
|
||||||
search_location,
|
search_location,
|
||||||
start.elapsed(),
|
start.elapsed(),
|
||||||
);
|
);
|
||||||
})
|
});
|
||||||
.await
|
}
|
||||||
.unwrap();
|
|
||||||
|
while watch_rx.changed().await.is_ok() {
|
||||||
|
let is_ready = *watch_rx.borrow_and_update();
|
||||||
|
let _ = output.send(Message::SearchReady(is_ready)).await;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Send final ready
|
// Send final ready
|
||||||
let _ = output.lock().await.send(Message::SearchReady(true)).await;
|
let _ = output.send(Message::SearchReady(true)).await;
|
||||||
|
|
||||||
std::future::pending().await
|
std::future::pending().await
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,12 @@
|
||||||
use image::DynamicImage;
|
use image::DynamicImage;
|
||||||
use md5::{Digest, Md5};
|
use md5::{Digest, Md5};
|
||||||
use rustc_hash::FxHashMap;
|
use rustc_hash::FxHashMap;
|
||||||
|
#[cfg(unix)]
|
||||||
|
use std::os::unix::fs::PermissionsExt;
|
||||||
use std::{
|
use std::{
|
||||||
error::Error,
|
error::Error,
|
||||||
fs::{self, File},
|
fs::{self, File},
|
||||||
io::{self, BufReader, BufWriter},
|
io::{self, BufReader, BufWriter},
|
||||||
os::unix::fs::PermissionsExt,
|
|
||||||
path::{Path, PathBuf},
|
path::{Path, PathBuf},
|
||||||
sync::LazyLock,
|
sync::LazyLock,
|
||||||
time::UNIX_EPOCH,
|
time::UNIX_EPOCH,
|
||||||
|
|
@ -93,6 +94,7 @@ impl ThumbnailCacher {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn update_with_temp_file(&self, temp_file: NamedTempFile) -> Result<&Path, Box<dyn Error>> {
|
pub fn update_with_temp_file(&self, temp_file: NamedTempFile) -> Result<&Path, Box<dyn Error>> {
|
||||||
|
#[cfg(unix)]
|
||||||
fs::set_permissions(temp_file.path(), fs::Permissions::from_mode(0o600))?;
|
fs::set_permissions(temp_file.path(), fs::Permissions::from_mode(0o600))?;
|
||||||
self.update_thumbnail_text_metadata(temp_file.path())?;
|
self.update_thumbnail_text_metadata(temp_file.path())?;
|
||||||
fs::rename(temp_file.path(), &self.thumbnail_path)?;
|
fs::rename(temp_file.path(), &self.thumbnail_path)?;
|
||||||
|
|
@ -127,6 +129,7 @@ impl ThumbnailCacher {
|
||||||
pub fn create_fail_marker(&self) -> Result<(), Box<dyn Error>> {
|
pub fn create_fail_marker(&self) -> Result<(), Box<dyn Error>> {
|
||||||
if let Some(dir) = self.thumbnail_fail_marker_path.parent() {
|
if let Some(dir) = self.thumbnail_fail_marker_path.parent() {
|
||||||
fs::create_dir_all(dir)?;
|
fs::create_dir_all(dir)?;
|
||||||
|
#[cfg(unix)]
|
||||||
fs::set_permissions(dir, fs::Permissions::from_mode(0o700))?;
|
fs::set_permissions(dir, fs::Permissions::from_mode(0o700))?;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
// Copyright 2023 System76 <info@system76.com>
|
// Copyright 2023 System76 <info@system76.com>
|
||||||
// SPDX-License-Identifier: GPL-3.0-only
|
// SPDX-License-Identifier: GPL-3.0-only
|
||||||
|
|
||||||
|
#[cfg(feature = "desktop")]
|
||||||
use cosmic::desktop::fde::GenericEntry;
|
use cosmic::desktop::fde::GenericEntry;
|
||||||
use mime_guess::Mime;
|
use mime_guess::Mime;
|
||||||
use rustc_hash::FxHashMap;
|
use rustc_hash::FxHashMap;
|
||||||
|
|
|
||||||
145
src/trash.rs
Normal file
145
src/trash.rs
Normal file
|
|
@ -0,0 +1,145 @@
|
||||||
|
use cosmic::widget;
|
||||||
|
use regex::Regex;
|
||||||
|
use std::{collections::HashSet, path::PathBuf};
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
config::IconSizes,
|
||||||
|
tab::{Item, SearchItem},
|
||||||
|
};
|
||||||
|
|
||||||
|
pub trait TrashExt {
|
||||||
|
fn is_empty() -> bool {
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
fn entries() -> usize {
|
||||||
|
0
|
||||||
|
}
|
||||||
|
|
||||||
|
fn folders() -> Result<HashSet<PathBuf>, trash::Error> {
|
||||||
|
Err(trash::Error::Unknown {
|
||||||
|
description: "reading trash folders not supported on this platform".into(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn scan(_sizes: IconSizes) -> Vec<Item> {
|
||||||
|
log::warn!("viewing trash not supported on this platform");
|
||||||
|
Vec::new()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn scan_search<F: Fn(SearchItem) -> bool + Sync>(callback: F, regex: &Regex) {}
|
||||||
|
|
||||||
|
fn icon(icon_size: u16) -> widget::icon::Handle {
|
||||||
|
widget::icon::from_name(if Self::is_empty() {
|
||||||
|
"user-trash"
|
||||||
|
} else {
|
||||||
|
"user-trash-full"
|
||||||
|
})
|
||||||
|
.size(icon_size)
|
||||||
|
.handle()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn icon_symbolic(icon_size: u16) -> widget::icon::Handle {
|
||||||
|
widget::icon::from_name(if Self::is_empty() {
|
||||||
|
"user-trash-symbolic"
|
||||||
|
} else {
|
||||||
|
"user-trash-full-symbolic"
|
||||||
|
})
|
||||||
|
.size(icon_size)
|
||||||
|
.handle()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Trash;
|
||||||
|
|
||||||
|
// This config statement is from trash::os_limited
|
||||||
|
#[cfg(any(
|
||||||
|
target_os = "windows",
|
||||||
|
all(
|
||||||
|
unix,
|
||||||
|
not(target_os = "macos"),
|
||||||
|
not(target_os = "ios"),
|
||||||
|
not(target_os = "android")
|
||||||
|
)
|
||||||
|
))]
|
||||||
|
impl TrashExt for Trash {
|
||||||
|
fn is_empty() -> bool {
|
||||||
|
trash::os_limited::is_empty().unwrap_or(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn entries() -> usize {
|
||||||
|
match trash::os_limited::list() {
|
||||||
|
Ok(entries) => entries.len(),
|
||||||
|
Err(_err) => 0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Not available on Windows only
|
||||||
|
#[cfg(not(target_os = "windows"))]
|
||||||
|
fn folders() -> Result<HashSet<PathBuf>, trash::Error> {
|
||||||
|
trash::os_limited::trash_folders()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn scan(sizes: IconSizes) -> Vec<Item> {
|
||||||
|
use crate::{localize::LANGUAGE_SORTER, tab::item_from_trash_entry};
|
||||||
|
use std::cmp::Ordering;
|
||||||
|
|
||||||
|
let entries = match trash::os_limited::list() {
|
||||||
|
Ok(entry) => entry,
|
||||||
|
Err(err) => {
|
||||||
|
log::warn!("failed to read trash items: {err}");
|
||||||
|
return Vec::new();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let mut items: Vec<_> = entries
|
||||||
|
.into_iter()
|
||||||
|
.filter_map(|entry| {
|
||||||
|
let metadata = trash::os_limited::metadata(&entry)
|
||||||
|
.inspect_err(|err| {
|
||||||
|
log::warn!("failed to get metadata for trash item {entry:?}: {err}")
|
||||||
|
})
|
||||||
|
.ok()?;
|
||||||
|
Some(item_from_trash_entry(entry, metadata, sizes))
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
items.sort_by(|a, b| match (a.metadata.is_dir(), b.metadata.is_dir()) {
|
||||||
|
(true, false) => Ordering::Less,
|
||||||
|
(false, true) => Ordering::Greater,
|
||||||
|
_ => LANGUAGE_SORTER.compare(&a.display_name, &b.display_name),
|
||||||
|
});
|
||||||
|
items
|
||||||
|
}
|
||||||
|
|
||||||
|
fn scan_search<F: Fn(SearchItem) -> bool + Sync>(callback: F, regex: &Regex) {
|
||||||
|
let entries = match trash::os_limited::list() {
|
||||||
|
Ok(entries) => entries,
|
||||||
|
Err(err) => {
|
||||||
|
log::warn!("failed to read trash items: {err}");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
for entry in entries {
|
||||||
|
if let Ok(metadata) = trash::os_limited::metadata(&entry).inspect_err(|err| {
|
||||||
|
log::warn!("failed to get metadata for trash item {entry:?}: {err}")
|
||||||
|
}) {
|
||||||
|
let name = entry.name.to_string_lossy();
|
||||||
|
if regex.is_match(&name) && !callback(SearchItem::Trash(entry, metadata)) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// This config statement is from trash::os_limited, inverted
|
||||||
|
#[cfg(not(any(
|
||||||
|
target_os = "windows",
|
||||||
|
all(
|
||||||
|
unix,
|
||||||
|
not(target_os = "macos"),
|
||||||
|
not(target_os = "ios"),
|
||||||
|
not(target_os = "android")
|
||||||
|
)
|
||||||
|
)))]
|
||||||
|
impl TrashExt for Trash {}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue