Compare commits
No commits in common. "9bcfe7a1f58580c713e21703e5c2fb2acd397cda" and "91243b99b556586c16c7c9b89872d4784f93c654" have entirely different histories.
9bcfe7a1f5
...
91243b99b5
42 changed files with 774 additions and 1864 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.11"
|
version = "1.0.9"
|
||||||
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,6 +14,7 @@ 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"
|
||||||
|
|
@ -55,16 +56,12 @@ 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]
|
||||||
|
|
@ -80,6 +77,7 @@ features = [
|
||||||
"about",
|
"about",
|
||||||
"advanced-shaping",
|
"advanced-shaping",
|
||||||
"autosize",
|
"autosize",
|
||||||
|
"desktop",
|
||||||
"multi-window",
|
"multi-window",
|
||||||
"tokio",
|
"tokio",
|
||||||
"winit",
|
"winit",
|
||||||
|
|
@ -111,7 +109,7 @@ default = [
|
||||||
"wgpu",
|
"wgpu",
|
||||||
]
|
]
|
||||||
dbus-config = ["libcosmic/dbus-config"]
|
dbus-config = ["libcosmic/dbus-config"]
|
||||||
desktop = ["libcosmic/desktop", "dep:cosmic-mime-apps", "dep:xdg"]
|
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"]
|
||||||
|
|
@ -129,7 +127,6 @@ 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"
|
||||||
|
|
@ -147,12 +144,13 @@ 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" }
|
||||||
iced_futures = { path = "../libcosmic/iced/futures" }
|
# libcosmic = { git = "https://github.com/pop-os/libcosmic//", branch = "iced-rebase" }
|
||||||
iced_winit = { path = "../libcosmic/iced/winit" }
|
# cosmic-config = { git = "https://github.com/pop-os/libcosmic//", branch = "iced-rebase" }
|
||||||
|
# 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.11"
|
version = "1.0.9"
|
||||||
edition = "2024"
|
edition = "2024"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
|
|
||||||
6
debian/changelog
vendored
6
debian/changelog
vendored
|
|
@ -1,9 +1,3 @@
|
||||||
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,25 +9,9 @@ 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>> {
|
||||||
let log_format = tracing_subscriber::fmt::format()
|
env_logger::Builder::from_env(env_logger::Env::default().default_filter_or("warn")).init();
|
||||||
.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,4 +371,3 @@ 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,4 +417,3 @@ 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 = Gib den Pfad zum Verzeichnis oder zur Datei ein
|
type-to-search-enter-path = Gibt den Pfad zu einem Verzeichnis oder einer 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 = Hineinzoomen
|
zoom-in = Vergrößern
|
||||||
default-size = Standardgröße
|
default-size = Standardgröße
|
||||||
zoom-out = Herauszoomen
|
zoom-out = Verkleinern
|
||||||
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 = Sortierung
|
sort = Sortieren
|
||||||
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,8 +424,3 @@ 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
|
||||||
|
|
|
||||||
|
|
@ -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 = Liitetyt asemat
|
mounted-drives = Tiedostojärjestelmään liitetyt kovalevyt
|
||||||
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 = Roskakorikansion kohteet poistetaan pysyvästi
|
empty-trash-warning = Haluatko varmasti tyhjentää koko roskakorin 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 = Miten haluat avata kohteen "{ $name }"?
|
open-with-title = Kuinka haluat avata kohteen "{ $name }"?
|
||||||
browse-store = Selaa { $store }a
|
browse-store = Selaa { $store }
|
||||||
|
|
||||||
## 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 käyttäen
|
replace-with = Korvaa kohteella
|
||||||
apply-to-all = Toteuta kaikkiin
|
apply-to-all = Sovella 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äynnistettäväksi ja käynnistää sen?
|
set-executable-and-launch-description = Haluatko asettaa kohteen "{ $name }" käynnistettvä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 = Yhdistetään…
|
connecting = Yhdistää…
|
||||||
domain = Verkkotunnus
|
domain = Verkkotunnus
|
||||||
enter-server-address = Kirjoita palvelimen osoite
|
enter-server-address = Syötä 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 =
|
||||||
Saatavilla olevat yhteyskäytännöt,Etuliite
|
Saatavissa olevat protokollat,Etuliite
|
||||||
AppleTalk,afp://
|
AppleTalk,afp://
|
||||||
File Transfer Protocol,ftp:// tai ftps://
|
File Transfer Protocol,ftp:// or ftps://
|
||||||
Network File System,nfs://
|
Network File System,nfs://
|
||||||
Server Message Block,smb://
|
Server Message Block,smb://
|
||||||
SSH File Transfer Protocol,sftp:// tai ssh://
|
SSH File Transfer Protocol,sftp:// or ssh://
|
||||||
WebDav,dav:// tai davs://
|
WebDav,dav:// or davs://
|
||||||
network-drive-error = Verkkolevy ei saatavilla
|
network-drive-error = Verkkolevy saavuttamattomissa
|
||||||
password = Salasana
|
password = Salasana
|
||||||
remember-password = Muista salasana
|
remember-password = Muista salasana
|
||||||
try-again = Yritä uudelleen
|
try-again = Yritä uudelleen
|
||||||
username = Käyttäjätunnus
|
username = Käyttäjänimi
|
||||||
|
|
||||||
## 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 = Jonossa
|
pending = Odottaa käsittelyä
|
||||||
failed = Epäonnistuneet
|
failed = Epäonnistui
|
||||||
complete = Valmiit
|
complete = Valmis
|
||||||
compressing =
|
compressing =
|
||||||
Pakataan { $items } { $items ->
|
Tiivistetään { $items } { $items ->
|
||||||
[one] kohde
|
[one] kohde
|
||||||
*[other] kohdetta
|
*[other] kohteita
|
||||||
} sijainnista "{ $from }" arkistoon "{ $to }" ({ $progress })…
|
} lähteestä "{ $from }" arkistoon "{ $to }"
|
||||||
compressed =
|
compressed =
|
||||||
Pakattu { $items } { $items ->
|
Tiivistetty { $items } { $items ->
|
||||||
[one] kohde
|
[one] kohde
|
||||||
*[other] kohdetta
|
*[other] kohteet
|
||||||
} sijainnista "{ $from }" arkistoon "{ $to }"
|
} lähteestä "{ $from }" arkistoon "{ $to }"
|
||||||
copy_noun = Kopio
|
copy_noun = Kopio
|
||||||
creating = Luodaan "{ $name }" kohteen "{ $parent }" alle
|
creating = Luodaan kohdetta "{ $name }" kohteen "{ $parent }" alle
|
||||||
created = Luotu "{ $name }" kohteen "{ $parent }" alle
|
created = Luotu kohde "{ $name }" kohteen "{ $parent }" alle
|
||||||
copying =
|
copying =
|
||||||
Kopioidaan { $items } { $items ->
|
Kopioidaan { $items } { $items ->
|
||||||
[one] kohde
|
[one] kohde
|
||||||
*[other] kohdetta
|
*[other] kohteita
|
||||||
} sijainnista "{ $from }" kohteeseen "{ $to }" ({ $progress })…
|
} lähteestä "{ $from }" kohteeseen "{ $to }"
|
||||||
copied =
|
copied =
|
||||||
Kopioitu { $items } { $items ->
|
Kopioitu { $items } { $items ->
|
||||||
[one] kohde
|
[one] kohde
|
||||||
*[other] kohdetta
|
*[other] kohteet
|
||||||
} sijainnista "{ $from }" kohteeseen "{ $to }"
|
} lähteestä "{ $from }" kohteeseen "{ $to }"
|
||||||
emptying-trash = Tyhjennetään { trash } ({ $progress })…
|
emptying-trash = Tyhjennetään { trash }
|
||||||
emptied-trash = Tyhjennetty { trash }
|
emptied-trash = Tyhjennetty { trash }
|
||||||
extracting =
|
extracting =
|
||||||
Puretaan { $items } { $items ->
|
Puretaan { $items } { $items ->
|
||||||
[one] kohde
|
[one] kohde
|
||||||
*[other] kohdetta
|
*[other] kohteet
|
||||||
} arkistosta "{ $from }" kohteeseen "{ $to }" ({ $progress })…
|
} arkistosta "{ $from }" kohteeseen "{ $to }"
|
||||||
extracted =
|
extracted =
|
||||||
Purettu { $items } { $items ->
|
Purettu { $items } { $items ->
|
||||||
[one] kohde
|
[one] kohde
|
||||||
*[other] kohdetta
|
*[other] kohteet
|
||||||
} 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] kohdetta
|
*[other] kohteet
|
||||||
} sijainnista "{ $from }" kohteeseen "{ $to }" ({ $progress })…
|
} lähteestä "{ $from }" kohteeseen "{ $to }"
|
||||||
moved =
|
moved =
|
||||||
Siirretty { $items } { $items ->
|
Siirretty { $items } { $items ->
|
||||||
[one] kohde
|
[one] kohde
|
||||||
*[other] kohdetta
|
*[other] kohteet
|
||||||
} sijainnista "{ $from }" kohteeseen "{ $to }"
|
} lähteestä "{ $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] kohdetta
|
*[other] kohteet
|
||||||
} roskakorista ({ $progress })…
|
} { trash }sta
|
||||||
restored =
|
restored =
|
||||||
Palautettu { $items } { $items ->
|
Palautettu { $items } { $items ->
|
||||||
[one] kohde
|
[one] kohde
|
||||||
*[other] kohdetta
|
*[other] kohteet
|
||||||
} roskakorista
|
} { trash }sta
|
||||||
unknown-folder = tuntematon kansio
|
unknown-folder = Tuntematon kansio
|
||||||
|
|
||||||
## Open with
|
## Open with
|
||||||
|
|
||||||
menu-open-with = Avaa sovelluksella…
|
menu-open-with = Avaa ohjelmalla…
|
||||||
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 työpöytään
|
match-desktop = Sovita yhteen työpöydän kanssa
|
||||||
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ä muokkausajan mukaan
|
sort-by-modified = Järjestä muokkauspäivämäärän mukaan
|
||||||
sort-by-size = Järjestä koon mukaan
|
sort-by-size = Järjestä koon mukaan
|
||||||
sort-by-trashed = Järjestä poistamisajan mukaan
|
sort-by-trashed = Järjestä poistamispäivämäärän 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 esikatselu
|
gallery-preview = Gallerian esinäkymä
|
||||||
menu-settings = Asetukset…
|
menu-settings = Asetukset…
|
||||||
menu-about = Tietoa COSMICin tiedostonhallinnasta…
|
menu-about = Tietoa COSMIC Tiedostoista…
|
||||||
|
|
||||||
## Sort
|
## Sort
|
||||||
|
|
||||||
|
|
@ -318,90 +318,3 @@ 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,4 +436,3 @@ 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,4 +433,3 @@ 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,4 +436,3 @@ 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,4 +318,3 @@ 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,322 +1 @@
|
||||||
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,4 +318,3 @@ 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,8 +272,3 @@ 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,4 +437,3 @@ 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 colado
|
pasted-text = Texto copiado
|
||||||
pasted-video = Vídeo colado
|
pasted-video = Vídeo copiado
|
||||||
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,7 +433,6 @@ 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 de recentes na barra lateral
|
show-recents = Pasta 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,4 +380,3 @@ 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,5 +435,4 @@ 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,112 +275,9 @@ 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 = 混合
|
|
||||||
|
|
|
||||||
52
src/app.rs
52
src/app.rs
|
|
@ -18,7 +18,9 @@ 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, executor,
|
cosmic_theme,
|
||||||
|
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},
|
||||||
|
|
@ -97,7 +99,6 @@ 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},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -833,12 +834,9 @@ 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" {
|
||||||
#[cfg(feature = "desktop")]
|
|
||||||
{
|
|
||||||
// Try opening desktop application
|
// Try opening desktop application
|
||||||
Self::launch_desktop_entries(&paths);
|
Self::launch_desktop_entries(&paths);
|
||||||
continue;
|
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 {
|
||||||
|
|
@ -899,10 +897,7 @@ 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() {
|
||||||
|
|
@ -1799,7 +1794,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(Trash::icon_symbolic(16)))
|
.icon(icon::icon(tab::trash_helpers::trash_icon_symbolic(16)))
|
||||||
.data(Location::Trash)
|
.data(Location::Trash)
|
||||||
.divider_above()
|
.divider_above()
|
||||||
});
|
});
|
||||||
|
|
@ -2660,7 +2655,9 @@ impl Application for App {
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
if matches!(location_opt, Some(Location::Trash)) && !Trash::is_empty() {
|
if matches!(location_opt, Some(Location::Trash))
|
||||||
|
&& !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,
|
||||||
|
|
@ -4240,8 +4237,10 @@ 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
|
self.nav_model.icon_set(
|
||||||
.icon_set(entity, icon::icon(Trash::icon_symbolic(16)));
|
entity,
|
||||||
|
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()]);
|
||||||
|
|
@ -4263,14 +4262,12 @@ impl Application for App {
|
||||||
.collect();
|
.collect();
|
||||||
if !selected.is_empty() {
|
if !selected.is_empty() {
|
||||||
//TODO: batch rename
|
//TODO: batch rename
|
||||||
let mut last_name = String::new();
|
let tasks = selected
|
||||||
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,
|
||||||
|
|
@ -4278,15 +4275,9 @@ impl Application for App {
|
||||||
dir,
|
dir,
|
||||||
}))
|
}))
|
||||||
})
|
})
|
||||||
.collect();
|
.chain(std::iter::once(widget::text_input::focus(
|
||||||
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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -6160,7 +6151,6 @@ 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(),
|
||||||
|
|
@ -6804,7 +6794,14 @@ impl Application for App {
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
match (watcher_res, Trash::folders()) {
|
// TODO: Trash watching support for Windows, macOS, and other OSes
|
||||||
|
#[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
|
||||||
|
|
@ -6961,7 +6958,8 @@ impl Application for App {
|
||||||
|_| {
|
|_| {
|
||||||
stream::channel(
|
stream::channel(
|
||||||
1,
|
1,
|
||||||
move |mut msg_tx: futures::channel::mpsc::Sender<_>| async move {
|
move |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"))
|
||||||
|
|
@ -6971,6 +6969,8 @@ 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,8 +189,6 @@ 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)]
|
||||||
{
|
{
|
||||||
|
|
@ -201,15 +199,11 @@ 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(
|
return Err(ZipError::InvalidArchive("Invalid UTF-8 as symlink target"));
|
||||||
"Invalid UTF-8 as symlink target".into(),
|
|
||||||
));
|
|
||||||
};
|
};
|
||||||
let target_is_dir_from_archive = match password {
|
let target = target.into_boxed_str();
|
||||||
None => archive.by_name(&target),
|
let target_is_dir_from_archive =
|
||||||
Some(pwd) => archive.by_name_decrypt(&target, pwd.as_bytes()),
|
archive.shared.files.contains_key(&target) && is_dir(&target);
|
||||||
}
|
|
||||||
.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
|
||||||
|
|
|
||||||
|
|
@ -1,77 +0,0 @@
|
||||||
// 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,8 +441,13 @@ impl<M: Send + 'static> Dialog<M> {
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
enum DialogPage {
|
enum DialogPage {
|
||||||
NewFolder { parent: PathBuf, name: String },
|
NewFolder {
|
||||||
Replace { filename: String },
|
parent: PathBuf,
|
||||||
|
name: String,
|
||||||
|
},
|
||||||
|
Replace {
|
||||||
|
filename: String,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
|
|
@ -595,7 +600,6 @@ 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)),
|
||||||
);
|
);
|
||||||
|
|
@ -1958,16 +1962,6 @@ 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());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
53
src/lib.rs
53
src/lib.rs
|
|
@ -3,20 +3,14 @@
|
||||||
|
|
||||||
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;
|
||||||
pub mod config;
|
|
||||||
mod context_action;
|
mod context_action;
|
||||||
|
use config::Config;
|
||||||
|
pub mod config;
|
||||||
pub mod dialog;
|
pub mod dialog;
|
||||||
mod key_bind;
|
mod key_bind;
|
||||||
pub(crate) mod large_image;
|
pub(crate) mod large_image;
|
||||||
|
|
@ -29,11 +23,13 @@ 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>;
|
||||||
|
|
||||||
|
|
@ -77,22 +73,7 @@ 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>> {
|
||||||
let log_format = tracing_subscriber::fmt::format()
|
env_logger::Builder::from_env(env_logger::Env::default().default_filter_or("warn")).init();
|
||||||
.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();
|
||||||
|
|
||||||
|
|
@ -127,21 +108,7 @@ 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>> {
|
||||||
let log_format = tracing_subscriber::fmt::format()
|
env_logger::Builder::from_env(env_logger::Env::default().default_filter_or("warn")).init();
|
||||||
.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();
|
||||||
|
|
||||||
|
|
@ -191,7 +158,7 @@ pub fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
}
|
}
|
||||||
|
|
||||||
if daemonize {
|
if daemonize {
|
||||||
#[cfg(all(unix, not(any(target_os = "macos", target_os = "redox"))))]
|
#[cfg(all(unix, not(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,7 +23,6 @@ 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> =
|
||||||
|
|
@ -193,7 +192,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::is_empty() {
|
if !trash::os_limited::is_empty().unwrap_or(true) {
|
||||||
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,7 +19,6 @@ 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,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -27,17 +26,10 @@ 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)
|
||||||
|
|
@ -61,16 +53,6 @@ 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>,
|
||||||
|
|
@ -118,13 +100,8 @@ 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::mpsc;
|
use tokio::sync::{Mutex, mpsc};
|
||||||
|
|
||||||
use super::{Mounter, MounterAuth, MounterItem, MounterItems, MounterMessage};
|
use super::{Mounter, MounterAuth, MounterItem, MounterItems, MounterMessage};
|
||||||
use crate::{
|
use crate::{
|
||||||
|
|
@ -199,7 +199,6 @@ 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,
|
||||||
|
|
@ -230,10 +229,7 @@ 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(
|
fn mount_op(uri: String, event_tx: mpsc::UnboundedSender<Event>) -> gio::MountOperation {
|
||||||
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| {
|
||||||
|
|
@ -256,9 +252,9 @@ fn mount_op(
|
||||||
.then_some(false),
|
.then_some(false),
|
||||||
};
|
};
|
||||||
let (auth_tx, mut auth_rx) = mpsc::channel(1);
|
let (auth_tx, mut auth_rx) = mpsc::channel(1);
|
||||||
if let Some(event_tx) = event_tx.upgrade() {
|
event_tx
|
||||||
event_tx.send(Event::NetworkAuth(uri.clone(), auth, auth_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) {
|
||||||
|
|
@ -361,45 +357,37 @@ impl Item {
|
||||||
|
|
||||||
pub struct Gvfs {
|
pub struct Gvfs {
|
||||||
command_tx: mpsc::UnboundedSender<Cmd>,
|
command_tx: mpsc::UnboundedSender<Cmd>,
|
||||||
event_rx: Arc<crate::channel::Receiver<Event>>,
|
event_rx: Arc<Mutex<mpsc::UnboundedReceiver<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) = crate::channel::channel();
|
let (event_tx, event_rx) = mpsc::unbounded_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));
|
||||||
if let Some(event_tx) = event_tx.upgrade() {
|
event_tx.send(Event::Changed).unwrap();
|
||||||
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));
|
||||||
if let Some(event_tx) = event_tx.upgrade() {
|
event_tx.send(Event::Changed).unwrap();
|
||||||
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));
|
||||||
if let Some(event_tx) = event_tx.upgrade() {
|
event_tx.send(Event::Changed).unwrap();
|
||||||
event_tx.send(Event::Changed);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -407,27 +395,21 @@ 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));
|
||||||
if let Some(event_tx) = event_tx.upgrade() {
|
event_tx.send(Event::Changed).unwrap();
|
||||||
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));
|
||||||
if let Some(event_tx) = event_tx.upgrade() {
|
event_tx.send(Event::Changed).unwrap();
|
||||||
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));
|
||||||
if let Some(event_tx) = event_tx.upgrade() {
|
event_tx.send(Event::Changed).unwrap();
|
||||||
event_tx.send(Event::Changed);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -437,11 +419,7 @@ impl Gvfs {
|
||||||
items_tx.send(items(&monitor, sizes)).await.unwrap();
|
items_tx.send(items(&monitor, sizes)).await.unwrap();
|
||||||
}
|
}
|
||||||
Cmd::Rescan => {
|
Cmd::Rescan => {
|
||||||
let Some(event_tx) = event_tx.upgrade() else {
|
event_tx.send(Event::Items(items(&monitor, IconSizes::default()))).unwrap();
|
||||||
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 {
|
||||||
|
|
@ -493,9 +471,6 @@ 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(()));
|
||||||
|
|
@ -507,7 +482,7 @@ impl Gvfs {
|
||||||
Some(gio::IOErrorEnum::FailedHandled) => Ok(false),
|
Some(gio::IOErrorEnum::FailedHandled) => Ok(false),
|
||||||
_ => Err(format!("{err}"))
|
_ => Err(format!("{err}"))
|
||||||
}}
|
}}
|
||||||
}));
|
})).unwrap();
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
break;
|
break;
|
||||||
|
|
@ -523,9 +498,6 @@ 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(()));
|
||||||
|
|
@ -536,7 +508,7 @@ impl Gvfs {
|
||||||
Some(gio::IOErrorEnum::FailedHandled) => Ok(false),
|
Some(gio::IOErrorEnum::FailedHandled) => Ok(false),
|
||||||
_ => Err(format!("{err}"))
|
_ => Err(format!("{err}"))
|
||||||
}}
|
}}
|
||||||
}));
|
})).unwrap();
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -560,9 +532,6 @@ 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)
|
||||||
|
|
@ -571,7 +540,7 @@ impl Gvfs {
|
||||||
Some(gio::IOErrorEnum::FailedHandled) => Ok(false),
|
Some(gio::IOErrorEnum::FailedHandled) => Ok(false),
|
||||||
_ => Err(format!("{err}"))
|
_ => Err(format!("{err}"))
|
||||||
}
|
}
|
||||||
}));
|
})).unwrap();
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -627,7 +596,7 @@ impl Gvfs {
|
||||||
});
|
});
|
||||||
Self {
|
Self {
|
||||||
command_tx,
|
command_tx,
|
||||||
event_rx: Arc::new(event_rx),
|
event_rx: Arc::new(Mutex::new(event_rx)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -701,7 +670,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<crate::channel::Receiver<Event>>,
|
event_rx: Arc<Mutex<mpsc::UnboundedReceiver<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) {
|
||||||
|
|
@ -725,7 +694,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.recv().await {
|
while let Some(event) = event_rx.lock().await.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,11 +1,7 @@
|
||||||
use atomic_float::AtomicF32;
|
use std::sync::{Arc, Mutex};
|
||||||
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, IntoPrimitive, TryFromPrimitive)]
|
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||||
#[repr(u16)]
|
|
||||||
pub enum ControllerState {
|
pub enum ControllerState {
|
||||||
Cancelled,
|
Cancelled,
|
||||||
Failed,
|
Failed,
|
||||||
|
|
@ -15,8 +11,8 @@ pub enum ControllerState {
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
struct ControllerInner {
|
struct ControllerInner {
|
||||||
state: AtomicU16,
|
state: Mutex<ControllerState>,
|
||||||
progress: AtomicF32,
|
progress: Mutex<f32>,
|
||||||
notify: Notify,
|
notify: Notify,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -31,8 +27,8 @@ impl Default for Controller {
|
||||||
Self {
|
Self {
|
||||||
primary: true,
|
primary: true,
|
||||||
inner: Arc::new(ControllerInner {
|
inner: Arc::new(ControllerInner {
|
||||||
state: AtomicU16::new(ControllerState::Running.into()),
|
state: Mutex::new(ControllerState::Running),
|
||||||
progress: AtomicF32::new(0.0),
|
progress: Mutex::new(0.0),
|
||||||
notify: Notify::new(),
|
notify: Notify::new(),
|
||||||
}),
|
}),
|
||||||
}
|
}
|
||||||
|
|
@ -54,24 +50,19 @@ impl Controller {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn progress(&self) -> f32 {
|
pub fn progress(&self) -> f32 {
|
||||||
self.inner.progress.load(atomic::Ordering::Relaxed)
|
*self.inner.progress.lock().unwrap()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_progress(&self, progress: f32) {
|
pub fn set_progress(&self, progress: f32) {
|
||||||
self.inner
|
*self.inner.progress.lock().unwrap() = progress;
|
||||||
.progress
|
|
||||||
.swap(progress, atomic::Ordering::Relaxed);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn state(&self) -> ControllerState {
|
pub fn state(&self) -> ControllerState {
|
||||||
ControllerState::try_from(self.inner.state.load(atomic::Ordering::Relaxed))
|
*self.inner.state.lock().unwrap()
|
||||||
.unwrap_or(ControllerState::Failed)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_state(&self, state: ControllerState) {
|
pub fn set_state(&self, state: ControllerState) {
|
||||||
self.inner
|
*self.inner.state.lock().unwrap() = state;
|
||||||
.state
|
|
||||||
.store(state.into(), atomic::Ordering::Relaxed);
|
|
||||||
self.inner.notify.notify_waiters();
|
self.inner.notify.notify_waiters();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -95,35 +86,6 @@ 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,9 +22,6 @@ 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;
|
||||||
|
|
||||||
|
|
@ -114,7 +111,7 @@ async fn copy_or_move(
|
||||||
);
|
);
|
||||||
|
|
||||||
// Handle duplicate file names by renaming paths
|
// Handle duplicate file names by renaming paths
|
||||||
let from_to_pairs_iter = paths
|
let mut from_to_pairs: Vec<(PathBuf, PathBuf)> = 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)| {
|
||||||
|
|
@ -132,23 +129,23 @@ 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 { .. }) {
|
||||||
let from_to_pairs: Vec<(PathBuf, PathBuf)> = if matches!(method, Method::Move { .. }) {
|
from_to_pairs.retain(|(from, to)| {
|
||||||
from_to_pairs_iter
|
|
||||||
.map(|(from, to)| async move {
|
|
||||||
//TODO: show replace dialog here?
|
//TODO: show replace dialog here?
|
||||||
if to.exists() {
|
if to.exists() {
|
||||||
return Some((from, to));
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
match compio::fs::rename(&from, &to).await {
|
//TODO: use compio::fs::rename?
|
||||||
|
match fs::rename(from, to) {
|
||||||
Ok(()) => {
|
Ok(()) => {
|
||||||
log::info!("renamed {} to {}", from.display(), to.display());
|
log::info!("renamed {} to {}", from.display(), to.display());
|
||||||
None
|
false
|
||||||
}
|
}
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
log::info!(
|
log::info!(
|
||||||
|
|
@ -157,21 +154,11 @@ async fn copy_or_move(
|
||||||
to.display(),
|
to.display(),
|
||||||
err
|
err
|
||||||
);
|
);
|
||||||
Some((from, to))
|
true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
});
|
||||||
.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());
|
||||||
|
|
||||||
|
|
@ -229,7 +216,7 @@ pub async fn sync_to_disk(
|
||||||
}
|
}
|
||||||
}))
|
}))
|
||||||
.buffer_unordered(32)
|
.buffer_unordered(32)
|
||||||
.collect::<()>()
|
.collect::<Vec<_>>()
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
// Sync directories to disk
|
// Sync directories to disk
|
||||||
|
|
@ -239,7 +226,7 @@ pub async fn sync_to_disk(
|
||||||
}
|
}
|
||||||
}))
|
}))
|
||||||
.buffer_unordered(16)
|
.buffer_unordered(16)
|
||||||
.collect::<()>()
|
.collect::<Vec<_>>()
|
||||||
.await;
|
.await;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1125,9 +1112,7 @@ 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 Err(OperationError::from_msg(
|
return OperationError::from_msg("Restoring from trash is not supported on macos");
|
||||||
"Restoring from trash is not supported on macos",
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
#[cfg(not(target_os = "macos"))]
|
#[cfg(not(target_os = "macos"))]
|
||||||
Self::Restore { items } => {
|
Self::Restore { items } => {
|
||||||
|
|
|
||||||
|
|
@ -1,58 +0,0 @@
|
||||||
// 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,33 +1,15 @@
|
||||||
// Copyright 2023 System76 <info@system76.com>
|
|
||||||
// SPDX-License-Identifier: GPL-3.0-only
|
|
||||||
|
|
||||||
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::BufResult;
|
||||||
use compio::buf::{IntoInner, IoBuf};
|
use compio::buf::{IntoInner, IoBuf};
|
||||||
use compio::driver::{ToSharedFd, op::AsyncifyFd};
|
|
||||||
use compio::io::{AsyncReadAt, AsyncWriteAt};
|
use compio::io::{AsyncReadAt, AsyncWriteAt};
|
||||||
use cosmic::iced::futures;
|
|
||||||
use futures::{FutureExt, StreamExt};
|
|
||||||
use std::future::Future;
|
use std::future::Future;
|
||||||
use std::pin::Pin;
|
use std::pin::Pin;
|
||||||
use std::rc::Rc;
|
|
||||||
use std::time::Instant;
|
use std::time::Instant;
|
||||||
use std::{cell::Cell, error::Error, fs, ops::ControlFlow, path::PathBuf};
|
use std::{cell::Cell, error::Error, fs, ops::ControlFlow, path::PathBuf, rc::Rc};
|
||||||
use walkdir::WalkDir;
|
use walkdir::WalkDir;
|
||||||
|
|
||||||
#[cfg(feature = "gvfs")]
|
use crate::operation::{OperationError, sync_to_disk};
|
||||||
use gio::prelude::FileExtManual;
|
|
||||||
|
|
||||||
#[derive(thiserror::Error, Debug)]
|
use super::{Controller, OperationSelection, ReplaceResult, copy_unique_path};
|
||||||
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,
|
||||||
|
|
@ -331,28 +313,136 @@ impl Op {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn run(&mut self, ctx: &mut Context, progress: Progress) -> Result<bool, Box<dyn Error>> {
|
async fn run(
|
||||||
|
&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 => {
|
||||||
crate::operation::actively_writing_add(self.to.clone());
|
// Remove `to` if overwriting and it is an existing file
|
||||||
let result = self.copy(ctx, progress).await;
|
if self.to.is_file() {
|
||||||
|
match ctx.replace(self).await? {
|
||||||
if result.is_err() {
|
ControlFlow::Continue(to) => {
|
||||||
_ = compio::fs::remove_file(&self.to).await;
|
self.to = to;
|
||||||
|
}
|
||||||
|
ControlFlow::Break(ret) => {
|
||||||
|
return Ok(ret);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
crate::operation::actively_writing_remove(&self.to);
|
let (from_file, metadata, mut to_file) = cosmic::iced::futures::try_join!(
|
||||||
return result;
|
async {
|
||||||
|
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? {
|
||||||
|
|
@ -430,268 +520,4 @@ 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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
217
src/tab.rs
217
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, font,
|
Apply, Element, cosmic_theme,
|
||||||
|
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,10 +39,9 @@ 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,
|
||||||
|
|
@ -53,6 +52,7 @@ 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,12 +557,6 @@ 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)) {
|
||||||
|
|
@ -576,12 +570,6 @@ 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,
|
||||||
|
|
@ -605,7 +593,6 @@ 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)) {
|
||||||
|
|
@ -721,9 +708,6 @@ 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,
|
||||||
|
|
@ -859,7 +843,6 @@ 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,
|
||||||
|
|
@ -913,9 +896,6 @@ 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,
|
||||||
|
|
@ -1181,7 +1161,133 @@ pub fn scan_search<F: Fn(SearchItem) -> bool + Sync>(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
SearchLocation::Trash => {
|
SearchLocation::Trash => {
|
||||||
Trash::scan_search(callback, ®ex);
|
trash_helpers::scan_search_trash(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;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1319,15 +1425,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::entries() as u64,
|
entries: trash_helpers::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::icon(sizes.grid()),
|
trash_helpers::trash_icon(sizes.grid()),
|
||||||
Trash::icon(sizes.list()),
|
trash_helpers::trash_icon(sizes.list()),
|
||||||
Trash::icon(sizes.list_condensed()),
|
trash_helpers::trash_icon(sizes.list_condensed()),
|
||||||
)
|
)
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -1338,7 +1444,6 @@ 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,
|
||||||
|
|
@ -1568,7 +1673,7 @@ impl Location {
|
||||||
// Search is done incrementally
|
// Search is done incrementally
|
||||||
Vec::new()
|
Vec::new()
|
||||||
}
|
}
|
||||||
Self::Trash => Trash::scan(sizes),
|
Self::Trash => trash_helpers::scan_trash(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),
|
||||||
};
|
};
|
||||||
|
|
@ -2214,7 +2319,6 @@ 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,
|
||||||
|
|
@ -2429,7 +2533,7 @@ impl Item {
|
||||||
|
|
||||||
let mode = metadata.mode();
|
let mode = metadata.mode();
|
||||||
|
|
||||||
let user_name = uzers::get_user_by_uid(metadata.uid())
|
let user_name = 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();
|
||||||
|
|
@ -2452,7 +2556,7 @@ impl Item {
|
||||||
)),
|
)),
|
||||||
);
|
);
|
||||||
|
|
||||||
let group_name = uzers::get_group_by_gid(metadata.gid())
|
let group_name = 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();
|
||||||
|
|
@ -4340,7 +4444,6 @@ 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()
|
||||||
|
|
@ -6358,17 +6461,15 @@ impl Tab {
|
||||||
} else {
|
} else {
|
||||||
total_size = total_size.saturating_add(metadata.len());
|
total_size = total_size.saturating_add(metadata.len());
|
||||||
}
|
}
|
||||||
#[cfg(unix)]
|
|
||||||
{
|
|
||||||
let mode = metadata.mode();
|
let mode = metadata.mode();
|
||||||
user_name.insert(
|
user_name.insert(
|
||||||
uzers::get_user_by_uid(metadata.uid())
|
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(),
|
||||||
);
|
);
|
||||||
mode_user.insert(get_mode_part(mode, MODE_SHIFT_USER));
|
mode_user.insert(get_mode_part(mode, MODE_SHIFT_USER));
|
||||||
group_name.insert(
|
group_name.insert(
|
||||||
uzers::get_group_by_gid(metadata.gid())
|
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(),
|
||||||
);
|
);
|
||||||
|
|
@ -6376,7 +6477,6 @@ impl Tab {
|
||||||
mode_other.insert(get_mode_part(mode, MODE_SHIFT_OTHER));
|
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();
|
||||||
mime_types.sort_by(|(_, v1), (_, v2)| v2.cmp(v1));
|
mime_types.sort_by(|(_, v1), (_, v2)| v2.cmp(v1));
|
||||||
|
|
||||||
|
|
@ -6613,13 +6713,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 item.image_dimensions {
|
match image::image_dimensions(&path) {
|
||||||
Some((width, height)) => {
|
Ok((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)
|
||||||
}
|
}
|
||||||
None => (max_mb, max_jobs),
|
Err(_) => (max_mb, max_jobs),
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
(max_mb, max_jobs)
|
(max_mb, max_jobs)
|
||||||
|
|
@ -6662,10 +6762,6 @@ 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();
|
||||||
|
|
||||||
|
|
@ -6850,8 +6946,9 @@ impl Tab {
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
let (watch_tx, mut watch_rx) = tokio::sync::watch::channel(true);
|
let output = Arc::new(tokio::sync::Mutex::new(output));
|
||||||
{
|
{
|
||||||
|
let output = output.clone();
|
||||||
tokio::task::spawn_blocking(move || {
|
tokio::task::spawn_blocking(move || {
|
||||||
scan_search(
|
scan_search(
|
||||||
&search_location,
|
&search_location,
|
||||||
|
|
@ -6879,7 +6976,14 @@ impl Tab {
|
||||||
true
|
true
|
||||||
} else {
|
} else {
|
||||||
// Wake up update method
|
// Wake up update method
|
||||||
watch_tx.send(false).is_ok()
|
futures::executor::block_on(async {
|
||||||
|
output
|
||||||
|
.lock()
|
||||||
|
.await
|
||||||
|
.send(Message::SearchReady(false))
|
||||||
|
.await
|
||||||
|
})
|
||||||
|
.is_ok()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Err(_) => false,
|
Err(_) => false,
|
||||||
|
|
@ -6892,16 +6996,13 @@ 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.send(Message::SearchReady(true)).await;
|
let _ = output.lock().await.send(Message::SearchReady(true)).await;
|
||||||
|
|
||||||
std::future::pending().await
|
std::future::pending().await
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,11 @@
|
||||||
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,
|
||||||
|
|
@ -94,7 +93,6 @@ 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)?;
|
||||||
|
|
@ -129,7 +127,6 @@ 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,7 +1,6 @@
|
||||||
// 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
145
src/trash.rs
|
|
@ -1,145 +0,0 @@
|
||||||
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