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]
|
||||
name = "cosmic-files"
|
||||
version = "1.0.11"
|
||||
version = "1.0.9"
|
||||
authors = ["Jeremy Soller <jeremy@system76.com>"]
|
||||
edition = "2024"
|
||||
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 }
|
||||
cosmic-mime-apps = { git = "https://github.com/pop-os/cosmic-mime-apps.git", optional = true }
|
||||
dirs = "6.0.0"
|
||||
env_logger = "0.11"
|
||||
gio = { version = "0.21", optional = true }
|
||||
glib = { version = "0.21", optional = true }
|
||||
glob = "0.3"
|
||||
|
|
@ -55,16 +56,12 @@ rust-embed = "8"
|
|||
slotmap = "1.1.1"
|
||||
recently-used-xbel = "1.2.0"
|
||||
zip = "8"
|
||||
uzers = "0.12.2"
|
||||
md-5 = "0.10.6"
|
||||
png = "0.18"
|
||||
jxl-oxide = { version = "0.12.5", features = ["image"] }
|
||||
num_cpus = "1.17.0"
|
||||
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.
|
||||
[dependencies.compio]
|
||||
|
|
@ -80,6 +77,7 @@ features = [
|
|||
"about",
|
||||
"advanced-shaping",
|
||||
"autosize",
|
||||
"desktop",
|
||||
"multi-window",
|
||||
"tokio",
|
||||
"winit",
|
||||
|
|
@ -111,7 +109,7 @@ default = [
|
|||
"wgpu",
|
||||
]
|
||||
dbus-config = ["libcosmic/dbus-config"]
|
||||
desktop = ["libcosmic/desktop", "dep:cosmic-mime-apps", "dep:xdg"]
|
||||
desktop = ["dep:cosmic-mime-apps", "dep:xdg"]
|
||||
desktop-applet = []
|
||||
gvfs = ["dep:gio", "dep:glib"]
|
||||
io-uring = ["compio/io-uring"]
|
||||
|
|
@ -129,7 +127,6 @@ debug = true
|
|||
|
||||
[target.'cfg(unix)'.dependencies]
|
||||
fork = "0.7"
|
||||
uzers = "0.12.2"
|
||||
|
||||
[target.'cfg(target_os = "linux")'.dependencies]
|
||||
procfs = "0.18"
|
||||
|
|
@ -147,12 +144,13 @@ tokio = { version = "1", features = ["rt", "macros"] }
|
|||
# [patch.'https://github.com/pop-os/cosmic-text']
|
||||
# cosmic-text = { path = "../cosmic-text" }
|
||||
|
||||
[patch.'https://github.com/pop-os/libcosmic']
|
||||
libcosmic = { path = "../libcosmic" }
|
||||
cosmic-config = { path = "../libcosmic/cosmic-config" }
|
||||
cosmic-theme = { path = "../libcosmic/cosmic-theme" }
|
||||
iced_futures = { path = "../libcosmic/iced/futures" }
|
||||
iced_winit = { path = "../libcosmic/iced/winit" }
|
||||
# [patch.'https://github.com/pop-os/libcosmic']
|
||||
# libcosmic = { path = "../libcosmic" }
|
||||
# cosmic-config = { path = "../libcosmic/cosmic-config" }
|
||||
# cosmic-theme = { path = "../libcosmic/cosmic-theme" }
|
||||
# libcosmic = { git = "https://github.com/pop-os/libcosmic//", branch = "iced-rebase" }
|
||||
# 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']
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "cosmic-files-applet"
|
||||
version = "1.0.11"
|
||||
version = "1.0.9"
|
||||
edition = "2024"
|
||||
|
||||
[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
|
||||
|
||||
* Epoch 1.0.9 version update
|
||||
|
|
|
|||
|
|
@ -9,25 +9,9 @@ use cosmic_files::dialog::{
|
|||
Dialog, DialogChoice, DialogChoiceOption, DialogFilter, DialogFilterPattern, DialogKind,
|
||||
DialogMessage, DialogResult, DialogSettings,
|
||||
};
|
||||
use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt};
|
||||
|
||||
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
let log_format = tracing_subscriber::fmt::format()
|
||||
.pretty()
|
||||
.without_time()
|
||||
.with_line_number(true)
|
||||
.with_file(true)
|
||||
.with_target(false)
|
||||
.with_thread_names(true);
|
||||
|
||||
let log_layer = tracing_subscriber::fmt::Layer::default()
|
||||
.with_writer(std::io::stderr)
|
||||
.event_format(log_format);
|
||||
|
||||
tracing_subscriber::registry()
|
||||
.with(tracing_subscriber::EnvFilter::from_env("RUST_LOG"))
|
||||
.with(log_layer)
|
||||
.init();
|
||||
env_logger::Builder::from_env(env_logger::Env::default().default_filter_or("warn")).init();
|
||||
|
||||
let settings = Settings::default();
|
||||
app::run::<App>(settings, ())?;
|
||||
|
|
|
|||
|
|
@ -22,10 +22,10 @@ empty-trash-warning = سيتم حذف العناصر الموجودة في مج
|
|||
|
||||
## New File/Folder Dialog
|
||||
|
||||
create-new-file = أنشئ ملف جديد
|
||||
create-new-folder = أنشئ مجلَّد جديد
|
||||
create-new-file = إنشاء ملف جديد
|
||||
create-new-folder = إنشاء مجلد جديد
|
||||
file-name = اسم الملف
|
||||
folder-name = اسم المجلَّد
|
||||
folder-name = اسم المجلد
|
||||
file-already-exists = يوجد ملف بهذا الاسم بالفعل
|
||||
folder-already-exists = يوجد مجلد بهذا الاسم بالفعل
|
||||
name-hidden = الاسماء التي تبدأ بنقطة «.» ستكون مخفية
|
||||
|
|
@ -34,10 +34,10 @@ name-no-slashes = لا يمكن أن يحتوي الاسم على شرطات م
|
|||
|
||||
## Open/Save Dialog
|
||||
|
||||
cancel = ألغِ
|
||||
open = افتح
|
||||
open-file = افتح ملف
|
||||
open-folder = افتح مجلَّد
|
||||
cancel = إلغاء
|
||||
open = فتح
|
||||
open-file = فتح ملف
|
||||
open-folder = افتح مجلد
|
||||
open-in-new-tab = افتح في لسان جديد
|
||||
open-in-new-window = افتح في نافذة جديدة
|
||||
open-multiple-files = افتح عدة ملفات
|
||||
|
|
@ -150,12 +150,12 @@ trashed-on = مهمل
|
|||
details = التفاصيل
|
||||
pause = ألبث
|
||||
resume = استئناف
|
||||
create-archive = أنشئ أرشيف
|
||||
create-archive = إنشاء أرشيف
|
||||
extract-password-required = كلمة السر مطلوبة
|
||||
extract-to = استخرِج إلى...
|
||||
extract-to-title = استخرِج إلى مجلّد
|
||||
mount-error = تعذر الوصول إلى القرص
|
||||
create = أنشئ
|
||||
create = إنشاء
|
||||
open-item-location = افتح مكان العنصر
|
||||
set-executable-and-launch-description = أتريد تعيين «{ $name }» كقابل للتنفيذ وتشغيله؟
|
||||
favorite-path-error-description =
|
||||
|
|
@ -259,7 +259,7 @@ related-apps = تطبيقات ذات صلة
|
|||
selected-items = العناصر { $items } المحدّدة
|
||||
permanently-delete-question = احذف نهائيًا؟
|
||||
delete = احذف
|
||||
permanently-delete-warning = سيُحذف { $target } نهائيًا. لا يمكن التراجع عن هذا الإجراء.
|
||||
permanently-delete-warning = هل أنت متأكد من أنك تريد حذف { $target } نهائيًا؟ لا يمكن التراجع عن هذا الإجراء.
|
||||
replace-warning-operation = أتريد استبداله؟ استبداله سيكتب فوق محتواه.
|
||||
original-file = الملف الأصلي
|
||||
replace-with = استبدل بـ
|
||||
|
|
@ -371,4 +371,3 @@ move-to = انقل إلى...
|
|||
show-recents = مجلد الحديثة في الشريط الجانبي
|
||||
clear-recents-history = امحُ التأريخ الحديث
|
||||
copy-path = انسخ المسار
|
||||
mixed = مختلط
|
||||
|
|
|
|||
|
|
@ -176,7 +176,7 @@ set-executable-and-launch-description = Chcete povolit spouštění souboru „{
|
|||
set-and-launch = Povolit a spustit
|
||||
open-with = Otevřít pomocí
|
||||
other = Ostatní
|
||||
none = Žádné
|
||||
none = Žádný
|
||||
icon-size-and-spacing = Velikost a rozestupy ikon
|
||||
grid-spacing = Rozestupy mřížky
|
||||
deleting =
|
||||
|
|
@ -417,4 +417,3 @@ move-to-title = Vyberte cíl přesunutí
|
|||
show-recents = Složka „Nedávné“ v postranním panelu
|
||||
copy-path = Kopírovat cestu
|
||||
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-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ü
|
||||
add-to-sidebar = Zur Seitenleiste hinzufügen
|
||||
compress = Komprimieren...
|
||||
|
|
@ -376,9 +376,9 @@ select-all = Alles auswählen
|
|||
|
||||
## Ansicht
|
||||
|
||||
zoom-in = Hineinzoomen
|
||||
zoom-in = Vergrößern
|
||||
default-size = Standardgröße
|
||||
zoom-out = Herauszoomen
|
||||
zoom-out = Verkleinern
|
||||
view = Ansicht
|
||||
grid-view = Rasteransicht
|
||||
list-view = Listenansicht
|
||||
|
|
@ -390,7 +390,7 @@ menu-about = Über COSMIC Dateien...
|
|||
|
||||
## Sortieren
|
||||
|
||||
sort = Sortierung
|
||||
sort = Sortieren
|
||||
sort-a-z = A-Z
|
||||
sort-z-a = Z-A
|
||||
sort-newest-first = Neueste zuerst
|
||||
|
|
@ -424,8 +424,3 @@ clear-recents-history = Verlauf zuletzt verwendeter Elemente leeren
|
|||
comment = Dateimanager für den COSMIC Desktop
|
||||
keywords = Ordner;Manager;
|
||||
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
|
||||
home = Inicio
|
||||
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
|
||||
recents = Recientes
|
||||
undo = Deshacer
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ no-results = Ei tuloksia
|
|||
filesystem = Tiedostojärjestelmä
|
||||
home = Koti
|
||||
networks = Verkot
|
||||
notification-in-progress = Tiedostotoimintoja käynnissä
|
||||
notification-in-progress = Tiedostotoimintoja käynnissä.
|
||||
trash = Roskakori
|
||||
recents = Viimeaikaiset
|
||||
undo = Kumoa
|
||||
|
|
@ -16,7 +16,7 @@ today = Tänään
|
|||
desktop-view-options = Työpöytänäkymän asetukset…
|
||||
show-on-desktop = Näytä työpöydällä
|
||||
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
|
||||
icon-size-and-spacing = Kuvakkeen koko ja välistys
|
||||
icon-size = Kuvakkeen koko
|
||||
|
|
@ -38,7 +38,7 @@ create-archive = Luo arkisto
|
|||
## Empty Trash Dialog
|
||||
|
||||
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
|
||||
|
||||
|
|
@ -73,8 +73,8 @@ save-file = Tallenna tiedosto
|
|||
|
||||
## Open With Dialog
|
||||
|
||||
open-with-title = Miten haluat avata kohteen "{ $name }"?
|
||||
browse-store = Selaa { $store }a
|
||||
open-with-title = Kuinka haluat avata kohteen "{ $name }"?
|
||||
browse-store = Selaa { $store }
|
||||
|
||||
## 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-operation = Haluatko korvata sen? Korvaaminen ylikirjoittaa sen sisällön.
|
||||
original-file = Alkuperäinen tiedosto
|
||||
replace-with = Korvaa käyttäen
|
||||
apply-to-all = Toteuta kaikkiin
|
||||
replace-with = Korvaa kohteella
|
||||
apply-to-all = Sovella kaikkiin
|
||||
keep-both = Pidä molemmat
|
||||
skip = Ohita
|
||||
|
||||
## Set as Executable and Launch Dialog
|
||||
|
||||
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ä
|
||||
|
||||
## Metadata Dialog
|
||||
|
|
@ -116,9 +116,9 @@ other = Muut
|
|||
add-network-drive = Lisää verkkolevy
|
||||
connect = Yhdistä
|
||||
connect-anonymously = Yhdistä nimettömästi
|
||||
connecting = Yhdistetään…
|
||||
connecting = Yhdistää…
|
||||
domain = Verkkotunnus
|
||||
enter-server-address = Kirjoita palvelimen osoite
|
||||
enter-server-address = Syötä palvelimen osoite
|
||||
network-drive-description =
|
||||
Palvelinosoitteet sisältävät protokollaetuliitteen sekä osoitteen.
|
||||
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
|
||||
|
||||
network-drive-schemes =
|
||||
Saatavilla olevat yhteyskäytännöt,Etuliite
|
||||
Saatavissa olevat protokollat,Etuliite
|
||||
AppleTalk,afp://
|
||||
File Transfer Protocol,ftp:// tai ftps://
|
||||
File Transfer Protocol,ftp:// or ftps://
|
||||
Network File System,nfs://
|
||||
Server Message Block,smb://
|
||||
SSH File Transfer Protocol,sftp:// tai ssh://
|
||||
WebDav,dav:// tai davs://
|
||||
network-drive-error = Verkkolevy ei saatavilla
|
||||
SSH File Transfer Protocol,sftp:// or ssh://
|
||||
WebDav,dav:// or davs://
|
||||
network-drive-error = Verkkolevy saavuttamattomissa
|
||||
password = Salasana
|
||||
remember-password = Muista salasana
|
||||
try-again = Yritä uudelleen
|
||||
username = Käyttäjätunnus
|
||||
username = Käyttäjänimi
|
||||
|
||||
## Operations
|
||||
|
||||
edit-history = Muokkaa historiaa
|
||||
history = Historia
|
||||
no-history = Historia on tyhjä.
|
||||
pending = Jonossa
|
||||
failed = Epäonnistuneet
|
||||
complete = Valmiit
|
||||
pending = Odottaa käsittelyä
|
||||
failed = Epäonnistui
|
||||
complete = Valmis
|
||||
compressing =
|
||||
Pakataan { $items } { $items ->
|
||||
Tiivistetään { $items } { $items ->
|
||||
[one] kohde
|
||||
*[other] kohdetta
|
||||
} sijainnista "{ $from }" arkistoon "{ $to }" ({ $progress })…
|
||||
*[other] kohteita
|
||||
} lähteestä "{ $from }" arkistoon "{ $to }"
|
||||
compressed =
|
||||
Pakattu { $items } { $items ->
|
||||
Tiivistetty { $items } { $items ->
|
||||
[one] kohde
|
||||
*[other] kohdetta
|
||||
} sijainnista "{ $from }" arkistoon "{ $to }"
|
||||
*[other] kohteet
|
||||
} lähteestä "{ $from }" arkistoon "{ $to }"
|
||||
copy_noun = Kopio
|
||||
creating = Luodaan "{ $name }" kohteen "{ $parent }" alle
|
||||
created = Luotu "{ $name }" kohteen "{ $parent }" alle
|
||||
creating = Luodaan kohdetta "{ $name }" kohteen "{ $parent }" alle
|
||||
created = Luotu kohde "{ $name }" kohteen "{ $parent }" alle
|
||||
copying =
|
||||
Kopioidaan { $items } { $items ->
|
||||
[one] kohde
|
||||
*[other] kohdetta
|
||||
} sijainnista "{ $from }" kohteeseen "{ $to }" ({ $progress })…
|
||||
*[other] kohteita
|
||||
} lähteestä "{ $from }" kohteeseen "{ $to }"
|
||||
copied =
|
||||
Kopioitu { $items } { $items ->
|
||||
[one] kohde
|
||||
*[other] kohdetta
|
||||
} sijainnista "{ $from }" kohteeseen "{ $to }"
|
||||
emptying-trash = Tyhjennetään { trash } ({ $progress })…
|
||||
*[other] kohteet
|
||||
} lähteestä "{ $from }" kohteeseen "{ $to }"
|
||||
emptying-trash = Tyhjennetään { trash }
|
||||
emptied-trash = Tyhjennetty { trash }
|
||||
extracting =
|
||||
Puretaan { $items } { $items ->
|
||||
[one] kohde
|
||||
*[other] kohdetta
|
||||
} arkistosta "{ $from }" kohteeseen "{ $to }" ({ $progress })…
|
||||
*[other] kohteet
|
||||
} arkistosta "{ $from }" kohteeseen "{ $to }"
|
||||
extracted =
|
||||
Purettu { $items } { $items ->
|
||||
[one] kohde
|
||||
*[other] kohdetta
|
||||
*[other] kohteet
|
||||
} arkistosta "{ $from }" kohteeseen "{ $to }"
|
||||
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
|
||||
moving =
|
||||
Siirretään { $items } { $items ->
|
||||
[one] kohde
|
||||
*[other] kohdetta
|
||||
} sijainnista "{ $from }" kohteeseen "{ $to }" ({ $progress })…
|
||||
*[other] kohteet
|
||||
} lähteestä "{ $from }" kohteeseen "{ $to }"
|
||||
moved =
|
||||
Siirretty { $items } { $items ->
|
||||
[one] kohde
|
||||
*[other] kohdetta
|
||||
} sijainnista "{ $from }" kohteeseen "{ $to }"
|
||||
*[other] kohteet
|
||||
} lähteestä "{ $from }" kohteeseen "{ $to }"
|
||||
renaming = Nimetään kohde "{ $from }" muotoon "{ $to }"
|
||||
renamed = Nimetty kohde "{ $from }" muotoon "{ $to }"
|
||||
restoring =
|
||||
Palautetaan { $items } { $items ->
|
||||
[one] kohde
|
||||
*[other] kohdetta
|
||||
} roskakorista ({ $progress })…
|
||||
*[other] kohteet
|
||||
} { trash }sta
|
||||
restored =
|
||||
Palautettu { $items } { $items ->
|
||||
[one] kohde
|
||||
*[other] kohdetta
|
||||
} roskakorista
|
||||
unknown-folder = tuntematon kansio
|
||||
*[other] kohteet
|
||||
} { trash }sta
|
||||
unknown-folder = Tuntematon kansio
|
||||
|
||||
## Open with
|
||||
|
||||
menu-open-with = Avaa sovelluksella…
|
||||
menu-open-with = Avaa ohjelmalla…
|
||||
default-app = { $name } (oletus)
|
||||
|
||||
## Show details
|
||||
|
|
@ -225,14 +225,14 @@ settings = Asetukset
|
|||
|
||||
appearance = Ulkoasu
|
||||
theme = Teema
|
||||
match-desktop = Sovita työpöytään
|
||||
match-desktop = Sovita yhteen työpöydän kanssa
|
||||
dark = Tumma
|
||||
light = Vaalea
|
||||
|
||||
# Context menu
|
||||
|
||||
add-to-sidebar = Lisää sivupalkkiin
|
||||
compress = Pakkaa…
|
||||
compress = Pakkaa
|
||||
extract-here = Pura
|
||||
new-file = Uusi tiedosto…
|
||||
new-folder = Uusi kansio…
|
||||
|
|
@ -241,9 +241,9 @@ move-to-trash = Siirrä roskakoriin
|
|||
restore-from-trash = Palauta roskakorista
|
||||
remove-from-sidebar = Poista sivupalkista
|
||||
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-trashed = Järjestä poistamisajan mukaan
|
||||
sort-by-trashed = Järjestä poistamispäivämäärän mukaan
|
||||
|
||||
## Desktop
|
||||
|
||||
|
|
@ -281,9 +281,9 @@ grid-view = Ruudukkonäkymä
|
|||
list-view = Listanäkymä
|
||||
show-hidden-files = Näytä piilotetut tiedostot
|
||||
list-directories-first = Näytä kansiot ensin
|
||||
gallery-preview = Gallerian esikatselu
|
||||
gallery-preview = Gallerian esinäkymä
|
||||
menu-settings = Asetukset…
|
||||
menu-about = Tietoa COSMICin tiedostonhallinnasta…
|
||||
menu-about = Tietoa COSMIC Tiedostoista…
|
||||
|
||||
## Sort
|
||||
|
||||
|
|
@ -318,90 +318,3 @@ move-to-button-label = Siirrä
|
|||
clear-recents-history = Tyhjennä viimeaikaisten historia
|
||||
copy-path = Kopioi polku
|
||||
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
|
||||
copy-path = Copier le chemin
|
||||
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
|
||||
clear-recents-history = Glan stair na n-earraí le déanaí
|
||||
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
|
||||
copy-path = Útvonal másolása
|
||||
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
|
||||
clear-recents-history = Bersihkan riwayat Terbaru
|
||||
copy-path = Salin jalur
|
||||
mixed = Bercampur
|
||||
|
|
|
|||
|
|
@ -1,322 +1 @@
|
|||
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-select = Бірінші сәйкес келетін файлды немесе буманы таңдайды
|
||||
add-to-sidebar = Бүйірлік панельге қосу
|
||||
compress = Сығу...
|
||||
compress = Сығу
|
||||
delete-permanently = Біржолата өшіру
|
||||
eject = Шығару
|
||||
extract-here = Тарқату
|
||||
|
|
@ -318,4 +318,3 @@ keywords = Folder;Manager;Бума;Басқарушы;
|
|||
show-recents = Бүйір панеліндегі «Жуырдағы құжаттар» бумасы
|
||||
clear-recents-history = Жуырдағылар тарихын өшіру
|
||||
copy-path = Орналасқан жолын көшіру
|
||||
mixed = Аралас
|
||||
|
|
|
|||
|
|
@ -110,7 +110,7 @@ dismiss = 메시지 무시
|
|||
copy_noun = 복사
|
||||
progress = { $percent }%
|
||||
related-apps = 관련 앱
|
||||
compress = 압축...
|
||||
compress = 압축
|
||||
network-drive-error = 네트워크 드라이브에 접근할 수 없음
|
||||
icon-size-and-spacing = 아이콘 크기 및 간격
|
||||
password = 암호
|
||||
|
|
@ -272,8 +272,3 @@ network-drive-schemes =
|
|||
WebDAV,dav:// 또는 davs://
|
||||
type-to-search-select = 일치하는 첫 번째 파일 또는 폴더를 선택합니다
|
||||
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-enter-path = Įvedamas aplanko ar failo kelias
|
||||
add-to-sidebar = Pridėti į šonjuostę
|
||||
compress = Suspausti...
|
||||
compress = Suspausti
|
||||
delete-permanently = Ištrinti visam laikui
|
||||
eject = Išstumti
|
||||
extract-here = Išskleisti
|
||||
|
|
|
|||
|
|
@ -32,7 +32,7 @@ pause = ਵਿਰਾਮ
|
|||
resume = ਮੁੜ-ਚਾਲੂ
|
||||
create-archive = ਅਕਾਇਵ ਬਣਾਓ
|
||||
extract-password-required = ਪਾਸਵਰਡ ਚਾਹੀਦਾ ਹੈ
|
||||
extract-to = ਖਿਲਾਰੋ...
|
||||
extract-to = Extract To...
|
||||
extract-to-title = ਫੋਲਡਰ ਵਿੱਚ ਖਿਲਾਰੋ
|
||||
empty-trash = ਰੱਦੀ ਨੂੰ ਖਾਲੀ ਕਰੋ
|
||||
empty-trash-title = ਰੱਦੀ ਨੂੰ ਖਾਲੀ ਕਰਨਾ ਹੈ?
|
||||
|
|
@ -113,7 +113,7 @@ replace-title = "{ $filename }" ਪਹਿਲਾਂ ਹੀ ਇਸ ਟਿਕਾਣ
|
|||
favorite-path-error = ਡਾਇਰੈਕਟਰੀ ਖੋਲ੍ਹਣ ਦੌਰਾਨ ਗਲਤੀ
|
||||
add-network-drive = ਨੈੱਟਵਰਕ ਡਰਾਇਵ ਜੋੜੋ
|
||||
connect-anonymously = ਅਣਪਛਾਤੇ ਵਜੋਂ ਕਨੈਕਟ ਕਰੋ
|
||||
connecting = ਕਨੈਕਟ ਕੀਤਾ ਜਾ ਰਿਹਾ ਹੈ...
|
||||
connecting = Connecting...
|
||||
domain = ਡੋਮੇਨ
|
||||
enter-server-address = ਸਰਵਰ ਦਾ ਸਿਰਨਾਵਾਂ ਦਿਓ
|
||||
password = ਪਾਸਵਰਡ
|
||||
|
|
@ -138,9 +138,9 @@ sort-by-modified = ਸੋਧ ਨਾਲ ਲੜੀਬੱਧ
|
|||
sort-by-size = ਆਕਾਰ ਨਾਲ ਲੜੀਬੱਧ
|
||||
sort-by-trashed = ਹਟਾਉਣ ਸਮੇਂ ਨਾਲ ਲੜੀਬੱਧ
|
||||
remove-from-recents = ਸੱਜਰਿਆਂ ਵਿੱਚੋਂ ਹਟਾਓ
|
||||
change-wallpaper = ਵਾਲਪੇਪਰ ਨੂੰ ਬਦਲੋ...
|
||||
desktop-appearance = ਡੈਸਕਟਾਪ ਦੀ ਦਿੱਖ...
|
||||
display-settings = ਡਿਸਪਲੇਅ ਸੈਟਿੰਗਾਂ...
|
||||
change-wallpaper = Change wallpaper...
|
||||
desktop-appearance = Desktop appearance...
|
||||
display-settings = Display settings...
|
||||
file = ਫ਼ਾਇਲ
|
||||
new-tab = ਨਵੀਂ ਟੈਬ
|
||||
new-window = ਨਵੀਂ ਵਿੰਡੋ
|
||||
|
|
@ -195,11 +195,11 @@ type-to-search = ਖੋਜਣ ਲਈ ਲਿਖੋ
|
|||
type-to-search-recursive = ਮੌਜੂਦਾ ਫੋਲਡਰ ਅਤੇ ਸਭ ਅਧੀਨ-ਫੋਲਡਰਾਂ ਵਿੱਚ ਖੋਜੋ
|
||||
add-to-sidebar = ਬਾਹੀ ਵਿੱਚ ਜੋੜੋ
|
||||
compress = ਕੰਪਰੈਸ
|
||||
copy-to = ਇੱਥੇ ਕਾਪੀ ਕਰੋ...
|
||||
copy-to = Copy to...
|
||||
delete-permanently = ਪੱਕੇ ਤੌਰ ਉੱਤੇ ਹਟਾਓ
|
||||
eject = ਬਾਹਰ
|
||||
extract-here = ਖਿਲਾਰੋ
|
||||
move-to = ਇੱਥੇ ਭੇਜੋ...
|
||||
move-to = Move to...
|
||||
remove-from-sidebar = ਬਾਹੀ ਵਿੱਚੋਂ ਹਟਾਓ
|
||||
reload-folder = ਫੋਲਡਰ ਨੂੰ ਮੁੜ-ਲੋਡ ਕਰੋ
|
||||
list-view = ਸੂਚੀ ਝਲਕ
|
||||
|
|
|
|||
|
|
@ -437,4 +437,3 @@ move-to = Przenieś do…
|
|||
show-recents = Ostatnie katalogi w panelu bocznym
|
||||
clear-recents-history = Wyczyść bierzącą historię
|
||||
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?
|
||||
type-to-search-select = Seleciona o primeiro arquivo ou pasta correspondente
|
||||
pasted-image = Imagem colada
|
||||
pasted-text = Texto colado
|
||||
pasted-video = Vídeo colado
|
||||
pasted-text = Texto copiado
|
||||
pasted-video = Vídeo copiado
|
||||
copy-to-title = Selecione o destino da cópia
|
||||
copy-to-button-label = Copiar
|
||||
move-to-title = Selecione o destino da movimentação
|
||||
|
|
@ -433,7 +433,6 @@ move-to-button-label = Mover
|
|||
copy-to = Copiar para...
|
||||
move-to = Mover para...
|
||||
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
|
||||
copy-path = Copiar caminho
|
||||
mixed = Misto
|
||||
|
|
|
|||
|
|
@ -195,7 +195,7 @@ dark = Тёмная
|
|||
light = Светлая
|
||||
# Context menu
|
||||
add-to-sidebar = Добавить на боковую панель
|
||||
compress = Сжать...
|
||||
compress = Сжать
|
||||
extract-here = Распаковать
|
||||
new-file = Новый файл…
|
||||
new-folder = Новая папка…
|
||||
|
|
@ -380,4 +380,3 @@ keywords = Папка;Менеджер;
|
|||
show-recents = «Недавние документы» в бок. панели
|
||||
clear-recents-history = Очистить историю недавних
|
||||
copy-path = Копировать путь
|
||||
mixed = Смешанные
|
||||
|
|
|
|||
|
|
@ -317,7 +317,7 @@ single-click = Відкривати одним клацанням
|
|||
type-to-search = Введіть для пошуку
|
||||
type-to-search-recursive = Шукає у поточній теці та всіх підтеках
|
||||
type-to-search-enter-path = Вводить шлях до каталогу або файлу
|
||||
compress = Стиснути...
|
||||
compress = Стиснути
|
||||
delete-permanently = Остаточно видалити
|
||||
eject = Безпечно вилучити
|
||||
extract-here = Видобути
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ recents = 最近访问
|
|||
undo = 撤销
|
||||
today = 今天
|
||||
# Desktop view options
|
||||
desktop-view-options = 桌面视图选项…
|
||||
desktop-view-options = 桌面视图选项...
|
||||
show-on-desktop = 在桌面显示
|
||||
desktop-folder-content = 桌面文件夹内容
|
||||
mounted-drives = 已装载驱动器
|
||||
|
|
@ -31,7 +31,7 @@ operations-running =
|
|||
正在进行 { $running } { $running ->
|
||||
[one] 个操作
|
||||
*[other] 个操作
|
||||
}({ $percent }%)…
|
||||
}({ $percent }%)...
|
||||
operations-running-finished =
|
||||
正在进行 { $running } { $running ->
|
||||
[one] 个操作
|
||||
|
|
@ -50,7 +50,7 @@ create-archive = 创建压缩包
|
|||
## Extract Dialog
|
||||
|
||||
extract-password-required = 需要密码
|
||||
extract-to = 提取到…
|
||||
extract-to = 提取到...
|
||||
extract-to-title = 提取到文件夹
|
||||
|
||||
## Empty Trash Dialog
|
||||
|
|
@ -317,7 +317,7 @@ unknown-folder = 未知文件夹
|
|||
|
||||
## Open with
|
||||
|
||||
menu-open-with = 打开方式…
|
||||
menu-open-with = 打开方式...
|
||||
default-app = { $name }(默认)
|
||||
|
||||
## Show details
|
||||
|
|
@ -329,7 +329,7 @@ item-size = 文件大小:{ $size }
|
|||
item-created = 创建于:{ $created }
|
||||
item-modified = 修改于:{ $modified }
|
||||
item-accessed = 访问于:{ $accessed }
|
||||
calculating = 计算中…
|
||||
calculating = 计算中...
|
||||
|
||||
## Settings
|
||||
|
||||
|
|
@ -355,8 +355,8 @@ compress = 压缩…
|
|||
delete-permanently = 永久删除
|
||||
eject = 弹出
|
||||
extract-here = 解压到此处
|
||||
new-file = 新建文件…
|
||||
new-folder = 新建文件夹…
|
||||
new-file = 新建文件...
|
||||
new-folder = 新建文件夹...
|
||||
open-in-terminal = 在终端模拟器中打开
|
||||
move-to-trash = 移动到回收站
|
||||
restore-from-trash = 从回收站中还原
|
||||
|
|
@ -369,9 +369,9 @@ remove-from-recents = 从最近访问中移除
|
|||
|
||||
## Desktop
|
||||
|
||||
change-wallpaper = 更改壁纸…
|
||||
desktop-appearance = 桌面外观…
|
||||
display-settings = 显示设置…
|
||||
change-wallpaper = 更改壁纸...
|
||||
desktop-appearance = 桌面外观...
|
||||
display-settings = 显示设置...
|
||||
|
||||
# Menu
|
||||
|
||||
|
|
@ -382,7 +382,7 @@ file = 文件
|
|||
new-tab = 新建标签
|
||||
new-window = 新建窗口
|
||||
reload-folder = 刷新文件夹
|
||||
rename = 重命名…
|
||||
rename = 重命名...
|
||||
close-tab = 关闭标签
|
||||
quit = 退出
|
||||
|
||||
|
|
@ -405,7 +405,7 @@ list-view = 列表视图
|
|||
show-hidden-files = 显示隐藏文件
|
||||
list-directories-first = 优先列出目录
|
||||
gallery-preview = 图库预览
|
||||
menu-settings = 设置…
|
||||
menu-settings = 设置...
|
||||
menu-about = 关于 COSMIC 文件…
|
||||
|
||||
## Sort
|
||||
|
|
@ -435,5 +435,4 @@ comment = COSMIC 桌面的文件管理器
|
|||
keywords = 文件夹;管理器;
|
||||
clear-recents-history = 清除最近访问历史
|
||||
copy-path = 复制文件路径
|
||||
show-recents = 侧边栏中的最近访问
|
||||
mixed = 混合
|
||||
show-recents = 侧边栏显示最近访问
|
||||
|
|
|
|||
|
|
@ -1,11 +1,11 @@
|
|||
cosmic-files = COSMIC 檔案
|
||||
cosmic-files = COSMIC 檔案總管
|
||||
empty-folder = 空資料夾
|
||||
empty-folder-hidden = 空資料夾(包含隱藏項目)
|
||||
no-results = 找不到結果
|
||||
filesystem = 檔案系統
|
||||
home = 家目錄
|
||||
home = 主目錄
|
||||
networks = 網路
|
||||
notification-in-progress = 檔案操作正在進行中
|
||||
notification-in-progress = 檔案操作正在進行中。
|
||||
trash = 垃圾桶
|
||||
recents = 最近使用
|
||||
undo = 復原
|
||||
|
|
@ -25,7 +25,7 @@ create-archive = 建立壓縮檔案
|
|||
## Empty Trash Dialog
|
||||
|
||||
empty-trash = 清空垃圾桶
|
||||
empty-trash-warning = 垃圾桶中的項目將被永久刪除
|
||||
empty-trash-warning = 你確定要永久刪除垃圾桶中的所有項目嗎?
|
||||
|
||||
## New File/Folder Dialog
|
||||
|
||||
|
|
@ -33,16 +33,16 @@ create-new-file = 建立新檔案
|
|||
create-new-folder = 建立新資料夾
|
||||
file-name = 檔案名稱
|
||||
folder-name = 資料夾名稱
|
||||
file-already-exists = 相同名稱的檔案已經存在
|
||||
folder-already-exists = 相同名稱的資料夾已經存在
|
||||
name-hidden = 以「.」開頭的名稱將會被隱藏
|
||||
name-invalid = 名稱不能是「{ $filename }」
|
||||
name-no-slashes = 名稱不能包含斜線
|
||||
file-already-exists = 已存在同名檔案。
|
||||
folder-already-exists = 已存在同名資料夾。
|
||||
name-hidden = 以「.」開頭的名稱將會被隱藏。
|
||||
name-invalid = 名稱不能是 「{ $filename }」。
|
||||
name-no-slashes = 名稱不能包含斜線。
|
||||
|
||||
## Open/Save Dialog
|
||||
|
||||
cancel = 取消
|
||||
create = 創作
|
||||
create = 建立
|
||||
open = 開啟
|
||||
open-file = 開啟檔案
|
||||
open-folder = 開啟資料夾
|
||||
|
|
@ -62,12 +62,12 @@ rename-folder = 重新命名資料夾
|
|||
## Replace Dialog
|
||||
|
||||
replace = 取代
|
||||
replace-title = 「{ $filename }」已存在於此位置
|
||||
replace-title = 檔案「{ $filename }」已存在於此位置。
|
||||
replace-warning = 你要取代它嗎?取代將覆蓋其內容。
|
||||
replace-warning-operation = 你要取代它嗎?取代將覆蓋其內容。
|
||||
original-file = 原始檔案
|
||||
replace-with = 取代為
|
||||
apply-to-all = 套用至全部
|
||||
apply-to-all = 套用至所有項目
|
||||
keep-both = 保留兩者
|
||||
skip = 跳過
|
||||
|
||||
|
|
@ -95,13 +95,13 @@ network-drive-description =
|
|||
伺服器地址包括協定前綴和地址。
|
||||
範例:ssh://192.168.0.1, ftp://[2001:db8::1]
|
||||
network-drive-schemes =
|
||||
可用協定,前綴
|
||||
AppleTalk,afp://
|
||||
檔案傳輸協定,ftp:// 或 ftps://
|
||||
網路檔案系統,nfs://
|
||||
伺服器訊息區塊,smb://
|
||||
SSH 檔案傳輸協定,sftp:// 或 ssh://
|
||||
WebDav,dav:// 或 davs://
|
||||
可用協定,前綴
|
||||
AppleTalk,afp://
|
||||
檔案傳輸協定,ftp:// 或 ftps://
|
||||
網路檔案系統,nfs://
|
||||
伺服器訊息區塊,smb://
|
||||
SSH 檔案傳輸協定,sftp:// 或 ssh://
|
||||
WebDav,dav:// 或 davs://
|
||||
network-drive-error = 無法存取網路磁碟機
|
||||
password = 密碼
|
||||
remember-password = 記住密碼
|
||||
|
|
@ -112,7 +112,7 @@ username = 使用者名稱
|
|||
|
||||
edit-history = 編輯歷史
|
||||
history = 歷史紀錄
|
||||
no-history = 無歷史記錄項目。
|
||||
no-history = 沒有歷史項目。
|
||||
pending = 待處理
|
||||
failed = 失敗
|
||||
complete = 完成
|
||||
|
|
@ -133,51 +133,51 @@ copying =
|
|||
正在複製 { $items } { $items ->
|
||||
[one] 項目
|
||||
*[other] 項目
|
||||
}從「{ $from }」到「{ $to }」({ $progress })...
|
||||
} 從「{ $from }」到「{ $to }」({ $progress })...
|
||||
copied =
|
||||
已複製 { $items } { $items ->
|
||||
[one] 項目
|
||||
*[other] 項目
|
||||
}從「{ $from }」到「{ $to }」
|
||||
emptying-trash = 正在清空 { trash }({ $progress })…
|
||||
emptied-trash = 已經清空 { trash }
|
||||
emptying-trash = 正在清空{ trash }({ $progress })…
|
||||
emptied-trash = 已清空{ trash }
|
||||
extracting =
|
||||
正在解壓縮 { $items } 項目 { $items ->
|
||||
[one] 項目
|
||||
*[other] 項目
|
||||
}從「{ $from }」至「{ $to }」({ $progress })...
|
||||
} 從「{ $from }」到「{ $to }」({ $progress })...
|
||||
extracted =
|
||||
已解壓縮 { $items } 項目 { $items ->
|
||||
[one] 項目
|
||||
*[other] 項目
|
||||
}從「{ $from }」到「{ $to }」
|
||||
} 從 { $from } 到 { $to }
|
||||
moving =
|
||||
正在移動 { $items } { $items ->
|
||||
[one] 項目
|
||||
*[other] 項目
|
||||
}從「{ $from }」到「{ $to }」({ $progress })...
|
||||
} 從「{ $from }」到「{ $to }」({ $progress })...
|
||||
moved =
|
||||
已經移動 { $items } { $items ->
|
||||
已移動 { $items } { $items ->
|
||||
[one] 項目
|
||||
*[other] 項目
|
||||
} 從「{ $from }」至「{ $to }」
|
||||
renaming = 正在重新命名「{ $from }」至「{ $to }」
|
||||
renamed = 已經重新命名「{ $from }」至「{ $to }」
|
||||
} 從「{ $from }」到「{ $to }」
|
||||
renaming = 正在重新命名「{ $from }」為「{ $to }」
|
||||
renamed = 已重新命名 { $from } 為 { $to }
|
||||
restoring =
|
||||
正在還原 { $items } 項目 { $items ->
|
||||
[one] 項目
|
||||
*[other] 項目
|
||||
}自 { trash } ({ $progress })...
|
||||
} 從{ trash }({ $progress })...
|
||||
restored =
|
||||
已經還原 { $items } 項目 { $items ->
|
||||
已還原 { $items } 項目 { $items ->
|
||||
[one] 項目
|
||||
*[other] 項目
|
||||
}從 { trash }
|
||||
unknown-folder = 不明資料夾
|
||||
} 從{ trash }
|
||||
unknown-folder = 未知資料夾
|
||||
|
||||
## Open with
|
||||
|
||||
menu-open-with = 開啟檔案...
|
||||
menu-open-with = 開啟方式...
|
||||
default-app = { $name } (預設)
|
||||
|
||||
## Show details
|
||||
|
|
@ -192,15 +192,15 @@ settings = 設定
|
|||
|
||||
appearance = 外觀
|
||||
theme = 主題
|
||||
match-desktop = 符合桌面
|
||||
dark = 深色
|
||||
light = 淺色
|
||||
match-desktop = 與桌面一致
|
||||
dark = 暗色模式
|
||||
light = 亮色模式
|
||||
# Context menu
|
||||
add-to-sidebar = 添加至側邊欄
|
||||
compress = 壓縮…
|
||||
extract-here = 解壓縮
|
||||
new-file = 新建檔案...
|
||||
new-folder = 新建資料夾...
|
||||
add-to-sidebar = 加入側邊欄
|
||||
compress = 壓縮
|
||||
extract-here = 解壓縮至此
|
||||
new-file = 新檔案...
|
||||
new-folder = 新資料夾...
|
||||
open-in-terminal = 在終端機中開啟
|
||||
move-to-trash = 移動至垃圾桶
|
||||
restore-from-trash = 從垃圾桶還原
|
||||
|
|
@ -215,8 +215,8 @@ sort-by-size = 依大小排序
|
|||
## File
|
||||
|
||||
file = 檔案
|
||||
new-tab = 新建分頁
|
||||
new-window = 新建視窗
|
||||
new-tab = 新分頁
|
||||
new-window = 新視窗
|
||||
rename = 重新命名...
|
||||
close-tab = 關閉分頁
|
||||
quit = 退出
|
||||
|
|
@ -238,36 +238,36 @@ view = 檢視
|
|||
grid-view = 網格檢視
|
||||
list-view = 列表檢視
|
||||
show-hidden-files = 顯示隱藏檔案
|
||||
list-directories-first = 目錄優先列出
|
||||
list-directories-first = 優先列出目錄
|
||||
menu-settings = 設定...
|
||||
menu-about = 關於 COSMIC 檔案...
|
||||
menu-about = 關於 COSMIC 檔案總管...
|
||||
|
||||
## Sort
|
||||
|
||||
sort = 排序
|
||||
sort-a-z = A-Z
|
||||
sort-z-a = Z-A
|
||||
sort-newest-first = 最新優先
|
||||
sort-oldest-first = 最舊優先
|
||||
sort-smallest-to-largest = 從小到大
|
||||
sort-largest-to-smallest = 從大到小
|
||||
sort-newest-first = 最新的在前
|
||||
sort-oldest-first = 最舊的在前
|
||||
sort-smallest-to-largest = 由小至大
|
||||
sort-largest-to-smallest = 由大至小
|
||||
deleted =
|
||||
已經刪除 { $items } { $items ->
|
||||
已刪除 { $items } { $items ->
|
||||
[one] 項目
|
||||
*[other] 項目
|
||||
}從 { trash }
|
||||
}從{ trash }
|
||||
permanently-deleting =
|
||||
正在永久刪除 { $items } { $items ->
|
||||
[one] 项目
|
||||
*[other] 项目
|
||||
}
|
||||
permanently-deleted =
|
||||
已經永久刪除 { $items } { $items ->
|
||||
已永久刪除 { $items } { $items ->
|
||||
[one] 项目
|
||||
*[other] 项目
|
||||
}
|
||||
removing-from-recents =
|
||||
正在從 { recents } 中移除 { $items } { $items ->
|
||||
正在從{ recents }中移除 { $items } { $items ->
|
||||
[one] 项目
|
||||
*[other] 项目
|
||||
}
|
||||
|
|
@ -275,112 +275,9 @@ deleting =
|
|||
正在刪除 { $items } { $items ->
|
||||
[one] 项目
|
||||
*[other] 项目
|
||||
}從 { trash }({ $progress })…
|
||||
}從{ trash }({ $progress })…
|
||||
removed-from-recents =
|
||||
已經從 { recents } 中移除 { $items } { $items ->
|
||||
已從{ recents }中移除 { $items } { $items ->
|
||||
[one] 项目
|
||||
*[other] 项目
|
||||
}
|
||||
repository = 軟體庫源
|
||||
desktop-view-options = 桌面檢視選項...
|
||||
show-on-desktop = 顯示在桌面
|
||||
desktop-folder-content = 桌面資料夾內容
|
||||
mounted-drives = 已經掛載的磁碟機
|
||||
trash-folder-icon = 垃圾桶圖示
|
||||
trashed-on = 遺棄時間
|
||||
icon-size-and-spacing = 圖示大小與間距
|
||||
icon-size = 圖示大小
|
||||
grid-spacing = 網格間距
|
||||
details = 詳情
|
||||
dismiss = 撤停訊息
|
||||
delete = 刪除
|
||||
remove = 移除
|
||||
support = 支援
|
||||
cancelled = 已取消
|
||||
keywords = 資料夾;管理器;
|
||||
empty-trash-title = 清空垃圾桶?
|
||||
pause = 暫停
|
||||
resume = 繼續
|
||||
extract-password-required = 需要密碼
|
||||
extract-to = 解壓縮至...
|
||||
extract-to-title = 解壓縮至資料夾
|
||||
mount-error = 無法存取磁碟機
|
||||
open-with-title = 您要如何開啟「{ $name }」?
|
||||
browse-store = 瀏覽 { $store }
|
||||
other-apps = 其他應用程式
|
||||
related-apps = 相關應用程式
|
||||
permanently-delete-question = 永久刪除?
|
||||
set-executable-and-launch = 設定為可以執行並啟動
|
||||
read-only = 唯讀
|
||||
read-execute = 讀取和執行
|
||||
read-write = 讀取和寫入
|
||||
read-write-execute = 讀取、寫入和執行
|
||||
favorite-path-error = 開啟目錄時發生錯誤
|
||||
set-executable-and-launch-description = 您是否要將「{ $name }」設為可執行並啟動它?
|
||||
set-and-launch = 設定並啟動
|
||||
none = 無
|
||||
execute-only = 僅執行
|
||||
write-only = 僅寫入
|
||||
write-execute = 寫入和執行
|
||||
operations-running =
|
||||
{ $running } { $running ->
|
||||
[one] 個操作
|
||||
*[other] 個操作
|
||||
}正在執行({ $percent }%)...
|
||||
operations-running-finished =
|
||||
{ $running } { $running ->
|
||||
[one] 個操作
|
||||
*[other] 個操作
|
||||
}正在執行({ $percent }%), { $finished } 個已經完成...
|
||||
permanently-delete-warning = 「{ $target }」將被永久刪除。此操作無法復原。
|
||||
open-with = 開啟檔案
|
||||
selected-items = 已經選定 { $items } 個項目
|
||||
copy-to-title = 選擇複製目的地
|
||||
copy-to-button-label = 複製
|
||||
move-to-title = 選擇移動目的地
|
||||
move-to-button-label = 移動
|
||||
keep = 保留
|
||||
progress = { $percent }%
|
||||
progress-cancelled = { $percent }%,已經取消
|
||||
progress-failed = { $percent }%,失敗
|
||||
progress-paused = { $percent }%,已經暫停
|
||||
favorite-path-error-description =
|
||||
無法開啟「{ $path }」
|
||||
「{ $path }」可能不存在,或您可能沒有權限開啟它。
|
||||
|
||||
您是否要將它從側邊欄移除?
|
||||
comment = COSMIC 桌面檔案管理器
|
||||
pasted-image = 已經貼上的圖片
|
||||
pasted-text = 已經貼上的文字
|
||||
pasted-video = 已經貼上的影片
|
||||
sort-by-trashed = 依丟入時間排序
|
||||
calculating = 計算中...
|
||||
single-click = 點按以開啟
|
||||
type-to-search = 輸入進行搜尋
|
||||
type-to-search-recursive = 搜尋目前資料夾及全部子資料夾
|
||||
type-to-search-enter-path = 輸入目錄或檔案的路徑
|
||||
delete-permanently = 永久刪除
|
||||
eject = 彈出
|
||||
remove-from-recents = 從最近項目中移除
|
||||
change-wallpaper = 變更桌布...
|
||||
desktop-appearance = 桌面外觀...
|
||||
display-settings = 顯示設定...
|
||||
reload-folder = 重新載入資料夾
|
||||
gallery-preview = 圖庫預覽
|
||||
type = 類型:{ $mime }
|
||||
items = 項目:{ $items }
|
||||
item-size = 大小:{ $size }
|
||||
item-created = 建立時間:{ $created }
|
||||
item-modified = 修改時間:{ $modified }
|
||||
item-accessed = 存取時間:{ $accessed }
|
||||
type-to-search-select = 選取第一個符合條件的檔案或資料夾
|
||||
copy-to = 複製至...
|
||||
move-to = 移動至...
|
||||
show-recents = 側邊欄中的最近使用資料夾
|
||||
clear-recents-history = 清除最近使用歷史記錄
|
||||
copy-path = 複製路徑
|
||||
setting-executable-and-launching = 設定「{ $name }」為可以執行並進行啟動
|
||||
set-executable-and-launched = 設定「{ $name }」為可以執行並已經啟動
|
||||
setting-permissions = 設定「{ $name }」的權限至 { $mode }
|
||||
set-permissions = 設定「{ $name }」的權限至 { $mode }
|
||||
mixed = 混合
|
||||
|
|
|
|||
58
src/app.rs
58
src/app.rs
|
|
@ -18,7 +18,9 @@ use cosmic::{
|
|||
Application, ApplicationExt, Element,
|
||||
app::{self, Core, Task, context_drawer},
|
||||
cosmic_config::{self, ConfigSet},
|
||||
cosmic_theme, executor,
|
||||
cosmic_theme,
|
||||
desktop::fde::DesktopEntry,
|
||||
executor,
|
||||
iced::core::widget::operation::focusable::unfocus,
|
||||
iced::runtime::{clipboard, task},
|
||||
iced::widget::{button::focus, scrollable::AbsoluteOffset},
|
||||
|
|
@ -97,7 +99,6 @@ use crate::{
|
|||
self, HOVER_DURATION, HeadingOptions, ItemMetadata, Location, SORT_OPTION_FALLBACK,
|
||||
SearchLocation, Tab,
|
||||
},
|
||||
trash::{Trash, TrashExt},
|
||||
zoom::{zoom_in_view, zoom_out_view, zoom_to_default},
|
||||
};
|
||||
|
||||
|
|
@ -833,12 +834,9 @@ impl App {
|
|||
|
||||
// First launch apps that can be launched directly
|
||||
if mime == "application/x-desktop" {
|
||||
#[cfg(feature = "desktop")]
|
||||
{
|
||||
// Try opening desktop application
|
||||
Self::launch_desktop_entries(&paths);
|
||||
continue;
|
||||
}
|
||||
// Try opening desktop application
|
||||
Self::launch_desktop_entries(&paths);
|
||||
continue;
|
||||
} else if mime == "application/x-executable" || mime == "application/vnd.appimage" {
|
||||
// Try opening executable
|
||||
for path in paths {
|
||||
|
|
@ -899,10 +897,7 @@ impl App {
|
|||
Task::batch(tasks)
|
||||
}
|
||||
|
||||
#[cfg(feature = "desktop")]
|
||||
fn launch_desktop_entries(paths: &[impl AsRef<Path>]) {
|
||||
use cosmic::desktop::fde::DesktopEntry;
|
||||
|
||||
for path in paths.iter().map(AsRef::as_ref) {
|
||||
match DesktopEntry::from_path::<&str>(path, None) {
|
||||
Ok(entry) => match entry.exec() {
|
||||
|
|
@ -1799,7 +1794,7 @@ impl App {
|
|||
|
||||
nav_model = nav_model.insert(|b| {
|
||||
b.text(fl!("trash"))
|
||||
.icon(icon::icon(Trash::icon_symbolic(16)))
|
||||
.icon(icon::icon(tab::trash_helpers::trash_icon_symbolic(16)))
|
||||
.data(Location::Trash)
|
||||
.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(
|
||||
fl!("empty-trash"),
|
||||
None,
|
||||
|
|
@ -4240,8 +4237,10 @@ impl Application for App {
|
|||
.is_some_and(|loc| matches!(loc, Location::Trash))
|
||||
});
|
||||
if let Some(entity) = maybe_entity {
|
||||
self.nav_model
|
||||
.icon_set(entity, icon::icon(Trash::icon_symbolic(16)));
|
||||
self.nav_model.icon_set(
|
||||
entity,
|
||||
icon::icon(tab::trash_helpers::trash_icon_symbolic(16)),
|
||||
);
|
||||
}
|
||||
|
||||
return Task::batch([self.rescan_trash(), self.update_desktop()]);
|
||||
|
|
@ -4263,14 +4262,12 @@ impl Application for App {
|
|||
.collect();
|
||||
if !selected.is_empty() {
|
||||
//TODO: batch rename
|
||||
let mut last_name = String::new();
|
||||
let tasks: Vec<_> = selected
|
||||
let tasks = selected
|
||||
.into_iter()
|
||||
.filter_map(|path| {
|
||||
let parent = path.parent()?.to_path_buf();
|
||||
let name = path.file_name()?.to_str()?.to_string();
|
||||
let dir = path.is_dir();
|
||||
last_name = name.clone();
|
||||
Some(self.dialog_pages.push_back(DialogPage::RenameItem {
|
||||
from: path,
|
||||
parent,
|
||||
|
|
@ -4278,15 +4275,9 @@ impl Application for App {
|
|||
dir,
|
||||
}))
|
||||
})
|
||||
.collect();
|
||||
let tasks = tasks.into_iter().chain([
|
||||
widget::text_input::focus(self.dialog_text_input.clone()),
|
||||
widget::text_input::select_until_last(
|
||||
.chain(std::iter::once(widget::text_input::focus(
|
||||
self.dialog_text_input.clone(),
|
||||
&last_name,
|
||||
'.',
|
||||
),
|
||||
]);
|
||||
)));
|
||||
return Task::batch(tasks);
|
||||
}
|
||||
}
|
||||
|
|
@ -6160,7 +6151,6 @@ impl Application for App {
|
|||
.into(),
|
||||
widget::text_input("", name.as_str())
|
||||
.id(self.dialog_text_input.clone())
|
||||
.double_click_select_delimiter('.')
|
||||
.on_input(move |name| {
|
||||
Message::DialogUpdate(DialogPage::RenameItem {
|
||||
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)) => {
|
||||
// Watch the "bins" themselves as well as the files folder where
|
||||
// trashed items are placed. This allows us to avoid recursively
|
||||
|
|
@ -6961,7 +6958,8 @@ impl Application for App {
|
|||
|_| {
|
||||
stream::channel(
|
||||
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 || {
|
||||
match notify_rust::Notification::new()
|
||||
.summary(&fl!("notification-in-progress"))
|
||||
|
|
@ -6971,6 +6969,8 @@ impl Application for App {
|
|||
Ok(notification) => {
|
||||
let _ = futures::executor::block_on(async {
|
||||
msg_tx
|
||||
.lock()
|
||||
.await
|
||||
.send(Message::Notification(Arc::new(
|
||||
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)) {
|
||||
let mut target = Vec::with_capacity(file.size() as usize);
|
||||
file.read_to_end(&mut target)?;
|
||||
// File no longer needed, drop to allow reading target on windows
|
||||
drop(file);
|
||||
|
||||
#[cfg(unix)]
|
||||
{
|
||||
|
|
@ -201,15 +199,11 @@ fn zip_extract<R: io::Read + io::Seek, P: AsRef<Path>>(
|
|||
#[cfg(windows)]
|
||||
{
|
||||
let Ok(target) = String::from_utf8(target) else {
|
||||
return Err(ZipError::InvalidArchive(
|
||||
"Invalid UTF-8 as symlink target".into(),
|
||||
));
|
||||
return Err(ZipError::InvalidArchive("Invalid UTF-8 as symlink target"));
|
||||
};
|
||||
let target_is_dir_from_archive = match password {
|
||||
None => archive.by_name(&target),
|
||||
Some(pwd) => archive.by_name_decrypt(&target, pwd.as_bytes()),
|
||||
}
|
||||
.map_or(false, |x| x.is_dir());
|
||||
let target = target.into_boxed_str();
|
||||
let target_is_dir_from_archive =
|
||||
archive.shared.files.contains_key(&target) && is_dir(&target);
|
||||
let target_path = directory.as_ref().join(OsString::from(target.to_string()));
|
||||
let target_is_dir = if target_is_dir_from_archive {
|
||||
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)]
|
||||
enum DialogPage {
|
||||
NewFolder { parent: PathBuf, name: String },
|
||||
Replace { filename: String },
|
||||
NewFolder {
|
||||
parent: PathBuf,
|
||||
name: String,
|
||||
},
|
||||
Replace {
|
||||
filename: String,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
|
|
@ -595,7 +600,6 @@ impl App {
|
|||
col = col.push(
|
||||
widget::text_input("", filename)
|
||||
.id(self.filename_id.clone())
|
||||
.double_click_select_delimiter('.')
|
||||
.on_input(Message::Filename)
|
||||
.on_submit(|_| Message::Save(false)),
|
||||
);
|
||||
|
|
@ -1958,16 +1962,6 @@ impl Application for App {
|
|||
if self.search_get().is_some() {
|
||||
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());
|
||||
}
|
||||
}
|
||||
|
|
@ -2037,7 +2031,7 @@ impl Application for App {
|
|||
}
|
||||
|
||||
col = col.push(
|
||||
self.tab
|
||||
self.tab
|
||||
.view(&self.key_binds, &self.modifiers, false, &[])
|
||||
.map(Message::TabMessage),
|
||||
);
|
||||
|
|
|
|||
53
src/lib.rs
53
src/lib.rs
|
|
@ -3,20 +3,14 @@
|
|||
|
||||
use cosmic::{app::Settings, iced::Limits};
|
||||
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;
|
||||
mod archive;
|
||||
pub mod channel;
|
||||
pub mod clipboard;
|
||||
pub mod config;
|
||||
mod context_action;
|
||||
use config::Config;
|
||||
pub mod config;
|
||||
pub mod dialog;
|
||||
mod key_bind;
|
||||
pub(crate) mod large_image;
|
||||
|
|
@ -29,11 +23,13 @@ mod mounter;
|
|||
mod mouse_area;
|
||||
pub mod operation;
|
||||
mod spawn_detached;
|
||||
use tab::Location;
|
||||
mod zoom;
|
||||
|
||||
use crate::config::State;
|
||||
pub mod tab;
|
||||
mod thumbnail_cacher;
|
||||
mod thumbnailer;
|
||||
pub(crate) mod trash;
|
||||
mod zoom;
|
||||
|
||||
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
|
||||
#[rustfmt::skip]
|
||||
pub fn desktop() -> Result<(), Box<dyn std::error::Error>> {
|
||||
let log_format = tracing_subscriber::fmt::format()
|
||||
.pretty()
|
||||
.without_time()
|
||||
.with_line_number(true)
|
||||
.with_file(true)
|
||||
.with_target(false)
|
||||
.with_thread_names(true);
|
||||
|
||||
let log_layer = tracing_subscriber::fmt::Layer::default()
|
||||
.with_writer(std::io::stderr)
|
||||
.event_format(log_format);
|
||||
|
||||
tracing_subscriber::registry()
|
||||
.with(tracing_subscriber::EnvFilter::from_env("RUST_LOG"))
|
||||
.with(log_layer)
|
||||
.init();
|
||||
env_logger::Builder::from_env(env_logger::Env::default().default_filter_or("warn")).init();
|
||||
|
||||
localize::localize();
|
||||
|
||||
|
|
@ -127,21 +108,7 @@ pub fn desktop() -> Result<(), Box<dyn std::error::Error>> {
|
|||
/// Runs application with these settings
|
||||
#[rustfmt::skip]
|
||||
pub fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
let log_format = tracing_subscriber::fmt::format()
|
||||
.pretty()
|
||||
.with_line_number(true)
|
||||
.with_file(true)
|
||||
.with_target(false)
|
||||
.with_thread_names(true);
|
||||
|
||||
let log_layer = tracing_subscriber::fmt::Layer::default()
|
||||
.with_writer(std::io::stderr)
|
||||
.event_format(log_format);
|
||||
|
||||
tracing_subscriber::registry()
|
||||
.with(tracing_subscriber::EnvFilter::from_default_env())
|
||||
.with(log_layer)
|
||||
.init();
|
||||
env_logger::Builder::from_env(env_logger::Env::default().default_filter_or("warn")).init();
|
||||
|
||||
localize::localize();
|
||||
|
||||
|
|
@ -191,7 +158,7 @@ pub fn main() -> Result<(), Box<dyn std::error::Error>> {
|
|||
}
|
||||
|
||||
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) {
|
||||
Ok(fork::Fork::Child) => (),
|
||||
Ok(fork::Fork::Parent(_child_pid)) => process::exit(0),
|
||||
|
|
|
|||
|
|
@ -23,7 +23,6 @@ use crate::{
|
|||
config::{Config, ContextActionPreset},
|
||||
fl,
|
||||
tab::{self, HeadingOptions, Location, LocationMenuAction, SearchLocation, Tab},
|
||||
trash::{Trash, TrashExt},
|
||||
};
|
||||
|
||||
static MENU_ID: LazyLock<cosmic::widget::Id> =
|
||||
|
|
@ -193,7 +192,7 @@ pub fn context_menu<'a>(
|
|||
) => {
|
||||
if selected_trash_only {
|
||||
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());
|
||||
}
|
||||
} else if let Some(entry) = selected_desktop_entry {
|
||||
|
|
|
|||
|
|
@ -19,7 +19,6 @@ struct MimeIconKey {
|
|||
|
||||
struct MimeIconCache {
|
||||
cache: FxHashMap<MimeIconKey, Option<icon::Handle>>,
|
||||
#[cfg(unix)]
|
||||
shared_mime_info: xdg_mime::SharedMimeInfo,
|
||||
}
|
||||
|
||||
|
|
@ -27,17 +26,10 @@ impl MimeIconCache {
|
|||
pub fn new() -> Self {
|
||||
Self {
|
||||
cache: FxHashMap::default(),
|
||||
#[cfg(unix)]
|
||||
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> {
|
||||
self.cache
|
||||
.entry(key)
|
||||
|
|
@ -61,16 +53,6 @@ impl MimeIconCache {
|
|||
static MIME_ICON_CACHE: LazyLock<Mutex<MimeIconCache>> =
|
||||
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(
|
||||
path: impl AsRef<Path>,
|
||||
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>> {
|
||||
let mime_icon_cache = MIME_ICON_CACHE.lock().unwrap();
|
||||
|
||||
mime_icon_cache.shared_mime_info.get_parents_aliased(mime)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ use cosmic::{
|
|||
};
|
||||
use gio::{glib, prelude::*};
|
||||
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 crate::{
|
||||
|
|
@ -199,7 +199,6 @@ fn network_scan(uri: &str, sizes: IconSizes) -> Result<Vec<tab::Item>, String> {
|
|||
metadata,
|
||||
hidden,
|
||||
location_opt: Some(location),
|
||||
image_dimensions: None,
|
||||
mime,
|
||||
icon_handle_grid,
|
||||
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()))
|
||||
}
|
||||
|
||||
fn mount_op(
|
||||
uri: String,
|
||||
event_tx: std::sync::Weak<crate::channel::Sender<Event>>,
|
||||
) -> gio::MountOperation {
|
||||
fn mount_op(uri: String, event_tx: mpsc::UnboundedSender<Event>) -> gio::MountOperation {
|
||||
let mount_op = gio::MountOperation::new();
|
||||
mount_op.connect_ask_password(
|
||||
move |mount_op, message, default_user, default_domain, flags| {
|
||||
|
|
@ -256,9 +252,9 @@ fn mount_op(
|
|||
.then_some(false),
|
||||
};
|
||||
let (auth_tx, mut auth_rx) = mpsc::channel(1);
|
||||
if let Some(event_tx) = event_tx.upgrade() {
|
||||
event_tx.send(Event::NetworkAuth(uri.clone(), auth, auth_tx));
|
||||
}
|
||||
event_tx
|
||||
.send(Event::NetworkAuth(uri.clone(), auth, auth_tx))
|
||||
.unwrap();
|
||||
//TODO: async recv?
|
||||
if let Some(auth) = auth_rx.blocking_recv() {
|
||||
if auth.anonymous_opt == Some(true) {
|
||||
|
|
@ -361,45 +357,37 @@ impl Item {
|
|||
|
||||
pub struct Gvfs {
|
||||
command_tx: mpsc::UnboundedSender<Cmd>,
|
||||
event_rx: Arc<crate::channel::Receiver<Event>>,
|
||||
event_rx: Arc<Mutex<mpsc::UnboundedReceiver<Event>>>,
|
||||
}
|
||||
|
||||
impl Gvfs {
|
||||
pub fn new() -> Self {
|
||||
//TODO: switch to using gvfs-zbus which will better integrate with async rust
|
||||
let (command_tx, mut command_rx) = mpsc::unbounded_channel();
|
||||
let (event_tx, event_rx) = crate::channel::channel();
|
||||
let event_tx = Arc::new(event_tx);
|
||||
let (event_tx, event_rx) = mpsc::unbounded_channel();
|
||||
std::thread::spawn(move || {
|
||||
let main_loop = glib::MainLoop::new(None, false);
|
||||
main_loop.context().spawn_local(async move {
|
||||
let event_tx = Arc::downgrade(&event_tx);
|
||||
let monitor = gio::VolumeMonitor::get();
|
||||
{
|
||||
let event_tx = event_tx.clone();
|
||||
monitor.connect_mount_changed(move |_monitor, mount| {
|
||||
log::info!("mount changed {}", MountExt::name(mount));
|
||||
if let Some(event_tx) = event_tx.upgrade() {
|
||||
event_tx.send(Event::Changed);
|
||||
}
|
||||
event_tx.send(Event::Changed).unwrap();
|
||||
});
|
||||
}
|
||||
{
|
||||
let event_tx = event_tx.clone();
|
||||
monitor.connect_mount_added(move |_monitor, mount| {
|
||||
log::info!("mount added {}", MountExt::name(mount));
|
||||
if let Some(event_tx) = event_tx.upgrade() {
|
||||
event_tx.send(Event::Changed);
|
||||
}
|
||||
event_tx.send(Event::Changed).unwrap();
|
||||
});
|
||||
}
|
||||
{
|
||||
let event_tx = event_tx.clone();
|
||||
monitor.connect_mount_removed(move |_monitor, mount| {
|
||||
log::info!("mount removed {}", MountExt::name(mount));
|
||||
if let Some(event_tx) = event_tx.upgrade() {
|
||||
event_tx.send(Event::Changed);
|
||||
}
|
||||
event_tx.send(Event::Changed).unwrap();
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -407,27 +395,21 @@ impl Gvfs {
|
|||
let event_tx = event_tx.clone();
|
||||
monitor.connect_volume_changed(move |_monitor, volume| {
|
||||
log::info!("volume changed {}", VolumeExt::name(volume));
|
||||
if let Some(event_tx) = event_tx.upgrade() {
|
||||
event_tx.send(Event::Changed);
|
||||
}
|
||||
event_tx.send(Event::Changed).unwrap();
|
||||
});
|
||||
}
|
||||
{
|
||||
let event_tx = event_tx.clone();
|
||||
monitor.connect_volume_added(move |_monitor, volume| {
|
||||
log::info!("volume added {}", VolumeExt::name(volume));
|
||||
if let Some(event_tx) = event_tx.upgrade() {
|
||||
event_tx.send(Event::Changed);
|
||||
}
|
||||
event_tx.send(Event::Changed).unwrap();
|
||||
});
|
||||
}
|
||||
{
|
||||
let event_tx = event_tx.clone();
|
||||
monitor.connect_volume_removed(move |_monitor, volume| {
|
||||
log::info!("volume removed {}", VolumeExt::name(volume));
|
||||
if let Some(event_tx) = event_tx.upgrade() {
|
||||
event_tx.send(Event::Changed);
|
||||
}
|
||||
event_tx.send(Event::Changed).unwrap();
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -437,11 +419,7 @@ impl Gvfs {
|
|||
items_tx.send(items(&monitor, sizes)).await.unwrap();
|
||||
}
|
||||
Cmd::Rescan => {
|
||||
let Some(event_tx) = event_tx.upgrade() else {
|
||||
return;
|
||||
};
|
||||
|
||||
event_tx.send(Event::Items(items(&monitor, IconSizes::default())));
|
||||
event_tx.send(Event::Items(items(&monitor, IconSizes::default()))).unwrap();
|
||||
}
|
||||
Cmd::Mount(mounter_item, complete_tx) => {
|
||||
let MounterItem::Gvfs(ref item) = mounter_item else {
|
||||
|
|
@ -493,9 +471,6 @@ impl Gvfs {
|
|||
.ok().map(|info| info.boolean(gio::FILE_ATTRIBUTE_FILESYSTEM_REMOTE))
|
||||
.unwrap_or(true);
|
||||
}
|
||||
let Some(event_tx) = event_tx.upgrade() else {
|
||||
return;
|
||||
};
|
||||
event_tx.send(Event::MountResult(updated_item, match res {
|
||||
Ok(()) => {
|
||||
_ = complete_tx.send(Ok(()));
|
||||
|
|
@ -507,7 +482,7 @@ impl Gvfs {
|
|||
Some(gio::IOErrorEnum::FailedHandled) => Ok(false),
|
||||
_ => Err(format!("{err}"))
|
||||
}}
|
||||
}));
|
||||
})).unwrap();
|
||||
},
|
||||
);
|
||||
break;
|
||||
|
|
@ -523,9 +498,6 @@ impl Gvfs {
|
|||
gio::Cancellable::NONE,
|
||||
move |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 {
|
||||
Ok(()) => {
|
||||
_ = result_tx.send(Ok(()));
|
||||
|
|
@ -536,7 +508,7 @@ impl Gvfs {
|
|||
Some(gio::IOErrorEnum::FailedHandled) => Ok(false),
|
||||
_ => Err(format!("{err}"))
|
||||
}}
|
||||
}));
|
||||
})).unwrap();
|
||||
}
|
||||
);
|
||||
}
|
||||
|
|
@ -560,9 +532,6 @@ impl Gvfs {
|
|||
// FIXME sometimes a uri can be mounted and then not recognized as mounted...
|
||||
// seems to be related to uri with a path
|
||||
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 {
|
||||
Ok(()) => {
|
||||
Ok(true)
|
||||
|
|
@ -571,7 +540,7 @@ impl Gvfs {
|
|||
Some(gio::IOErrorEnum::FailedHandled) => Ok(false),
|
||||
_ => Err(format!("{err}"))
|
||||
}
|
||||
}));
|
||||
})).unwrap();
|
||||
}
|
||||
);
|
||||
} else {
|
||||
|
|
@ -627,7 +596,7 @@ impl Gvfs {
|
|||
});
|
||||
Self {
|
||||
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();
|
||||
struct Wrapper {
|
||||
command_tx: mpsc::UnboundedSender<Cmd>,
|
||||
event_rx: Arc<crate::channel::Receiver<Event>>,
|
||||
event_rx: Arc<Mutex<mpsc::UnboundedReceiver<Event>>>,
|
||||
}
|
||||
impl Hash for Wrapper {
|
||||
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
|
||||
|
|
@ -725,7 +694,7 @@ impl Mounter for Gvfs {
|
|||
MounterMessage,
|
||||
>| async move {
|
||||
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 {
|
||||
Event::Changed => command_tx.send(Cmd::Rescan).unwrap(),
|
||||
Event::Items(items) => {
|
||||
|
|
|
|||
|
|
@ -1,11 +1,7 @@
|
|||
use atomic_float::AtomicF32;
|
||||
use num_enum::{IntoPrimitive, TryFromPrimitive};
|
||||
use std::sync::Arc;
|
||||
use std::sync::atomic::{self, AtomicU16};
|
||||
use std::sync::{Arc, Mutex};
|
||||
use tokio::sync::Notify;
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, IntoPrimitive, TryFromPrimitive)]
|
||||
#[repr(u16)]
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
pub enum ControllerState {
|
||||
Cancelled,
|
||||
Failed,
|
||||
|
|
@ -15,8 +11,8 @@ pub enum ControllerState {
|
|||
|
||||
#[derive(Debug)]
|
||||
struct ControllerInner {
|
||||
state: AtomicU16,
|
||||
progress: AtomicF32,
|
||||
state: Mutex<ControllerState>,
|
||||
progress: Mutex<f32>,
|
||||
notify: Notify,
|
||||
}
|
||||
|
||||
|
|
@ -31,8 +27,8 @@ impl Default for Controller {
|
|||
Self {
|
||||
primary: true,
|
||||
inner: Arc::new(ControllerInner {
|
||||
state: AtomicU16::new(ControllerState::Running.into()),
|
||||
progress: AtomicF32::new(0.0),
|
||||
state: Mutex::new(ControllerState::Running),
|
||||
progress: Mutex::new(0.0),
|
||||
notify: Notify::new(),
|
||||
}),
|
||||
}
|
||||
|
|
@ -54,24 +50,19 @@ impl Controller {
|
|||
}
|
||||
|
||||
pub fn progress(&self) -> f32 {
|
||||
self.inner.progress.load(atomic::Ordering::Relaxed)
|
||||
*self.inner.progress.lock().unwrap()
|
||||
}
|
||||
|
||||
pub fn set_progress(&self, progress: f32) {
|
||||
self.inner
|
||||
.progress
|
||||
.swap(progress, atomic::Ordering::Relaxed);
|
||||
*self.inner.progress.lock().unwrap() = progress;
|
||||
}
|
||||
|
||||
pub fn state(&self) -> ControllerState {
|
||||
ControllerState::try_from(self.inner.state.load(atomic::Ordering::Relaxed))
|
||||
.unwrap_or(ControllerState::Failed)
|
||||
*self.inner.state.lock().unwrap()
|
||||
}
|
||||
|
||||
pub fn set_state(&self, state: ControllerState) {
|
||||
self.inner
|
||||
.state
|
||||
.store(state.into(), atomic::Ordering::Relaxed);
|
||||
*self.inner.state.lock().unwrap() = state;
|
||||
self.inner.notify.notify_waiters();
|
||||
}
|
||||
|
||||
|
|
@ -95,35 +86,6 @@ impl Controller {
|
|||
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) {
|
||||
if !self.is_cancelled() | !self.is_failed() {
|
||||
self.set_state(ControllerState::Running);
|
||||
|
|
|
|||
|
|
@ -22,9 +22,6 @@ use zip::AesMode::Aes256;
|
|||
pub use self::controller::{Controller, ControllerState};
|
||||
pub mod controller;
|
||||
|
||||
pub use notifiers::*;
|
||||
mod notifiers;
|
||||
|
||||
pub use self::reader::OpReader;
|
||||
pub mod reader;
|
||||
|
||||
|
|
@ -114,7 +111,7 @@ async fn copy_or_move(
|
|||
);
|
||||
|
||||
// Handle duplicate file names by renaming paths
|
||||
let from_to_pairs_iter = paths
|
||||
let mut from_to_pairs: Vec<(PathBuf, PathBuf)> = paths
|
||||
.into_iter()
|
||||
.zip(std::iter::repeat(to.as_path()))
|
||||
.filter_map(|(from, to)| {
|
||||
|
|
@ -132,46 +129,36 @@ async fn copy_or_move(
|
|||
//TODO: how to handle from missing file name?
|
||||
None
|
||||
}
|
||||
});
|
||||
})
|
||||
.collect();
|
||||
|
||||
// Attempt quick and simple renames
|
||||
//TODO: allow rename to be used for directories in recursive context?
|
||||
if matches!(method, Method::Move { .. }) {
|
||||
from_to_pairs.retain(|(from, to)| {
|
||||
//TODO: show replace dialog here?
|
||||
if to.exists() {
|
||||
return true;
|
||||
}
|
||||
|
||||
let from_to_pairs: Vec<(PathBuf, PathBuf)> = if matches!(method, Method::Move { .. }) {
|
||||
from_to_pairs_iter
|
||||
.map(|(from, to)| async move {
|
||||
//TODO: show replace dialog here?
|
||||
if to.exists() {
|
||||
return Some((from, to));
|
||||
//TODO: use compio::fs::rename?
|
||||
match fs::rename(from, to) {
|
||||
Ok(()) => {
|
||||
log::info!("renamed {} to {}", from.display(), to.display());
|
||||
false
|
||||
}
|
||||
|
||||
match compio::fs::rename(&from, &to).await {
|
||||
Ok(()) => {
|
||||
log::info!("renamed {} to {}", from.display(), to.display());
|
||||
None
|
||||
}
|
||||
Err(err) => {
|
||||
log::info!(
|
||||
"failed to rename {} to {}, fallback to recursive move: {}",
|
||||
from.display(),
|
||||
to.display(),
|
||||
err
|
||||
);
|
||||
Some((from, to))
|
||||
}
|
||||
Err(err) => {
|
||||
log::info!(
|
||||
"failed to rename {} to {}, fallback to recursive move: {}",
|
||||
from.display(),
|
||||
to.display(),
|
||||
err
|
||||
);
|
||||
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());
|
||||
|
||||
|
|
@ -229,7 +216,7 @@ pub async fn sync_to_disk(
|
|||
}
|
||||
}))
|
||||
.buffer_unordered(32)
|
||||
.collect::<()>()
|
||||
.collect::<Vec<_>>()
|
||||
.await;
|
||||
|
||||
// Sync directories to disk
|
||||
|
|
@ -239,7 +226,7 @@ pub async fn sync_to_disk(
|
|||
}
|
||||
}))
|
||||
.buffer_unordered(16)
|
||||
.collect::<()>()
|
||||
.collect::<Vec<_>>()
|
||||
.await;
|
||||
}
|
||||
|
||||
|
|
@ -1125,9 +1112,7 @@ impl Operation {
|
|||
#[cfg(target_os = "macos")]
|
||||
Self::Restore { .. } => {
|
||||
// TODO: add support for macos
|
||||
return Err(OperationError::from_msg(
|
||||
"Restoring from trash is not supported on macos",
|
||||
));
|
||||
return OperationError::from_msg("Restoring from trash is not supported on macos");
|
||||
}
|
||||
#[cfg(not(target_os = "macos"))]
|
||||
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::buf::{IntoInner, IoBuf};
|
||||
use compio::driver::{ToSharedFd, op::AsyncifyFd};
|
||||
use compio::io::{AsyncReadAt, AsyncWriteAt};
|
||||
use cosmic::iced::futures;
|
||||
use futures::{FutureExt, StreamExt};
|
||||
use std::future::Future;
|
||||
use std::pin::Pin;
|
||||
use std::rc::Rc;
|
||||
use std::time::Instant;
|
||||
use std::{cell::Cell, error::Error, fs, ops::ControlFlow, path::PathBuf};
|
||||
use std::{cell::Cell, error::Error, fs, ops::ControlFlow, path::PathBuf, rc::Rc};
|
||||
use walkdir::WalkDir;
|
||||
|
||||
#[cfg(feature = "gvfs")]
|
||||
use gio::prelude::FileExtManual;
|
||||
use crate::operation::{OperationError, sync_to_disk};
|
||||
|
||||
#[derive(thiserror::Error, Debug)]
|
||||
pub enum GioCopyError {
|
||||
#[error("controller state")]
|
||||
Controller(OperationError),
|
||||
#[cfg(feature = "gvfs")]
|
||||
#[error("gio copy failed")]
|
||||
GLib(#[from] glib::Error),
|
||||
}
|
||||
use super::{Controller, OperationSelection, ReplaceResult, copy_unique_path};
|
||||
|
||||
pub enum Method {
|
||||
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()) {
|
||||
return Ok(true);
|
||||
}
|
||||
match self.kind {
|
||||
OpKind::Copy => {
|
||||
crate::operation::actively_writing_add(self.to.clone());
|
||||
let result = self.copy(ctx, progress).await;
|
||||
|
||||
if result.is_err() {
|
||||
_ = compio::fs::remove_file(&self.to).await;
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
crate::operation::actively_writing_remove(&self.to);
|
||||
return result;
|
||||
let (from_file, metadata, mut to_file) = cosmic::iced::futures::try_join!(
|
||||
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 } => {
|
||||
// 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
|
||||
if self.to.is_file() {
|
||||
match ctx.replace(self).await? {
|
||||
|
|
@ -430,268 +520,4 @@ impl Op {
|
|||
}
|
||||
Ok(true)
|
||||
}
|
||||
|
||||
async fn copy(
|
||||
&mut self,
|
||||
ctx: &mut Context,
|
||||
mut progress: Progress,
|
||||
) -> Result<bool, Box<dyn Error>> {
|
||||
// Remove `to` if overwriting and it is an existing file
|
||||
if self.to.is_file() {
|
||||
match ctx.replace(self).await? {
|
||||
ControlFlow::Continue(to) => {
|
||||
self.to = to;
|
||||
}
|
||||
ControlFlow::Break(ret) => {
|
||||
return Ok(ret);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let (from_file, metadata, to_file) = cosmic::iced::futures::join!(
|
||||
async {
|
||||
compio::fs::OpenOptions::new()
|
||||
.read(true)
|
||||
.open(&self.from)
|
||||
.await
|
||||
.with_context(|| format!("failed to open {} for reading", self.from.display(),))
|
||||
},
|
||||
async { compio::fs::metadata(&self.from).await.ok() },
|
||||
// This is atomic and ensures `to` is not created by any other process
|
||||
async {
|
||||
compio::fs::OpenOptions::new()
|
||||
.create_new(true)
|
||||
.write(true)
|
||||
.open(&self.to)
|
||||
.await
|
||||
.with_context(|| format!("failed to open {} for writing", self.to.display()))
|
||||
}
|
||||
);
|
||||
|
||||
let from_file = from_file?;
|
||||
let mut to_file = to_file?;
|
||||
progress.total_bytes = metadata.as_ref().map(|m| m.len());
|
||||
(ctx.on_progress)(self, &progress);
|
||||
|
||||
if let Some(metadata) = metadata.as_ref() {
|
||||
if let Err(why) = to_file.set_permissions(metadata.permissions()).await {
|
||||
// This error is not propagated upwards as some filesystems do not support setting permissions
|
||||
if !matches!(why.kind(), std::io::ErrorKind::Unsupported) {
|
||||
tracing::warn!(?why, "failed to set permissions for {}", self.to.display(),);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Prevent spamming the progress callbacks.
|
||||
let mut last_progress_update = Instant::now();
|
||||
// io_uring/IOCP requires transferring ownership of the buffer to the kernel.
|
||||
let mut buf_in = std::mem::take(&mut ctx.buf);
|
||||
// Track where the current read/write position is at.
|
||||
let mut pos = 0;
|
||||
|
||||
loop {
|
||||
let BufResult(result, buf_out) = from_file.read_at(buf_in, pos).await;
|
||||
|
||||
let count = match result {
|
||||
Ok(0) => {
|
||||
buf_in = buf_out;
|
||||
break;
|
||||
}
|
||||
Ok(count) => count,
|
||||
Err(why) => {
|
||||
ctx.buf = buf_out;
|
||||
tracing::error!("failed to read: {:?}", why);
|
||||
_ = futures::future::join(from_file.close(), to_file.close()).await;
|
||||
return Err(why).context("failed to read")?;
|
||||
}
|
||||
};
|
||||
|
||||
let BufResult(result, buf_out_slice) =
|
||||
to_file.write_at(buf_out.slice(..count), pos).await;
|
||||
let buf_out = buf_out_slice.into_inner();
|
||||
|
||||
if let Err(why) = result {
|
||||
#[cfg(feature = "gvfs")]
|
||||
if let std::io::ErrorKind::Unsupported = why.kind() {
|
||||
ctx.buf = buf_out;
|
||||
_ = futures::future::join(from_file.close(), to_file.close()).await;
|
||||
return self
|
||||
.gio_file_copy(ctx, progress)
|
||||
.await
|
||||
.map(|_| true)
|
||||
.map_err(Into::into);
|
||||
}
|
||||
|
||||
tracing::error!("failed to write: {:?}", why);
|
||||
ctx.buf = buf_out;
|
||||
_ = futures::future::join(from_file.close(), to_file.close()).await;
|
||||
return Err(why).context("failed to write")?;
|
||||
}
|
||||
|
||||
progress.current_bytes += count as u64;
|
||||
pos += count as u64;
|
||||
|
||||
// Avoid spamming progress messages too early.
|
||||
let current = Instant::now();
|
||||
if current.duration_since(last_progress_update).as_millis() > 49 {
|
||||
last_progress_update = current;
|
||||
(ctx.on_progress)(self, &progress);
|
||||
|
||||
// Also check if the progress was cancelled.
|
||||
if let Err(state) = ctx.controller.check().await {
|
||||
ctx.buf = buf_out;
|
||||
tracing::warn!(
|
||||
"operation to copy from {:?} to {:?} cancelled",
|
||||
self.from,
|
||||
self.to
|
||||
);
|
||||
_ = futures::future::join(from_file.close(), to_file.close()).await;
|
||||
return Err(OperationError::from_state(state, &ctx.controller).into());
|
||||
}
|
||||
}
|
||||
|
||||
buf_in = buf_out;
|
||||
}
|
||||
|
||||
ctx.buf = buf_in;
|
||||
|
||||
if let Some(metadata) = metadata.as_ref() {
|
||||
let mut times = fs::FileTimes::new();
|
||||
if let Ok(time) = metadata.modified() {
|
||||
times = times.set_modified(time);
|
||||
}
|
||||
if let Ok(time) = metadata.accessed() {
|
||||
times = times.set_accessed(time);
|
||||
}
|
||||
//TODO: upstream set_times implementation to compio?
|
||||
let op = AsyncifyFd::new(to_file.to_shared_fd(), move |file: &std::fs::File| {
|
||||
BufResult(file.set_times(times).map(|_| 0), ())
|
||||
});
|
||||
match compio::runtime::submit(op).await.0.map(|_| ()) {
|
||||
Ok(()) => {
|
||||
tracing::info!("set times for {} to {:?}", self.to.display(), times);
|
||||
}
|
||||
Err(why) => {
|
||||
if !matches!(why.kind(), std::io::ErrorKind::Unsupported) {
|
||||
tracing::error!(?why, "failed to set times for {}", self.to.display());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_ = to_file.close().await;
|
||||
|
||||
Ok(true)
|
||||
}
|
||||
|
||||
/// Fallback mechanism in the event that unsupported I/O error errors occur.
|
||||
/// Fixes unsupported errors when copying large files over MTP.
|
||||
/// TODO: Find what Gio.File does to work around this.
|
||||
#[cfg(feature = "gvfs")]
|
||||
async fn gio_file_copy(
|
||||
&self,
|
||||
ctx: &mut Context,
|
||||
mut progress: Progress,
|
||||
) -> Result<(), GioCopyError> {
|
||||
_ = compio::fs::remove_file(&self.to).await;
|
||||
|
||||
let from = gio::File::for_path(&self.from);
|
||||
let to = gio::File::for_path(&self.to);
|
||||
let (progress_tx, mut progress_rx) = tokio::sync::mpsc::unbounded_channel();
|
||||
let (tx, rx) = tokio::sync::oneshot::channel();
|
||||
let (pause_tx, mut pause_rx) = tokio::sync::watch::channel(false);
|
||||
|
||||
let task = compio::runtime::spawn_blocking(move || {
|
||||
let glib_context = glib::MainContext::new();
|
||||
let glib_loop = glib::MainLoop::new(Some(&glib_context), false);
|
||||
glib_context.with_thread_default(move || {
|
||||
let glib_loop2 = glib_loop.clone();
|
||||
glib::MainContext::ref_thread_default().spawn_local(async move {
|
||||
// Create a future for copying the file with `gio::File`. This also creates a progress stream.
|
||||
let (gio_copy_fut, mut progress_stream) = from.copy_future(
|
||||
&to,
|
||||
gio::FileCopyFlags::OVERWRITE | gio::FileCopyFlags::ALL_METADATA,
|
||||
glib::Priority::LOW,
|
||||
);
|
||||
|
||||
let mut copy_fut = gio_copy_fut
|
||||
.map(|result| result.map_err(GioCopyError::GLib))
|
||||
.fuse();
|
||||
|
||||
let progress_fut = std::pin::pin!(async {
|
||||
while let Some((current_bytes, _)) = progress_stream.next().await {
|
||||
_ = progress_tx.send(current_bytes);
|
||||
}
|
||||
|
||||
drop(progress_tx);
|
||||
futures::future::pending::<()>().await;
|
||||
});
|
||||
|
||||
let mut progress_fut = progress_fut.fuse();
|
||||
let mut pause_rx2 = pause_rx.clone();
|
||||
|
||||
loop {
|
||||
let until_paused = std::pin::pin!(pause_rx.wait_for(|paused| *paused));
|
||||
futures::select! {
|
||||
_ = &mut progress_fut => {},
|
||||
|
||||
result = &mut copy_fut => {
|
||||
_ = tx.send(result.map(|_| ()));
|
||||
glib_loop2.quit();
|
||||
return;
|
||||
}
|
||||
|
||||
_ = until_paused.fuse() => {
|
||||
_ = pause_rx2.wait_for(|paused| !*paused).await;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
glib_loop.run();
|
||||
})
|
||||
});
|
||||
|
||||
let mut last_progress_update = Instant::now();
|
||||
let mut task = task.fuse();
|
||||
let mut rx = rx.fuse();
|
||||
|
||||
loop {
|
||||
let until_paused = std::pin::pin!(ctx.controller.until_paused());
|
||||
futures::select! {
|
||||
value = progress_rx.recv().fuse() => {
|
||||
if let Some(current_bytes) = value {
|
||||
progress.current_bytes = current_bytes as u64;
|
||||
let current = Instant::now();
|
||||
if current.duration_since(last_progress_update).as_millis() > 49 {
|
||||
last_progress_update = current;
|
||||
(ctx.on_progress)(self, &progress);
|
||||
// Also check if the progress was cancelled.
|
||||
if let Err(state) = ctx.controller.check().await {
|
||||
tracing::warn!(
|
||||
"operation to copy from {:?} to {:?} cancelled",
|
||||
self.from,
|
||||
self.to
|
||||
);
|
||||
return Err::<(), GioCopyError>(GioCopyError::Controller(
|
||||
OperationError::from_state(state, &ctx.controller),
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
result = rx => return result.unwrap(),
|
||||
|
||||
_ = task => (),
|
||||
|
||||
_ = until_paused.fuse() => {
|
||||
// Pauses an active copy while the controller state is paused.
|
||||
_ = pause_tx.send(true);
|
||||
ctx.controller.until_unpaused().await;
|
||||
_ = pause_tx.send(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
241
src/tab.rs
241
src/tab.rs
|
|
@ -1,7 +1,7 @@
|
|||
#[cfg(feature = "desktop")]
|
||||
use cosmic::desktop::fde::{DesktopEntry, get_languages_from_env};
|
||||
use cosmic::{
|
||||
Apply, Element, cosmic_theme, font,
|
||||
Apply, Element, cosmic_theme,
|
||||
desktop::fde::{DesktopEntry, get_languages_from_env},
|
||||
font,
|
||||
iced::core::{mouse::ScrollDelta, widget::tree},
|
||||
iced::{
|
||||
Alignment, Border, Color, ContentFit, Length, Point, Rectangle, Size, Subscription, Vector,
|
||||
|
|
@ -39,10 +39,9 @@ use icu::{
|
|||
use image::{DynamicImage, ImageReader};
|
||||
use jiff_icu::ConvertFrom;
|
||||
use mime_guess::{Mime, mime};
|
||||
use regex::Regex;
|
||||
use rustc_hash::FxHashMap;
|
||||
use serde::{Deserialize, Serialize};
|
||||
#[cfg(unix)]
|
||||
use std::os::unix::fs::MetadataExt;
|
||||
use std::{
|
||||
borrow::Cow,
|
||||
cell::Cell,
|
||||
|
|
@ -53,6 +52,7 @@ use std::{
|
|||
fs::{self, File, Metadata},
|
||||
hash::Hash,
|
||||
io::{BufRead, BufReader},
|
||||
os::unix::fs::MetadataExt,
|
||||
path::{self, Path, PathBuf},
|
||||
sync::{Arc, LazyLock, RwLock, atomic},
|
||||
time::{Duration, Instant, SystemTime},
|
||||
|
|
@ -84,8 +84,8 @@ use crate::{
|
|||
operation::{Controller, OperationError},
|
||||
thumbnail_cacher::{CachedThumbnail, ThumbnailCacher, ThumbnailSize},
|
||||
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 HOVER_DURATION: Duration = Duration::from_millis(1600);
|
||||
|
|
@ -557,12 +557,6 @@ pub fn fs_kind(_metadata: &Metadata) -> FsKind {
|
|||
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> {
|
||||
let locales = get_languages_from_env();
|
||||
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())
|
||||
}
|
||||
|
||||
#[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> {
|
||||
let entry = match DesktopEntry::from_path::<&str>(path, None) {
|
||||
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>) {
|
||||
let locales = get_languages_from_env();
|
||||
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,
|
||||
},
|
||||
hidden,
|
||||
image_dimensions: (!remote && mime.type_() == mime::IMAGE)
|
||||
.then(|| image::image_dimensions(&path).ok())
|
||||
.flatten(),
|
||||
location_opt: Some(Location::Path(path)),
|
||||
mime,
|
||||
icon_handle_grid,
|
||||
|
|
@ -859,7 +843,6 @@ pub fn item_from_entry(
|
|||
},
|
||||
hidden,
|
||||
location_opt: Some(Location::Path(path)),
|
||||
image_dimensions: None,
|
||||
mime,
|
||||
icon_handle_grid,
|
||||
icon_handle_list,
|
||||
|
|
@ -913,9 +896,6 @@ pub fn item_from_trash_entry(
|
|||
metadata: ItemMetadata::Trash { metadata, entry },
|
||||
hidden: false,
|
||||
location_opt: None,
|
||||
image_dimensions: (mime.type_() == mime::IMAGE)
|
||||
.then(|| image::image_dimensions(&original_path).ok())
|
||||
.flatten(),
|
||||
mime,
|
||||
icon_handle_grid,
|
||||
icon_handle_list,
|
||||
|
|
@ -1181,7 +1161,133 @@ pub fn scan_search<F: Fn(SearchItem) -> bool + Sync>(
|
|||
}
|
||||
}
|
||||
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 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) = {
|
||||
(
|
||||
"inode/directory".parse().unwrap(),
|
||||
Trash::icon(sizes.grid()),
|
||||
Trash::icon(sizes.list()),
|
||||
Trash::icon(sizes.list_condensed()),
|
||||
trash_helpers::trash_icon(sizes.grid()),
|
||||
trash_helpers::trash_icon(sizes.list()),
|
||||
trash_helpers::trash_icon(sizes.list_condensed()),
|
||||
)
|
||||
};
|
||||
|
||||
|
|
@ -1338,7 +1444,6 @@ pub fn scan_desktop(
|
|||
metadata,
|
||||
hidden: false,
|
||||
location_opt: Some(Location::Trash),
|
||||
image_dimensions: None,
|
||||
mime,
|
||||
icon_handle_grid,
|
||||
icon_handle_list,
|
||||
|
|
@ -1568,7 +1673,7 @@ impl Location {
|
|||
// Search is done incrementally
|
||||
Vec::new()
|
||||
}
|
||||
Self::Trash => Trash::scan(sizes),
|
||||
Self::Trash => trash_helpers::scan_trash(sizes),
|
||||
Self::Recents => scan_recents(sizes),
|
||||
Self::Network(uri, _, _) => scan_network(uri, sizes),
|
||||
};
|
||||
|
|
@ -2214,7 +2319,6 @@ pub struct Item {
|
|||
pub hidden: bool,
|
||||
pub location_opt: Option<Location>,
|
||||
pub mime: Mime,
|
||||
pub image_dimensions: Option<(u32, u32)>,
|
||||
pub icon_handle_grid: widget::icon::Handle,
|
||||
pub icon_handle_list: widget::icon::Handle,
|
||||
pub icon_handle_list_condensed: widget::icon::Handle,
|
||||
|
|
@ -2429,7 +2533,7 @@ impl Item {
|
|||
|
||||
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))
|
||||
.unwrap_or_default();
|
||||
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))
|
||||
.unwrap_or_default();
|
||||
let group_path = path.clone();
|
||||
|
|
@ -4340,7 +4444,6 @@ impl Tab {
|
|||
for item in self.items_opt().map_or(Vec::new(), |items| {
|
||||
items.iter().filter(|item| item.selected).collect()
|
||||
}) {
|
||||
#[cfg(unix)]
|
||||
if let (Some(path), Some(mode)) = (
|
||||
item.path_opt(),
|
||||
item.file_metadata()
|
||||
|
|
@ -6358,23 +6461,20 @@ impl Tab {
|
|||
} else {
|
||||
total_size = total_size.saturating_add(metadata.len());
|
||||
}
|
||||
#[cfg(unix)]
|
||||
{
|
||||
let mode = metadata.mode();
|
||||
user_name.insert(
|
||||
uzers::get_user_by_uid(metadata.uid())
|
||||
.and_then(|user| user.name().to_str().map(ToOwned::to_owned))
|
||||
.unwrap_or_default(),
|
||||
);
|
||||
mode_user.insert(get_mode_part(mode, MODE_SHIFT_USER));
|
||||
group_name.insert(
|
||||
uzers::get_group_by_gid(metadata.gid())
|
||||
.and_then(|group| group.name().to_str().map(ToOwned::to_owned))
|
||||
.unwrap_or_default(),
|
||||
);
|
||||
mode_group.insert(get_mode_part(mode, MODE_SHIFT_GROUP));
|
||||
mode_other.insert(get_mode_part(mode, MODE_SHIFT_OTHER));
|
||||
}
|
||||
let mode = metadata.mode();
|
||||
user_name.insert(
|
||||
get_user_by_uid(metadata.uid())
|
||||
.and_then(|user| user.name().to_str().map(ToOwned::to_owned))
|
||||
.unwrap_or_default(),
|
||||
);
|
||||
mode_user.insert(get_mode_part(mode, MODE_SHIFT_USER));
|
||||
group_name.insert(
|
||||
get_group_by_gid(metadata.gid())
|
||||
.and_then(|group| group.name().to_str().map(ToOwned::to_owned))
|
||||
.unwrap_or_default(),
|
||||
);
|
||||
mode_group.insert(get_mode_part(mode, MODE_SHIFT_GROUP));
|
||||
mode_other.insert(get_mode_part(mode, MODE_SHIFT_OTHER));
|
||||
}
|
||||
}
|
||||
let mut mime_types: Vec<(String, u64)> = mime_type_counts.into_iter().collect();
|
||||
|
|
@ -6613,13 +6713,13 @@ impl Tab {
|
|||
|
||||
// Determine effective memory budget based on image size
|
||||
let (effective_max_mb, effective_jobs) = if mime.type_() == mime::IMAGE {
|
||||
match item.image_dimensions {
|
||||
Some((width, height)) => {
|
||||
match image::image_dimensions(&path) {
|
||||
Ok((width, height)) => {
|
||||
let (_use_dedicated, eff_mb, eff_jobs) =
|
||||
should_use_dedicated_worker(width, height, max_mb, max_jobs);
|
||||
(eff_mb, eff_jobs)
|
||||
}
|
||||
None => (max_mb, max_jobs),
|
||||
Err(_) => (max_mb, max_jobs),
|
||||
}
|
||||
} else {
|
||||
(max_mb, max_jobs)
|
||||
|
|
@ -6662,10 +6762,6 @@ impl Tab {
|
|||
stream::channel(
|
||||
1,
|
||||
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 path = path.clone();
|
||||
|
||||
|
|
@ -6850,8 +6946,9 @@ impl Tab {
|
|||
.await
|
||||
.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 || {
|
||||
scan_search(
|
||||
&search_location,
|
||||
|
|
@ -6879,7 +6976,14 @@ impl Tab {
|
|||
true
|
||||
} else {
|
||||
// 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,
|
||||
|
|
@ -6892,16 +6996,13 @@ impl Tab {
|
|||
search_location,
|
||||
start.elapsed(),
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
while watch_rx.changed().await.is_ok() {
|
||||
let is_ready = *watch_rx.borrow_and_update();
|
||||
let _ = output.send(Message::SearchReady(is_ready)).await;
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
// Send final ready
|
||||
let _ = output.send(Message::SearchReady(true)).await;
|
||||
let _ = output.lock().await.send(Message::SearchReady(true)).await;
|
||||
|
||||
std::future::pending().await
|
||||
},
|
||||
|
|
|
|||
|
|
@ -1,12 +1,11 @@
|
|||
use image::DynamicImage;
|
||||
use md5::{Digest, Md5};
|
||||
use rustc_hash::FxHashMap;
|
||||
#[cfg(unix)]
|
||||
use std::os::unix::fs::PermissionsExt;
|
||||
use std::{
|
||||
error::Error,
|
||||
fs::{self, File},
|
||||
io::{self, BufReader, BufWriter},
|
||||
os::unix::fs::PermissionsExt,
|
||||
path::{Path, PathBuf},
|
||||
sync::LazyLock,
|
||||
time::UNIX_EPOCH,
|
||||
|
|
@ -94,7 +93,6 @@ impl ThumbnailCacher {
|
|||
}
|
||||
|
||||
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))?;
|
||||
self.update_thumbnail_text_metadata(temp_file.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>> {
|
||||
if let Some(dir) = self.thumbnail_fail_marker_path.parent() {
|
||||
fs::create_dir_all(dir)?;
|
||||
#[cfg(unix)]
|
||||
fs::set_permissions(dir, fs::Permissions::from_mode(0o700))?;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
// Copyright 2023 System76 <info@system76.com>
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
#[cfg(feature = "desktop")]
|
||||
use cosmic::desktop::fde::GenericEntry;
|
||||
use mime_guess::Mime;
|
||||
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