Compare commits
1 commit
local/libc
...
fix/dialog
| Author | SHA1 | Date | |
|---|---|---|---|
| a71ab4ce11 |
68 changed files with 2612 additions and 5683 deletions
|
|
@ -1,15 +0,0 @@
|
|||
{
|
||||
"format_on_save": "on",
|
||||
"lsp": {
|
||||
"rust-analyzer": {
|
||||
"initialization_options": {
|
||||
"check": {
|
||||
"command": "clippy",
|
||||
},
|
||||
"rustfmt": {
|
||||
"extraArgs": ["+nightly"],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
1733
Cargo.lock
generated
1733
Cargo.lock
generated
File diff suppressed because it is too large
Load diff
69
Cargo.toml
69
Cargo.toml
|
|
@ -1,19 +1,19 @@
|
|||
[package]
|
||||
name = "cosmic-files"
|
||||
version = "1.0.13"
|
||||
version = "1.0.8"
|
||||
authors = ["Jeremy Soller <jeremy@system76.com>"]
|
||||
edition = "2024"
|
||||
license = "GPL-3.0-only"
|
||||
rust-version = "1.93"
|
||||
rust-version = "1.90"
|
||||
|
||||
[dependencies]
|
||||
anyhow = "1"
|
||||
jiff = "0.2"
|
||||
jiff-icu = "0.2"
|
||||
icu = { version = "2.2.0", features = ["compiled_data"] }
|
||||
cctk = { path = "../cosmic-protocols/client-toolkit", package = "cosmic-client-toolkit", optional = true }
|
||||
chrono = { version = "0.4", features = ["unstable-locales"] }
|
||||
icu = { version = "2.1.1", features = ["compiled_data"] }
|
||||
cctk = { git = "https://github.com/pop-os/cosmic-protocols", package = "cosmic-client-toolkit", rev = "d0e95be", 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"
|
||||
|
|
@ -24,7 +24,7 @@ log = "0.4"
|
|||
mime_guess = "2"
|
||||
notify-debouncer-full = "0.7"
|
||||
notify-rust = { version = "4", optional = true }
|
||||
open = "5.3.4"
|
||||
open = "5.3.3"
|
||||
paste = "1.0"
|
||||
regex = "1"
|
||||
rustc-hash = "2.1"
|
||||
|
|
@ -36,15 +36,15 @@ tokio = { version = "1", features = ["process", "sync"] }
|
|||
trash = { git = "https://github.com/jackpot51/trash-rs.git", branch = "cosmic" }
|
||||
url = "2.5"
|
||||
walkdir = "2.5.0"
|
||||
wayland-client = { version = "0.31.14", optional = true }
|
||||
wayland-client = { version = "0.31.12", optional = true }
|
||||
xdg = { version = "3.0", optional = true }
|
||||
xdg-mime = { git = "https://github.com/ebassi/xdg-mime-rs" }
|
||||
# Compression
|
||||
bzip2 = { version = "0.6", optional = true } #TODO: replace with pure Rust crate
|
||||
flate2 = "1.1"
|
||||
tar = "0.4.45"
|
||||
lzma-rust2 = { version = "0.16", optional = true }
|
||||
ordermap = { version = "1.2.0", features = ["serde"] }
|
||||
tar = "0.4.44"
|
||||
lzma-rust2 = { version = "0.15.7", optional = true }
|
||||
ordermap = { version = "1.1.0", features = ["serde"] }
|
||||
# Internationalization
|
||||
i18n-embed = { version = "0.16", features = [
|
||||
"fluent-system",
|
||||
|
|
@ -54,18 +54,13 @@ i18n-embed-fl = "0.10"
|
|||
rust-embed = "8"
|
||||
slotmap = "1.1.1"
|
||||
recently-used-xbel = "1.2.0"
|
||||
zip = "8"
|
||||
zip = "7"
|
||||
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.23", features = ["env-filter"] }
|
||||
thiserror = "2.0.18"
|
||||
atomic_float = "1.1.0"
|
||||
num_enum = "0.7.6"
|
||||
bstr = "1.12.1"
|
||||
|
||||
# Completion-based IO runtime to enable io_uring / IOCP file IO support.
|
||||
[dependencies.compio]
|
||||
|
|
@ -73,18 +68,17 @@ version = "0.18"
|
|||
default-features = false
|
||||
features = ["fs", "io", "macros", "polling", "runtime"]
|
||||
|
||||
# Yoda fork — depend on libcosmic-yoda directly by path (no git/no patch).
|
||||
[dependencies.libcosmic-yoda]
|
||||
path = "../libcosmic"
|
||||
[dependencies.libcosmic]
|
||||
git = "https://github.com/pop-os/libcosmic.git"
|
||||
default-features = false
|
||||
#TODO: a11y feature crashes
|
||||
features = [
|
||||
"about",
|
||||
"advanced-shaping",
|
||||
"autosize",
|
||||
"desktop",
|
||||
"multi-window",
|
||||
"tokio",
|
||||
"wayland",
|
||||
"winit",
|
||||
"surface-message",
|
||||
]
|
||||
|
||||
|
|
@ -112,15 +106,15 @@ default = [
|
|||
"wayland",
|
||||
"wgpu",
|
||||
]
|
||||
dbus-config = ["libcosmic-yoda/dbus-config"]
|
||||
desktop = ["libcosmic-yoda/desktop", "dep:cosmic-mime-apps", "dep:xdg"]
|
||||
dbus-config = ["libcosmic/dbus-config"]
|
||||
desktop = ["dep:cosmic-mime-apps", "dep:xdg"]
|
||||
desktop-applet = []
|
||||
gvfs = ["dep:gio", "dep:glib"]
|
||||
io-uring = ["compio/io-uring"]
|
||||
jemalloc = ["dep:tikv-jemallocator"]
|
||||
notify = ["dep:notify-rust"]
|
||||
wayland = ["libcosmic-yoda/wayland", "dep:cctk", "dep:wayland-client"]
|
||||
wgpu = ["libcosmic-yoda/wgpu"]
|
||||
wayland = ["libcosmic/wayland", "dep:cctk", "dep:wayland-client"]
|
||||
wgpu = ["libcosmic/wgpu"]
|
||||
|
||||
[profile.dev]
|
||||
opt-level = 1
|
||||
|
|
@ -130,8 +124,7 @@ inherits = "release"
|
|||
debug = true
|
||||
|
||||
[target.'cfg(unix)'.dependencies]
|
||||
fork = "0.7"
|
||||
uzers = "0.12.2"
|
||||
fork = "0.6"
|
||||
|
||||
[target.'cfg(target_os = "linux")'.dependencies]
|
||||
procfs = "0.18"
|
||||
|
|
@ -146,12 +139,20 @@ fastrand = "2"
|
|||
test-log = "0.2"
|
||||
tokio = { version = "1", features = ["rt", "macros"] }
|
||||
|
||||
# Yoda fork — libcosmic dep is now a direct path dep (libcosmic-yoda above),
|
||||
# no [patch] block needed anymore. Keeping the block below would be a no-op
|
||||
# since nothing in the dep graph still asks for pop-os/libcosmic.git.
|
||||
# [patch.'https://github.com/pop-os/cosmic-text']
|
||||
# cosmic-text = { path = "../cosmic-text" }
|
||||
|
||||
[patch.'https://github.com/pop-os/cosmic-text.git']
|
||||
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" }
|
||||
# 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']
|
||||
# smithay-clipboard = { path = "../smithay-clipboard" }
|
||||
|
||||
[workspace]
|
||||
members = ["cosmic-files-applet"]
|
||||
|
|
|
|||
3
build.rs
3
build.rs
|
|
@ -1,5 +1,4 @@
|
|||
use std::path::PathBuf;
|
||||
use std::{env, fs};
|
||||
use std::{env, fs, path::PathBuf};
|
||||
use xdgen::{App, Context, FluentString};
|
||||
|
||||
fn main() {
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "cosmic-files-applet"
|
||||
version = "1.0.13"
|
||||
version = "1.0.8"
|
||||
edition = "2024"
|
||||
|
||||
[dependencies]
|
||||
|
|
|
|||
24
debian/changelog
vendored
24
debian/changelog
vendored
|
|
@ -1,27 +1,3 @@
|
|||
cosmic-files (1.0.13) noble; urgency=medium
|
||||
|
||||
* Epoch 1.0.13 version update
|
||||
|
||||
-- Jeremy Soller <jeremy@system76.com> Tue, 12 May 2026 09:39:14 -0600
|
||||
|
||||
cosmic-files (1.0.12) noble; urgency=medium
|
||||
|
||||
* Epoch 1.0.12 version update
|
||||
|
||||
-- Jeremy Soller <jeremy@system76.com> Tue, 05 May 2026 10:23:57 -0600
|
||||
|
||||
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
|
||||
|
||||
-- Jeremy Soller <jeremy@system76.com> Mon, 06 Apr 2026 15:10:13 -0600
|
||||
|
||||
cosmic-files (1.0.8) noble; urgency=medium
|
||||
|
||||
* Epoch 1.0.8 version update
|
||||
|
|
|
|||
|
|
@ -1,78 +0,0 @@
|
|||
# Local performance and portal notes
|
||||
|
||||
Date: 2026-05-05
|
||||
|
||||
This repository carries a local patch aimed at improving initial directory
|
||||
display latency in large folders, plus a system-level workaround for a COSMIC
|
||||
portal FileChooser crash observed with Firefox and Chromium.
|
||||
|
||||
## Directory listing latency
|
||||
|
||||
The slow path was the initial construction of `Item` values in `src/tab.rs`.
|
||||
On a test folder with about 2000 entries, raw filesystem enumeration and stat
|
||||
calls completed in a few milliseconds, while COSMIC Files took multiple seconds
|
||||
before showing the directory.
|
||||
|
||||
The local patch keeps initial item construction cheap:
|
||||
|
||||
- directory child counts are no longer computed synchronously in
|
||||
`item_from_entry` and `item_from_gvfs_info`;
|
||||
- initial MIME detection uses extension-based `mime_guess`;
|
||||
- regular files use a generic file icon during the initial scan instead of
|
||||
resolving the full MIME icon immediately.
|
||||
|
||||
This keeps folders and `.desktop` files special-cased, while avoiding expensive
|
||||
per-file work for ordinary files during first paint.
|
||||
|
||||
Measured locally during investigation:
|
||||
|
||||
- before the MIME/icon changes: about 3.1 seconds for `~/Téléchargements`;
|
||||
- after avoiding full MIME icon resolution during scan: below the temporary
|
||||
100 ms perf-log threshold for the same folder.
|
||||
|
||||
## File chooser portal workaround
|
||||
|
||||
Firefox and Chromium were failing to open `Save As` on the first attempt because
|
||||
`xdg-desktop-portal-cosmic` crashed while handling `FileChooser`.
|
||||
|
||||
Logs showed:
|
||||
|
||||
```text
|
||||
Backend call failed: Remote peer disconnected
|
||||
xdg-desktop-portal-cosmic ... status=11/SEGV
|
||||
```
|
||||
|
||||
The working local system workaround is to remove
|
||||
`org.freedesktop.impl.portal.FileChooser` from:
|
||||
|
||||
```text
|
||||
/usr/share/xdg-desktop-portal/portals/cosmic.portal
|
||||
```
|
||||
|
||||
so the file chooser falls back to GTK. The resulting local `cosmic.portal`
|
||||
keeps COSMIC for the other interfaces:
|
||||
|
||||
```ini
|
||||
[portal]
|
||||
DBusName=org.freedesktop.impl.portal.desktop.cosmic
|
||||
Interfaces=org.freedesktop.impl.portal.Access;org.freedesktop.impl.portal.Screenshot;org.freedesktop.impl.portal.Settings;org.freedesktop.impl.portal.ScreenCast
|
||||
UseIn=COSMIC
|
||||
```
|
||||
|
||||
Backups created locally:
|
||||
|
||||
```text
|
||||
/usr/share/xdg-desktop-portal/portals/cosmic.portal.bak-codex-20260505-filechooser
|
||||
/usr/share/xdg-desktop-portal/cosmic-portals.conf.bak-codex-20260505
|
||||
/usr/share/xdg-desktop-portal/cosmic-portals.conf.bak-codex-20260505-2
|
||||
```
|
||||
|
||||
After editing portal files, restart:
|
||||
|
||||
```sh
|
||||
systemctl --user restart xdg-desktop-portal.service xdg-desktop-portal-gtk.service
|
||||
```
|
||||
|
||||
Package updates may overwrite `/usr/share/xdg-desktop-portal/portals/cosmic.portal`.
|
||||
If `Save As` starts needing two attempts again, re-check that `FileChooser` has
|
||||
not been reintroduced in `cosmic.portal`.
|
||||
|
|
@ -1,8 +1,6 @@
|
|||
use cosmic_files::operation::recursive::{Context, Method};
|
||||
use cosmic_files::operation::{Controller, ReplaceResult};
|
||||
use std::error::Error;
|
||||
use std::io;
|
||||
use std::path::PathBuf;
|
||||
use cosmic_files::operation::recursive::Method;
|
||||
use cosmic_files::operation::{Controller, ReplaceResult, recursive::Context};
|
||||
use std::{error::Error, io, path::PathBuf};
|
||||
|
||||
#[compio::main]
|
||||
async fn main() -> Result<(), Box<dyn Error>> {
|
||||
|
|
|
|||
|
|
@ -1,30 +1,17 @@
|
|||
use cosmic::app::{self, Core, Settings, Task};
|
||||
use cosmic::iced::{Subscription, window};
|
||||
use cosmic::{Application, Element, executor, widget};
|
||||
use cosmic::{
|
||||
Application, Element,
|
||||
app::{self, Core, Settings, Task},
|
||||
executor,
|
||||
iced::{Subscription, window},
|
||||
widget,
|
||||
};
|
||||
use cosmic_files::dialog::{
|
||||
Dialog, DialogChoice, DialogChoiceOption, DialogFilter, DialogFilterPattern, DialogKind,
|
||||
DialogMessage, DialogResult, DialogSettings,
|
||||
};
|
||||
use tracing_subscriber::layer::SubscriberExt;
|
||||
use tracing_subscriber::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, ())?;
|
||||
|
|
@ -161,7 +148,7 @@ impl Application for App {
|
|||
}
|
||||
|
||||
fn view(&self) -> Element<'_, Message> {
|
||||
let mut column = widget::column::with_capacity(8).spacing(8).padding(8);
|
||||
let mut column = widget::column().spacing(8).padding(8);
|
||||
{
|
||||
let mut button = widget::button::standard("Open File");
|
||||
if self.dialog_opt.is_none() {
|
||||
|
|
|
|||
|
|
@ -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,15 +371,3 @@ move-to = انقل إلى...
|
|||
show-recents = مجلد الحديثة في الشريط الجانبي
|
||||
clear-recents-history = امحُ التأريخ الحديث
|
||||
copy-path = انسخ المسار
|
||||
mixed = مختلط
|
||||
context-action = إجراء السياق
|
||||
context-action-confirm-title = شغِّل "{ $name }"؟
|
||||
context-action-confirm-warning =
|
||||
سيُشغِّل هذا على { $items } { $items ->
|
||||
[one] عنصر
|
||||
[two] عنصرين
|
||||
[few] عناصر
|
||||
[many] عنصرًا
|
||||
*[other] عنصر
|
||||
}.
|
||||
run = شغِّل
|
||||
|
|
|
|||
|
|
@ -74,7 +74,7 @@ name-no-slashes = El nom no pot contenir barres.
|
|||
|
||||
## Open/Save Dialog
|
||||
|
||||
cancel = Cancel·lar
|
||||
cancel = Cancel·la
|
||||
create = Crea
|
||||
open = Obre
|
||||
open-file = Obre el fixer
|
||||
|
|
@ -352,4 +352,3 @@ sort-newest-first = Primer més recents
|
|||
sort-oldest-first = Primer més antics
|
||||
sort-smallest-to-largest = De petit a gran
|
||||
sort-largest-to-smallest = De gran a petit
|
||||
run = Executa
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ size = Velikost
|
|||
|
||||
## Empty Trash Dialog
|
||||
|
||||
empty-trash = Vyprázdnit koš
|
||||
empty-trash = Vysypat koš
|
||||
empty-trash-warning = Položky v koši budou trvale smazány
|
||||
|
||||
## New File/Folder Dialog
|
||||
|
|
@ -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 =
|
||||
|
|
@ -195,8 +195,8 @@ deleted =
|
|||
[few] položky
|
||||
*[other] položek
|
||||
} z koše
|
||||
emptying-trash = Vyprazdňování koše ({ $progress })...
|
||||
emptied-trash = Koš vyprázdněn
|
||||
emptying-trash = Vysypávání koše ({ $progress })...
|
||||
emptied-trash = Koš vysypán
|
||||
restoring =
|
||||
Obnovování { $items } { $items ->
|
||||
[one] položky
|
||||
|
|
@ -403,7 +403,7 @@ sort-largest-to-smallest = Od největšího po nejmenší
|
|||
gallery-preview = Náhled galerie
|
||||
sort = Řazení
|
||||
sort-a-z = A-Z
|
||||
empty-trash-title = Vyprázdnit koš?
|
||||
empty-trash-title = Vysypat koš?
|
||||
type-to-search-select = Vybere první shodující se soubor nebo složku
|
||||
pasted-image = Vložený obrázek
|
||||
pasted-text = Vložený text
|
||||
|
|
@ -417,13 +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é
|
||||
context-action = Kontextová akce
|
||||
context-action-confirm-title = Spustit „{ $name }“?
|
||||
context-action-confirm-warning =
|
||||
Tato akce se spustí pro { $items } { $items ->
|
||||
[one] položku
|
||||
[few] položky
|
||||
*[other] položek
|
||||
}.
|
||||
run = Spustit
|
||||
|
|
|
|||
|
|
@ -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,16 +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
|
||||
context-action = Kontextaktion
|
||||
context-action-confirm-title = „{ $name }“ ausführen?
|
||||
context-action-confirm-warning =
|
||||
Dies wird ausgeführt auf { $items } { $items ->
|
||||
[one] Element
|
||||
*[other] Elementen
|
||||
}.
|
||||
run = Ausführen
|
||||
|
|
|
|||
|
|
@ -1,329 +1,15 @@
|
|||
empty-folder = Άδειος φάκελος
|
||||
no-results = Δεν βρέθηκαν αποτελέσματα
|
||||
trash = Απορρίμματα
|
||||
trash = Κάδος Ανακύκλωσης
|
||||
recents = Πρόσφατα
|
||||
cosmic-files = Αρχεία COSMIC
|
||||
empty-folder-hidden = Άδειος φάκελος (περιέχει κρυφά στοιχεία)
|
||||
cosmic-files = COSMIC Αρχεία
|
||||
empty-folder-hidden = Άδειος φάκελος (περιέχει κρυφά αντικείμενα)
|
||||
filesystem = Σύστημα αρχείων
|
||||
home = Προσωπικός φάκελος
|
||||
networks = Δίκτυα
|
||||
comment = Διαχείριση αρχείων για το περιβάλλον επιφάνειας εργασίας COSMIC
|
||||
keywords = Αρχείο;Φάκελος;Διαχείριση;Folder;Manager;
|
||||
networks = Δίκτυο
|
||||
comment = Αρχεία για το COSMIC περιβάλλον
|
||||
keywords = Φάκελος;Διαχειριστής;
|
||||
rename = Μετονομασία...
|
||||
close-tab = Κλείσιμο καρτέλας
|
||||
light = Ανοιχτόχρωμο
|
||||
dark = Σκουρόχρωμο
|
||||
connect = Σύνδεση
|
||||
dismiss = Απόρριψη μηνύματος
|
||||
copy_noun = Αντιγραφή
|
||||
open-file = Άνοιγμα αρχείου
|
||||
save = Αποθήκευση
|
||||
password = Κωδικός πρόσβασης
|
||||
remove = Αφαίρεση
|
||||
create = Δημιουργία
|
||||
pause = Παύση
|
||||
quit = Έξοδος
|
||||
calculating = Υπολογισμός...
|
||||
keep = Διατήρηση
|
||||
edit = Επεξεργασία
|
||||
connecting = Σύνδεση...
|
||||
copy = Αντιγραφή
|
||||
theme = Θέμα
|
||||
appearance = Εμφάνιση
|
||||
name = Όνομα
|
||||
resume = Συνέχιση
|
||||
username = Όνομα χρήστη
|
||||
delete = Διαγραφή
|
||||
repository = Αποθετήριο
|
||||
support = Υποστήριξη
|
||||
eject = Εξαγωγή
|
||||
group = Ομάδα
|
||||
skip = Παράλειψη
|
||||
paste = Επικόλληση
|
||||
menu-settings = Ρυθμίσεις...
|
||||
view = Προβολή
|
||||
undo = Αναίρεση
|
||||
details = Λεπτομέρειες
|
||||
sort-a-z = Α-Ω
|
||||
extract-here = Αποσυμπίεση
|
||||
cancel = Ακύρωση
|
||||
sort-z-a = Ω-Α
|
||||
open = Άνοιγμα
|
||||
history = Ιστορικό
|
||||
domain = Τομέας
|
||||
sort = Ταξινόμηση
|
||||
settings = Ρυθμίσεις
|
||||
pending = Σε εκκρεμότητα
|
||||
open-folder = Άνοιγμα φακέλου
|
||||
replace = Αντικατάσταση
|
||||
cut = Αποκοπή
|
||||
file = Αρχείο
|
||||
today = Σήμερα
|
||||
compress = Συμπίεση...
|
||||
size = Μέγεθος
|
||||
related-apps = Σχετικές εφαρμογές
|
||||
zoom-in = Μεγέθυνση
|
||||
select-all = Επιλογή όλων
|
||||
icon-size-and-spacing = Μέγεθος και απόσταση εικονιδίων
|
||||
new-window = Νέο παράθυρο
|
||||
zoom-out = Σμίκρυνση
|
||||
default-size = Προεπιλεγμένο μέγεθος
|
||||
create-archive = Δημιουργία συμπιεσμένου αρχείου
|
||||
other-apps = Άλλες εφαρμογές
|
||||
rename-folder = Μετονομασία φακέλου
|
||||
folder-name = Όνομα φακέλου
|
||||
connect-anonymously = Ανώνυμη σύνδεση
|
||||
replace-with = Αντικατάσταση με
|
||||
mounted-drives = Προσαρτημένες μονάδες
|
||||
desktop-view-options = Επιλογές προβολής επιφάνειας εργασίας...
|
||||
show-on-desktop = Εμφάνιση στην επιφάνεια εργασίας
|
||||
trash-folder-icon = Εικονίδιο φακέλου απορριμμάτων
|
||||
open-with = Άνοιγμα με
|
||||
keep-both = Διατήρηση αμφότερων
|
||||
icon-size = Μέγεθος εικονιδίων
|
||||
open-with-title = Πώς θέλετε να ανοίξετε το «{ $name }»;
|
||||
extract-password-required = Απαιτείται κωδικός πρόσβασης
|
||||
rename-file = Μετονομασία αρχείου
|
||||
file-name = Όνομα αρχείου
|
||||
save-file = Αποθήκευση αρχείου
|
||||
name-hidden = Τα ονόματα που ξεκινούν με «.» θα αποκρύπτονται
|
||||
folder-already-exists = Υπάρχει ήδη ένας φάκελος με αυτό το όνομα
|
||||
empty-trash = Άδειασμα απορριμμάτων
|
||||
permanently-delete-question = Οριστική διαγραφή;
|
||||
copy-to-button-label = Αντιγραφή
|
||||
move-to-button-label = Μετακίνηση
|
||||
run = Εκτέλεση
|
||||
copy-to-title = Επιλογή προορισμού αντιγραφής
|
||||
sort-newest-first = Πρώτα τα νεότερα
|
||||
default-app = { $name } (προεπιλογή)
|
||||
renamed = Έγινε μετονομασία από «{ $from }» σε «{ $to }»
|
||||
read-execute = Ανάγνωση και εκτέλεση
|
||||
deleted =
|
||||
Έγινε διαγραφή { $items } { $items ->
|
||||
[one] στοιχείου
|
||||
*[other] στοιχείων
|
||||
} από τα { trash }
|
||||
item-modified = Ημερομηνία τροποποίησης: { $modified }
|
||||
list-view = Προβολή λίστας
|
||||
reload-folder = Επαναφόρτωση φακέλου
|
||||
favorite-path-error = Σφάλμα ανοίγματος καταλόγου
|
||||
progress = { $percent }%
|
||||
remove-from-sidebar = Αφαίρεση από την πλαϊνή στήλη
|
||||
restoring =
|
||||
Ανάκτηση { $items } { $items ->
|
||||
[one] στοιχείου
|
||||
*[other] στοιχείων
|
||||
} από τα { trash } ({ $progress })...
|
||||
network-drive-error = Αδυναμία πρόσβασης σε μονάδα δικτύου
|
||||
gallery-preview = Προεπισκόπηση συλλογής
|
||||
sort-smallest-to-largest = Από τα μικρότερα στα μεγαλύτερα
|
||||
removing-from-recents =
|
||||
Αφαίρεση { $items } { $items ->
|
||||
[one] στοιχείου
|
||||
*[other] στοιχείων
|
||||
} από τα { recents }
|
||||
type-to-search-enter-path = Εισέρχεται στη διαδρομή προς τον κατάλογο ή το αρχείο
|
||||
emptying-trash = Άδειασμα φακέλου «{ trash }» ({ $progress })...
|
||||
trashed-on = Ημερομηνία διαγραφής
|
||||
compressing =
|
||||
Συμπίεση { $items } { $items ->
|
||||
[one] στοιχείου
|
||||
*[other] στοιχείων
|
||||
} από τον φάκελο «{ $from }» στο αρχείο «{ $to }» ({ $progress })...
|
||||
move-to-trash = Μετακίνηση στα απορρίμματα
|
||||
menu-about = Σχετικά με τα Αρχεία COSMIC...
|
||||
setting-executable-and-launching = Ορισμός του «{ $name }» ως εκτελέσιμου και εκκίνηση
|
||||
open-multiple-files = Άνοιγμα πολλαπλών αρχείων
|
||||
menu-open-with = Άνοιγμα με...
|
||||
extracted =
|
||||
Έγινε αποσυμπίεση { $items } { $items ->
|
||||
[one] στοιχείου
|
||||
*[other] στοιχείων
|
||||
} από το αρχείο «{ $from }» στον φάκελο «{ $to }»
|
||||
create-new-folder = Δημιουργία νέου φακέλου
|
||||
original-file = Πρωτότυπο αρχείο
|
||||
read-write-execute = Ανάγνωση, εγγραφή και εκτέλεση
|
||||
set-permissions = Έγινε ορισμός των δικαιωμάτων για το «{ $name }» σε: { $mode }
|
||||
sort-by-size = Ταξινόμηση κατά μέγεθος
|
||||
item-size = Μέγεθος: { $size }
|
||||
permanently-deleting =
|
||||
Οριστική διαγραφή { $items } { $items ->
|
||||
[one] στοιχείου
|
||||
*[other] στοιχείων
|
||||
}
|
||||
read-write = Ανάγνωση και εγγραφή
|
||||
none = Κανένα
|
||||
items = Στοιχεία: { $items }
|
||||
type = Τύπος: { $mime }
|
||||
compressed =
|
||||
Έγινε συμπίεση { $items } { $items ->
|
||||
[one] στοιχείου
|
||||
*[other] στοιχείων
|
||||
} από τον φάκελο «{ $from }» στο αρχείο «{ $to }»
|
||||
replace-warning = Θέλετε να το αντικαταστήσετε με αυτό που αποθηκεύετε; Αυτό θα οδηγήσει στην αντικατάσταση του περιεχομένου του.
|
||||
new-file = Νέο αρχείο...
|
||||
open-in-terminal = Άνοιγμα σε τερματικό
|
||||
open-multiple-folders = Άνοιγμα πολλαπλών φακέλων
|
||||
remember-password = Απομνημόνευση κωδικού πρόσβασης
|
||||
show-details = Εμφάνιση λεπτομερειών
|
||||
grid-spacing = Απόσταση πλέγματος
|
||||
extract-to = Αποσυμπίεση σε...
|
||||
add-network-drive = Προσθήκη μονάδας δικτύου
|
||||
copying =
|
||||
Αντιγραφή { $items } { $items ->
|
||||
[one] στοιχείου
|
||||
*[other] στοιχείων
|
||||
} από τον φάκελο «{ $from }» στον φάκελο «{ $to }» ({ $progress })...
|
||||
sort-oldest-first = Πρώτα τα παλαιότερα
|
||||
create-new-file = Δημιουργία νέου αρχείου
|
||||
sort-by-trashed = Ταξινόμηση κατά ημερομηνία διαγραφής
|
||||
replace-warning-operation = Θέλετε να το αντικαταστήσετε; Αυτό θα οδηγήσει στην αντικατάσταση του περιεχομένου του.
|
||||
try-again = Δοκιμή ξανά
|
||||
copied =
|
||||
Έγινε αντιγραφή { $items } { $items ->
|
||||
[one] στοιχείου
|
||||
*[other] στοιχείων
|
||||
} από τον φάκελο «{ $from }» στον φάκελο «{ $to }»
|
||||
other = Άλλο
|
||||
open-in-new-window = Άνοιγμα σε νέο παράθυρο
|
||||
sort-by-modified = Ταξινόμηση κατά ημερομηνία τροποποίησης
|
||||
list-directories-first = Παράθεση των καταλόγων πρώτα
|
||||
read-only = Μόνο ανάγνωση
|
||||
browse-store = Περιήγηση στο { $store }
|
||||
enter-server-address = Εισαγάγετε τη διεύθυνση διακομιστή
|
||||
remove-from-recents = Αφαίρεση από τα πρόσφατα
|
||||
apply-to-all = Εφαρμογή σε όλα
|
||||
moving =
|
||||
Μετακίνηση { $items } { $items ->
|
||||
[one] στοιχείου
|
||||
*[other] στοιχείων
|
||||
} από τον φάκελο «{ $from }» στον φάκελο «{ $to }» ({ $progress })...
|
||||
change-wallpaper = Αλλαγή ταπετσαρίας...
|
||||
network-drive-description =
|
||||
Οι διευθύνσεις διακομιστών αποτελούνται από ένα πρόθεμα πρωτοκόλλου και μια διεύθυνση.
|
||||
Παραδείγματα: ssh://192.168.0.1, ftp://[2001:db8::1]
|
||||
deleting =
|
||||
Διαγραφή { $items } { $items ->
|
||||
[one] στοιχείου
|
||||
*[other] στοιχείων
|
||||
} από τα { trash } ({ $progress })...
|
||||
single-click = Μονό κλικ για άνοιγμα
|
||||
setting-permissions = Ορισμός των δικαιωμάτων για το «{ $name }» σε: { $mode }
|
||||
owner = Κάτοχος
|
||||
creating = Δημιουργία του «{ $name }» στον φάκελο «{ $parent }»
|
||||
execute-only = Μόνο εκτέλεση
|
||||
open-item-location = Άνοιγμα τοποθεσίας στοιχείου
|
||||
set-executable-and-launched = Έγινε ορισμός του «{ $name }» ως εκτελέσιμου και εκκινήθηκε
|
||||
mount-error = Αδυναμία πρόσβασης στη μονάδα
|
||||
grid-view = Προβολή πλέγματος
|
||||
set-and-launch = Ορισμός και εκκίνηση
|
||||
removed-from-recents =
|
||||
Έγινε αφαίρεση { $items } { $items ->
|
||||
[one] στοιχείου
|
||||
*[other] στοιχείων
|
||||
} από τα { recents }
|
||||
add-to-sidebar = Προσθήκη στην πλαϊνή στήλη
|
||||
item-created = Ημερομηνία δημιουργίας: { $created }
|
||||
network-drive-schemes =
|
||||
Διαθέσιμα πρωτόκολλα,Πρόθεμα
|
||||
AppleTalk,afp://
|
||||
File Transfer Protocol,ftp:// ή ftps://
|
||||
Network File System,nfs://
|
||||
Server Message Block,smb://
|
||||
SSH File Transfer Protocol,sftp:// ή ssh://
|
||||
WebDAV,dav:// ή davs://
|
||||
set-executable-and-launch = Ορισμός ως εκτελέσιμο και εκκίνηση
|
||||
restored =
|
||||
Έγινε ανάκτηση { $items } { $items ->
|
||||
[one] στοιχείου
|
||||
*[other] στοιχείων
|
||||
} από τα { trash }
|
||||
type-to-search-recursive = Κάνει αναζήτηση στον τρέχοντα φάκελο και όλους τους υποφακέλους
|
||||
progress-paused = { $percent }%, σε παύση
|
||||
cancelled = Ακυρωμένες
|
||||
new-folder = Νέος φάκελος...
|
||||
match-desktop = Συμφωνία με την επιφάνεια εργασίας
|
||||
operations-running-finished =
|
||||
Εκτέλεση { $running } { $running ->
|
||||
[one] διεργασίας
|
||||
*[other] διεργασιών
|
||||
} ({ $percent }%), { $finished } ολοκληρωμένες...
|
||||
sort-by-name = Ταξινόμηση κατά όνομα
|
||||
edit-history = Ιστορικό επεξεργασιών
|
||||
show-hidden-files = Εμφάνιση κρυφών αρχείων
|
||||
progress-failed = { $percent }%, απέτυχε
|
||||
item-accessed = Ημερομηνία πρόσβασης: { $accessed }
|
||||
extract-to-title = Αποσυμπίεση σε φάκελο
|
||||
extracting =
|
||||
Αποσυμπίεση { $items } { $items ->
|
||||
[one] στοιχείου
|
||||
*[other] στοιχείων
|
||||
} από το αρχείο «{ $from }» στον φάκελο «{ $to }» ({ $progress })...
|
||||
permanently-deleted =
|
||||
Έγινε οριστική διαγραφή { $items } { $items ->
|
||||
[one] στοιχείου
|
||||
*[other] στοιχείων
|
||||
}
|
||||
complete = Ολοκληρωμένες
|
||||
write-execute = Εγγραφή και εκτέλεση
|
||||
desktop-folder-content = Περιεχόμενο φακέλου επιφάνειας εργασίας
|
||||
renaming = Μετονομασία από «{ $from }» σε «{ $to }»
|
||||
set-executable-and-launch-description = Θέλετε να ορίσετε το «{ $name }» ως εκτελέσιμο και να το εκκινήσετε;
|
||||
no-history = Δεν υπάρχουν στοιχεία στο ιστορικό.
|
||||
emptied-trash = Έγινε άδειασμα του φακέλου «{ trash }»
|
||||
sort-largest-to-smallest = Από τα μεγαλύτερα στα μικρότερα
|
||||
restore-from-trash = Ανάκτηση από τα απορρίμματα
|
||||
moved =
|
||||
Έγινε μετακίνηση { $items } { $items ->
|
||||
[one] στοιχείου
|
||||
*[other] στοιχείων
|
||||
} από τον φάκελο «{ $from }» στον φάκελο «{ $to }»
|
||||
progress-cancelled = { $percent }%, ακυρώθηκε
|
||||
open-in-new-tab = Άνοιγμα σε νέα καρτέλα
|
||||
unknown-folder = άγνωστος φάκελος
|
||||
created = Έγινε δημιουργία του «{ $name }» στον φάκελο «{ $parent }»
|
||||
delete-permanently = Οριστική διαγραφή
|
||||
write-only = Μόνο εγγραφή
|
||||
display-settings = Ρυθμίσεις οθόνης...
|
||||
new-tab = Νέα καρτέλα
|
||||
failed = Αποτυχημένες
|
||||
modified = Ημερομηνία τροποποίησης
|
||||
desktop-appearance = Εμφάνιση επιφάνειας εργασίας...
|
||||
file-already-exists = Υπάρχει ήδη ένα αρχείο με αυτό το όνομα
|
||||
permanently-delete-warning = Θα διαγραφούν οριστικά τα εξής: { $target }. Δεν είναι δυνατή η αναίρεση αυτής της ενέργειας.
|
||||
favorite-path-error-description =
|
||||
Αδυναμία ανοίγματος του «{ $path }»
|
||||
Το «{ $path }» ενδέχεται να μην υπάρχει ή να μην έχετε το δικαίωμα να το ανοίξετε
|
||||
|
||||
Θέλετε να το αφαιρέσετε από την πλαϊνή στήλη;
|
||||
empty-trash-warning = Τα στοιχεία του φακέλου «Απορρίμματα» θα διαγραφούν οριστικά
|
||||
empty-trash-title = Άδειασμα απορριμμάτων;
|
||||
type-to-search = Πληκτρολόγηση για αναζήτηση
|
||||
notification-in-progress = Βρίσκονται σε εξέλιξη διεργασίες αρχείων
|
||||
name-no-slashes = Το όνομα δεν μπορεί να περιέχει καθέτους
|
||||
replace-title = Το «{ $filename }» υπάρχει ήδη σε αυτήν την τοποθεσία
|
||||
name-invalid = Το όνομα δεν μπορεί να είναι «{ $filename }»
|
||||
operations-running =
|
||||
Εκτέλεση { $running } { $running ->
|
||||
[one] διεργασίας
|
||||
*[other] διεργασιών
|
||||
} ({ $percent }%)...
|
||||
context-action-confirm-title = Εκτέλεση του «{ $name }»;
|
||||
pasted-image = Επικολλημένη εικόνα
|
||||
pasted-text = Επικολλημένο κείμενο
|
||||
pasted-video = Επικολλημένο βίντεο
|
||||
show-recents = Φάκελος «Πρόσφατα» στην πλαϊνή στήλη
|
||||
move-to = Μετακίνηση σε...
|
||||
copy-path = Αντιγραφή διαδρομής
|
||||
move-to-title = Επιλογή προορισμού μετακίνησης
|
||||
selected-items = { $items } επιλεγμένα στοιχεία
|
||||
context-action = Ενέργεια περιβάλλοντος
|
||||
copy-to = Αντιγραφή σε...
|
||||
mixed = Μικτό
|
||||
type-to-search-select = Επιλέγει την πρώτη αντιστοιχία αρχείου ή φακέλου
|
||||
clear-recents-history = Απαλοιφή ιστορικού πρόσφατων
|
||||
context-action-confirm-warning =
|
||||
Θα εκτελεστεί σε { $items } { $items ->
|
||||
[one] στοιχείο
|
||||
*[other] στοιχεία
|
||||
}.
|
||||
light = Φωτεινό
|
||||
dark = Σκοτεινό
|
||||
|
|
|
|||
|
|
@ -96,17 +96,9 @@ save-file = Save file
|
|||
|
||||
## Open With Dialog
|
||||
open-with-title = How do you want to open "{$name}"?
|
||||
open-with-set-default = Always use this app for this file type
|
||||
browse-store = Browse {$store}
|
||||
other-apps = Other applications
|
||||
related-apps = Related applications
|
||||
context-action = Context action
|
||||
context-action-confirm-title = Run "{$name}"?
|
||||
context-action-confirm-warning = This will run on {$items} {$items ->
|
||||
[one] item
|
||||
*[other] items
|
||||
}.
|
||||
run = Run
|
||||
|
||||
## Permanently delete Dialog
|
||||
selected-items = The {$items} selected items
|
||||
|
|
@ -117,7 +109,6 @@ permanently-delete-warning = {$target} will be permanently deleted. This action
|
|||
## Rename Dialog
|
||||
rename-file = Rename file
|
||||
rename-folder = Rename folder
|
||||
rename-confirm = Rename
|
||||
|
||||
## Replace Dialog
|
||||
replace = Replace
|
||||
|
|
@ -140,12 +131,6 @@ open-with = Open with
|
|||
owner = Owner
|
||||
group = Group
|
||||
other = Other
|
||||
toolbar = Toolbar
|
||||
toolbar-available = Available
|
||||
toolbar-empty-hint = No buttons. Drag or add from below.
|
||||
toolbar-reset = Reset to defaults
|
||||
parent-directory = Parent directory
|
||||
mixed = Mixed
|
||||
### Mode 0
|
||||
none = None
|
||||
### Mode 1 (unusual)
|
||||
|
|
|
|||
|
|
@ -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,99 +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
|
||||
mixed = Sekoitettu
|
||||
context-action = Kontekstitoiminto
|
||||
context-action-confirm-title = Suoritetaanko "{ $name }"?
|
||||
context-action-confirm-warning =
|
||||
Tämä suorittaa { $items } { $items ->
|
||||
[one] kohteen
|
||||
*[other] kohdetta
|
||||
}.
|
||||
run = Suorita
|
||||
|
|
|
|||
|
|
@ -92,7 +92,6 @@ save-file = Enregistrer fichier
|
|||
## Open With Dialog
|
||||
|
||||
open-with-title = Comment souhaitez-vous ouvrir "{ $name }" ?
|
||||
open-with-set-default = Toujours utiliser cette application pour ce type de fichier
|
||||
browse-store = Parcourir { $store }
|
||||
|
||||
## Permanently delete Dialog
|
||||
|
|
@ -131,11 +130,6 @@ open-with = Ouvrir avec
|
|||
owner = Propriétaire
|
||||
group = Groupe
|
||||
other = Autre
|
||||
toolbar = Barre d'outils
|
||||
toolbar-available = Disponibles
|
||||
toolbar-empty-hint = Aucun bouton. Glisser-déposer ou ajouter depuis la liste ci-dessous.
|
||||
toolbar-reset = Rétablir par défaut
|
||||
parent-directory = Dossier parent
|
||||
|
||||
### Mode 0
|
||||
|
||||
|
|
@ -442,12 +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
|
||||
context-action-confirm-title = Exécuter "{ $name }" ?
|
||||
context-action-confirm-warning =
|
||||
Cela exécutera sur { $items } { $items ->
|
||||
[one] élément
|
||||
*[other] éléments
|
||||
}.
|
||||
run = Exécuter
|
||||
context-action = Action contextuelle
|
||||
|
|
|
|||
|
|
@ -433,12 +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
|
||||
context-action = Gníomh comhthéacsúil
|
||||
context-action-confirm-title = Rith "{ $name }"?
|
||||
context-action-confirm-warning =
|
||||
Rithfidh sé seo ar { $items } { $items ->
|
||||
[one] mhír
|
||||
*[other] míreanna
|
||||
}.
|
||||
run = Rith
|
||||
|
|
|
|||
|
|
@ -436,12 +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
|
||||
context-action = Helyi művelet
|
||||
context-action-confirm-title = Futtatod ezt: „{ $name }”?
|
||||
context-action-confirm-warning =
|
||||
Ez a művelet { $items } { $items ->
|
||||
[one] elemen
|
||||
*[other] elemen
|
||||
} fog lefutni.
|
||||
run = Futtatás
|
||||
|
|
|
|||
|
|
@ -318,12 +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
|
||||
context-action = Tindakan konteks
|
||||
context-action-confirm-title = Jalankan "{ $name }"?
|
||||
run = Jalankan
|
||||
context-action-confirm-warning =
|
||||
Ini akan dijalankan pada { $items } { $items ->
|
||||
[one] item
|
||||
*[other] item
|
||||
}.
|
||||
|
|
|
|||
|
|
@ -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,12 +318,3 @@ keywords = Folder;Manager;Бума;Басқарушы;
|
|||
show-recents = Бүйір панеліндегі «Жуырдағы құжаттар» бумасы
|
||||
clear-recents-history = Жуырдағылар тарихын өшіру
|
||||
copy-path = Орналасқан жолын көшіру
|
||||
mixed = Аралас
|
||||
context-action = Контекст әрекеті
|
||||
context-action-confirm-title = "{ $name }" орындау керек пе?
|
||||
context-action-confirm-warning =
|
||||
Бұл { $items } орындалады { $items ->
|
||||
[one] нәрсеге
|
||||
*[other] нәрсеге
|
||||
}.
|
||||
run = Орындау
|
||||
|
|
|
|||
|
|
@ -110,7 +110,7 @@ dismiss = 메시지 무시
|
|||
copy_noun = 복사
|
||||
progress = { $percent }%
|
||||
related-apps = 관련 앱
|
||||
compress = 압축...
|
||||
compress = 압축
|
||||
network-drive-error = 네트워크 드라이브에 접근할 수 없음
|
||||
icon-size-and-spacing = 아이콘 크기 및 간격
|
||||
password = 암호
|
||||
|
|
@ -272,9 +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 = 복사 경로
|
||||
move-to-title = 이동 위치 선택
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
progress = { $percent }%
|
||||
cosmic-files = COSMIC Failai
|
||||
cosmic-files = Cosmic Files
|
||||
empty-folder = Tuščias aplankas
|
||||
empty-folder-hidden = Tuščias aplankas (turi paslėptų failų)
|
||||
no-results = Rezultatų nėra
|
||||
|
|
@ -76,7 +76,7 @@ delete = Ištrinti
|
|||
permanently-delete-warning = { $target } bus ištrintas visam laikui. Šis veiksmas yra negrįžtamas.
|
||||
rename-file = Pervadinti failą
|
||||
rename-folder = Pervadinti aplanką
|
||||
replace = Keisti
|
||||
replace = Pakeisti
|
||||
replace-title = „{ $filename }“ jau egzistuoja šioje vietoje
|
||||
replace-warning = Ar norite pakeisti tai su tuo, kas yra įrašoma? Keičiant bus pakeistas turinys.
|
||||
replace-warning-operation = Ar norite pakeisti tai? Pakeičiant bus keičiamas turinys.
|
||||
|
|
@ -244,7 +244,7 @@ item-created = Sukurtas: { $created }
|
|||
item-modified = Modifikuota: { $modified }
|
||||
item-accessed = Paskutinė prieiga: { $accessed }
|
||||
calculating = Skaičiuojama...
|
||||
settings = Nuostatos
|
||||
settings = Nustatymai
|
||||
single-click = Vieno paspaudimo atidarymas
|
||||
appearance = Išvaizda
|
||||
match-desktop = Pagal darbalaukio temą
|
||||
|
|
@ -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
|
||||
|
|
@ -280,18 +280,18 @@ quit = Išeiti
|
|||
edit = Redaguoti
|
||||
cut = Iškirpti
|
||||
copy = Kopijuoti
|
||||
paste = Įdėti
|
||||
select-all = Žymėti viską
|
||||
zoom-in = Artinti
|
||||
paste = Įklijuoti
|
||||
select-all = Pažymėti viską
|
||||
zoom-in = Priartinti
|
||||
default-size = Numatytas dydis
|
||||
zoom-out = Tolinti
|
||||
zoom-out = Nutolinti
|
||||
view = Rodymas
|
||||
grid-view = Tinklelio išdėstymas
|
||||
list-view = Sąrašo išdėstymas
|
||||
show-hidden-files = Rodyti paslėptus failus
|
||||
list-directories-first = Pirmiau pateikti aplankus
|
||||
gallery-preview = Galerijos peržiūra
|
||||
menu-settings = Nuostatos...
|
||||
menu-settings = Nustatymai...
|
||||
menu-about = Apie COSMIC Files...
|
||||
sort = Rikiuoti
|
||||
sort-a-z = A-Ž
|
||||
|
|
@ -302,7 +302,7 @@ sort-smallest-to-largest = Nuo mažiausio iki didžiausio
|
|||
sort-largest-to-smallest = Nuo didžiausio iki mažiausio
|
||||
dark = Tamsus
|
||||
light = Šviesus
|
||||
comment = COSMIC aplinkos failų tvarkyklė
|
||||
comment = COSMIC desktop failų tvarkyklė
|
||||
keywords = Aplankas;Tvarkyklė;
|
||||
copy-to-title = Pasirinkti kopijavimo vietą
|
||||
copy-to-button-label = Kopijuoti
|
||||
|
|
@ -317,4 +317,3 @@ clear-recents-history = Išvalyti Neseniai naudotų istoriją
|
|||
copy-to = Kopijuoti į...
|
||||
move-to = Perkeltiį į...
|
||||
copy-path = Kopijuoti kelią
|
||||
theme = Stilius
|
||||
|
|
|
|||
|
|
@ -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,12 +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
|
||||
context-action = Akcja sytuacyjna
|
||||
context-action-confirm-title = Uruchomić "{ $name }"?
|
||||
context-action-confirm-warning =
|
||||
Zostanie uruchomionych { $items } { $items ->
|
||||
[one] element
|
||||
*[other] elementów
|
||||
}.
|
||||
run = Uruchom
|
||||
|
|
|
|||
|
|
@ -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,15 +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
|
||||
context-action = Ação de contexto
|
||||
context-action-confirm-title = Executar "{ $name }"?
|
||||
context-action-confirm-warning =
|
||||
Isso será executado em { $items } { $items ->
|
||||
[one] item
|
||||
*[other] itens
|
||||
}.
|
||||
run = Executar
|
||||
|
|
|
|||
|
|
@ -352,5 +352,3 @@ sort-newest-first = Mais recentes primeiro
|
|||
sort-oldest-first = Mais antigos primeiro
|
||||
sort-smallest-to-largest = Do menor para o maior
|
||||
sort-largest-to-smallest = Do maior para o menor
|
||||
context-action-confirm-title = Executar "{ $name }"?
|
||||
run = Executar
|
||||
|
|
|
|||
|
|
@ -195,7 +195,7 @@ dark = Тёмная
|
|||
light = Светлая
|
||||
# Context menu
|
||||
add-to-sidebar = Добавить на боковую панель
|
||||
compress = Сжать...
|
||||
compress = Сжать
|
||||
extract-here = Распаковать
|
||||
new-file = Новый файл…
|
||||
new-folder = Новая папка…
|
||||
|
|
@ -380,12 +380,3 @@ keywords = Папка;Менеджер;
|
|||
show-recents = «Недавние документы» в бок. панели
|
||||
clear-recents-history = Очистить историю недавних
|
||||
copy-path = Копировать путь
|
||||
mixed = Смешанные
|
||||
context-action = Контекстная команда
|
||||
context-action-confirm-title = Выполнить «{ $name }»?
|
||||
context-action-confirm-warning =
|
||||
Команда затронет { $items } { $items ->
|
||||
[one] элемент
|
||||
*[other] элем.
|
||||
}.
|
||||
run = Выполнить
|
||||
|
|
|
|||
|
|
@ -1,329 +0,0 @@
|
|||
open-file = Отвори датотеку
|
||||
quit = Изађи
|
||||
cancel = Откажи
|
||||
open = Отвори
|
||||
run = Покрени
|
||||
connect = Повежи
|
||||
save = Сачувај
|
||||
password = Лозинка
|
||||
remove = Уклони
|
||||
appearance = Изглед
|
||||
username = Корисничко име
|
||||
light = Светла
|
||||
dark = Тамна
|
||||
settings = Подешавања
|
||||
replace = Замени
|
||||
size = Величина
|
||||
sort-newest-first = Најновије прво
|
||||
default-app = { $name } (подразумевано)
|
||||
renamed = Преименована „{ $from }“ у „{ $to }“
|
||||
read-execute = Читање и извршавање
|
||||
deleted =
|
||||
Обрисано је { $items } { $items ->
|
||||
[one] ставка
|
||||
*[other] ставки
|
||||
} из { trash }
|
||||
item-modified = Измењено: { $modified }
|
||||
dismiss = Одбаци поруку
|
||||
list-view = Преглед у виду списака
|
||||
reload-folder = Поново учитај фасциклу
|
||||
copy_noun = Копија
|
||||
favorite-path-error = Грешка при отварању директоријума
|
||||
progress = { $percent }%
|
||||
remove-from-sidebar = Уклони из бочне траке
|
||||
related-apps = Повезани програми
|
||||
restoring =
|
||||
Враћање { $items } { $items ->
|
||||
[one] ставке
|
||||
*[other] ставки
|
||||
} из { trash } ({ $progress })...
|
||||
network-drive-error = Немогуће приступити мрежном уређају
|
||||
gallery-preview = Преглед галерије
|
||||
sort-smallest-to-largest = Од најмање до највеће
|
||||
zoom-in = Увећајте приказ
|
||||
select-all = Означи све
|
||||
icon-size-and-spacing = Величина и размак иконица
|
||||
removing-from-recents =
|
||||
Уклањање { $items } { $items ->
|
||||
[one] ставке
|
||||
*[other] ставки
|
||||
} из { recents }
|
||||
cosmic-files = Космик датотеке
|
||||
type-to-search-enter-path = Уноси путању до директоријума или датотеке
|
||||
trash = Смеће
|
||||
emptying-trash = Пражњење { trash } ({ $progress })...
|
||||
trashed-on = Премештено у смеће
|
||||
new-window = Нови прозор
|
||||
zoom-out = Умањите приказ
|
||||
compressing =
|
||||
Сажимање { $items } { $items ->
|
||||
[one] ставке
|
||||
*[other] ставки
|
||||
} из „{ $from }“ у „{ $to }“ ({ $progress })...
|
||||
move-to-trash = Премести у смеће
|
||||
menu-about = О програму Космик Датотеке...
|
||||
setting-executable-and-launching = Подешавање „{ $name }“ као извршне датотеке и покретање
|
||||
open-multiple-files = Отвори више датотека
|
||||
default-size = Подразумевана величина
|
||||
menu-open-with = Отвори програмом...
|
||||
extracted =
|
||||
Извлачено { $items } { $items ->
|
||||
[one] ставка
|
||||
*[other] ставки
|
||||
} из „{ $from }“ у „{ $to }“
|
||||
create-new-folder = Направи нову фасциклу
|
||||
original-file = Изворна датотека
|
||||
create = Направи
|
||||
create-archive = Направи архиву
|
||||
read-write-execute = Читање, уписивање и извршавање
|
||||
other-apps = Остали програми
|
||||
set-permissions = Овлашћења за „{ $name }“ су постављена на { $mode }
|
||||
pause = Паузирај
|
||||
calculating = Израчунавам…
|
||||
sort-by-size = Поређај по величини
|
||||
rename = Преименуј...
|
||||
empty-folder-hidden = Празна фасцикла (има сакривених ставки)
|
||||
keep = Задржи
|
||||
item-size = Величина: { $size }
|
||||
permanently-deleting =
|
||||
Трајно брисање { $items } { $items ->
|
||||
[one] ставке
|
||||
*[other] ставки
|
||||
}
|
||||
edit = Уреди
|
||||
connecting = Повезујем се...
|
||||
read-write = Читање и уписивање
|
||||
copy = Умножи
|
||||
none = Ништа
|
||||
items = Ставки: { $items }
|
||||
no-results = Нису пронађени резултати
|
||||
theme = Тема
|
||||
type = Врста: { $mime }
|
||||
compressed =
|
||||
Сажето { $items } { $items ->
|
||||
[one] ставка
|
||||
*[other] ставки
|
||||
} из „{ $from }“ у „{ $to }“
|
||||
replace-warning = Да ли желите да га замените оним који снимате? Замена ће преписати његов садржај.
|
||||
rename-folder = Преименуј фасциклу
|
||||
new-file = Нова датотека...
|
||||
close-tab = Затвори језичак
|
||||
name = Назив
|
||||
open-in-terminal = Отвори у терминалу
|
||||
resume = Настави
|
||||
open-multiple-folders = Отвори више фасцикла
|
||||
remember-password = Запамти лозинку
|
||||
show-details = Прикажи детаље
|
||||
grid-spacing = Размак мреже
|
||||
extract-to = Распакуј у...
|
||||
add-network-drive = Додај мрежни уређај
|
||||
copying =
|
||||
Умножавање { $items } { $items ->
|
||||
[one] ставке
|
||||
*[other] ставки
|
||||
} из „{ $from }“ у „{ $to }“ ({ $progress })...
|
||||
delete = Обриши
|
||||
sort-oldest-first = Најстарије прво
|
||||
repository = Ризница
|
||||
create-new-file = Направи нову датотеку
|
||||
sort-by-trashed = Поређај по времену брисања
|
||||
replace-warning-operation = Да ли желите да га замените? Замена ће преписати његов садржај.
|
||||
support = Подршка
|
||||
try-again = Покушај поново
|
||||
eject = Избаци
|
||||
copied =
|
||||
Умножено { $items } { $items ->
|
||||
[one] ставке
|
||||
*[other] ставки
|
||||
} из „{ $from }“ у „{ $to }“
|
||||
other = Друго
|
||||
open-in-new-window = Отвори у новом прозору
|
||||
empty-folder = Празна фасцикла
|
||||
sort-by-modified = Поређај по датуму измене
|
||||
list-directories-first = Прикажи директоријуме прво
|
||||
read-only = Само за читање
|
||||
folder-name = Назив фасцикле
|
||||
browse-store = Разгледај { $store }
|
||||
enter-server-address = Унесите адресу сервера
|
||||
remove-from-recents = Уклони из недавних
|
||||
connect-anonymously = Повежи се анонимно
|
||||
group = Група
|
||||
apply-to-all = Примени на све
|
||||
skip = Прескочи
|
||||
paste = Залепи
|
||||
menu-settings = Подешавања...
|
||||
moving =
|
||||
Премештање { $items } { $items ->
|
||||
[one] датотеке
|
||||
*[other] датотека
|
||||
} из „{ $from }“ у „{ $to }“ ({ $progress })...
|
||||
replace-with = Замени са
|
||||
recents = Недавно
|
||||
change-wallpaper = Промени позадину...
|
||||
network-drive-description =
|
||||
Адресе сервера укључују префикс протокола и адресу.
|
||||
Примери: ssh://192.168.0.1, ftp://[2001:db8::1]
|
||||
deleting =
|
||||
Брише се { $items } { $items ->
|
||||
[one] ставка
|
||||
*[other] ставки
|
||||
} из { trash } ({ $progress })...
|
||||
single-click = Један клик за отварање
|
||||
view = Преглед
|
||||
undo = Опозови
|
||||
setting-permissions = Подешавање овлашћења за „{ $name }“ на { $mode }
|
||||
owner = Власник
|
||||
creating = Правим „{ $name }“ у „{ $parent }“
|
||||
execute-only = Само извршавање
|
||||
open-item-location = Отвори локацију ставке
|
||||
details = Детаљи
|
||||
set-executable-and-launched = Постављено „{ $name }“ као извршну датотеку и покренуто
|
||||
mounted-drives = Прикључени дискови
|
||||
sort-a-z = А-Ш
|
||||
mount-error = Немогуће приступити уређају
|
||||
extract-here = Извуци
|
||||
grid-view = Преглед у виду мреже
|
||||
filesystem = Систем датотека
|
||||
set-and-launch = Подеси и покрени
|
||||
removed-from-recents =
|
||||
Уклоњено { $items } { $items ->
|
||||
[one] ставке
|
||||
*[other] ставки
|
||||
} из { recents }
|
||||
add-to-sidebar = Додај у страничник
|
||||
item-created = Направљено: { $created }
|
||||
network-drive-schemes =
|
||||
Доступни протоколи,Префикс
|
||||
ЕплТок,afp://
|
||||
Протокол за пренос датотека,ftp:// или ftps://
|
||||
Мрежни систем датотека,nfs://
|
||||
Серверски блок порука,smb://
|
||||
SSH протокол за пренос датотека,sftp:// или ssh://
|
||||
ВебДАВ,dav:// или davs://
|
||||
home = Лична
|
||||
set-executable-and-launch = Постави као извршну и покрени
|
||||
restored =
|
||||
Враћено { $items } { $items ->
|
||||
[one] ставка
|
||||
*[other] ставки
|
||||
} из { trash }
|
||||
sort-z-a = Ш-А
|
||||
type-to-search-recursive = Претражује тренутну фасциклу и све подфасцикле
|
||||
history = Историјат
|
||||
progress-paused = { $percent }%, паузирано
|
||||
desktop-view-options = Могућности приказа радне површине...
|
||||
show-on-desktop = Прикажи на радној површини
|
||||
cancelled = Отказано
|
||||
new-folder = Нова фасцикла...
|
||||
match-desktop = Прати радну површину
|
||||
domain = Домен
|
||||
operations-running-finished =
|
||||
{ $running } { $running ->
|
||||
[one] радња покренута
|
||||
*[other] радње покренуте
|
||||
} ({ $percent }%), { $finished } завршено...
|
||||
sort-by-name = Поређај по називу
|
||||
edit-history = Историјат уређивања
|
||||
sort = Поређај
|
||||
show-hidden-files = Прикажи скривене датотеке
|
||||
progress-failed = { $percent }%, није успело
|
||||
trash-folder-icon = Иконица фасцикле Смеће
|
||||
item-accessed = Приступљено: { $accessed }
|
||||
extract-to-title = Распакуј у фасциклу
|
||||
open-with = Отвори помоћу
|
||||
keep-both = Задржи оба
|
||||
icon-size = Величина иконице
|
||||
open-with-title = Како желите да отворите „{ $name }“?
|
||||
extracting =
|
||||
Извлачење { $items } { $items ->
|
||||
[one] ставке
|
||||
*[other] ставки
|
||||
} из „{ $from }“ у „{ $to }“ ({ $progress })...
|
||||
permanently-deleted =
|
||||
Трајно обрисано { $items } { $items ->
|
||||
[one] ставке
|
||||
*[other] ставки
|
||||
}
|
||||
complete = Завршено
|
||||
write-execute = Писање и извршавање
|
||||
extract-password-required = Потребна је лозинка
|
||||
pending = У току
|
||||
desktop-folder-content = Садржај фасцикле радне површине
|
||||
renaming = Преименовање „{ $from }“ у „{ $to }“
|
||||
set-executable-and-launch-description = Да ли желите да поставите „{ $name }“ као извршну и покренете је?
|
||||
no-history = Нема ставки у историјату.
|
||||
open-folder = Отвори фасциклу
|
||||
emptied-trash = Опражњено { trash }
|
||||
rename-file = Преименуј датотеку
|
||||
sort-largest-to-smallest = Од највеће до најмање
|
||||
restore-from-trash = Врати из смећа
|
||||
cut = Исеци
|
||||
moved =
|
||||
Премештено { $items } { $items ->
|
||||
[one] датотеке
|
||||
*[other] датотека
|
||||
} из „{ $from }“ у „{ $to }“
|
||||
progress-cancelled = { $percent }%, отказано
|
||||
open-in-new-tab = Отвори у новом језичку
|
||||
unknown-folder = непозната фасцикла
|
||||
file = Датотека
|
||||
file-name = Назив датотеке
|
||||
save-file = Сачувај датотеку
|
||||
created = Направљено „{ $name }“ у „{ $parent }“
|
||||
delete-permanently = Трајно обриши
|
||||
networks = Мреже
|
||||
write-only = Само писање
|
||||
today = Данас
|
||||
display-settings = Подешавања екрана...
|
||||
new-tab = Нови језичак
|
||||
failed = Неуспешно
|
||||
modified = Измењено
|
||||
desktop-appearance = Изглед радне површине...
|
||||
file-already-exists = Датотека са овим називом већ постоји
|
||||
name-hidden = Називи који почињу тачком „.“ ће бити сакривени
|
||||
folder-already-exists = Фасцикла са овим називом већ постоји
|
||||
permanently-delete-warning = { $target } ће бити трајно обрисано. Ова радња се не може поништити.
|
||||
favorite-path-error-description =
|
||||
Не можемо да отворимо „{ $path }“
|
||||
„{ $path }“ можда не постоји или немате дозволу за његово отварање
|
||||
|
||||
Желите ли да га уклоните из бочне површи?
|
||||
empty-trash-warning = Ставке у смећу биће трајно обрисане
|
||||
empty-trash = Испразни смеће
|
||||
empty-trash-title = Испразнити смеће?
|
||||
type-to-search = Куцајте за претрагу
|
||||
notification-in-progress = Радње са датотекама су у току
|
||||
name-no-slashes = Назив не може садржати косе црте
|
||||
permanently-delete-question = Трајно обриши?
|
||||
replace-title = „{ $filename }“ већ постоји на овој локацији
|
||||
name-invalid = Назив не може бити „{ $filename }“
|
||||
operations-running =
|
||||
{ $running } { $running ->
|
||||
[one] радња покренута
|
||||
*[other] радње покренуте
|
||||
} ({ $percent }%)...
|
||||
comment = Управник датотека за Космик радну површину
|
||||
keywords = Folder;Manager;Фасцикла;Управник;fascikla;upravnik;
|
||||
copy-to-title = Изабери одредиште умножавања
|
||||
copy-to-button-label = Умножи
|
||||
move-to-title = Изабери одредиште премештања
|
||||
move-to-button-label = Помери
|
||||
context-action = Контекстна радња
|
||||
context-action-confirm-title = Покрени „{ $name }“?
|
||||
context-action-confirm-warning =
|
||||
Ово ће се извршити на { $items } { $items ->
|
||||
[one] ставку
|
||||
*[other] ставки
|
||||
}.
|
||||
selected-items = Изабраних { $items } ставки
|
||||
mixed = Помешано
|
||||
pasted-image = Убачена слика
|
||||
pasted-text = Убачен текст
|
||||
pasted-video = Убачен видео
|
||||
show-recents = Недавна фасцикла у бочној површи
|
||||
type-to-search-select = Обира прву подударајућу датотеку или фасциклу
|
||||
clear-recents-history = Очисти историју недавних
|
||||
compress = Сажми...
|
||||
copy-to = Умножи у...
|
||||
move-to = Помери у...
|
||||
copy-path = Умножи путању
|
||||
|
|
@ -101,7 +101,6 @@ open-with = Öppna med
|
|||
owner = Ägare
|
||||
group = Grupp
|
||||
other = Andra
|
||||
mixed = Blandade
|
||||
# Listvy
|
||||
name = Namn
|
||||
modified = Ändrad
|
||||
|
|
@ -410,11 +409,3 @@ move-to = Flytta till...
|
|||
show-recents = Mapp för senast använda filer i sidofältet
|
||||
clear-recents-history = Töm historik för Senaste
|
||||
copy-path = Kopiera sökväg
|
||||
context-action = Kontextåtgärd
|
||||
context-action-confirm-title = Kör "{ $name }"?
|
||||
context-action-confirm-warning =
|
||||
Detta kommer att köras på { $items } { $items ->
|
||||
[one] objekt
|
||||
*[other] objekt
|
||||
}.
|
||||
run = Kör
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ filesystem = Файлова система
|
|||
home = Домівка
|
||||
trash = Смітник
|
||||
recents = Нещодавні
|
||||
undo = Скасувати
|
||||
undo = Відмінити
|
||||
# List view
|
||||
name = Назва
|
||||
modified = Змінено
|
||||
|
|
@ -86,7 +86,6 @@ copying =
|
|||
copied =
|
||||
Скопійовано { $items } { $items ->
|
||||
[one] елемент
|
||||
[few] елементи
|
||||
*[other] елеменів
|
||||
} з «{ $from }» в «{ $to }»
|
||||
emptying-trash = Спорожнення { trash } ({ $progress })...
|
||||
|
|
@ -99,8 +98,7 @@ moving =
|
|||
moved =
|
||||
Переміщено { $items } { $items ->
|
||||
[one] елемент
|
||||
[few] елементи
|
||||
*[other] елементів
|
||||
*[other] елементи
|
||||
} з «{ $from }» в «{ $to }»
|
||||
renaming = Перейменування «{ $from }» на «{ $to }»
|
||||
renamed = Перейменовано «{ $from }» на «{ $to }»
|
||||
|
|
@ -112,8 +110,7 @@ restoring =
|
|||
restored =
|
||||
Відновлено { $items } { $items ->
|
||||
[one] елемент
|
||||
[few] елементи
|
||||
*[other] елементів
|
||||
*[other] елементи
|
||||
} з { trash }
|
||||
unknown-folder = невідома тека
|
||||
|
||||
|
|
@ -226,7 +223,7 @@ permanently-delete-question = Остаточно видалити?
|
|||
delete = Видалити
|
||||
permanently-delete-warning = { $target } буде остаточно видалено. Цю дію не можна скасувати.
|
||||
set-executable-and-launch = Зробити виконуваним і запустити
|
||||
set-executable-and-launch-description = Бажаєте зробити «{ $name }» виконуваним і запустити його?
|
||||
set-executable-and-launch-description = Бажаєте зробити "{ $name }" виконуваним і запустити його?
|
||||
set-and-launch = Зробити і запустити
|
||||
open-with = Відкрити за допомогою
|
||||
owner = Власник
|
||||
|
|
@ -248,7 +245,7 @@ favorite-path-error-description =
|
|||
Вилучити з бічної панелі?
|
||||
keep = Залишити
|
||||
add-network-drive = Додати мережевий диск
|
||||
connect = З’єднати
|
||||
connect = З'єднати
|
||||
connect-anonymously = З'єднатись анонімно
|
||||
connecting = З'єднання…
|
||||
domain = Домен
|
||||
|
|
@ -277,13 +274,12 @@ compressing =
|
|||
Стиснення { $items } { $items ->
|
||||
[one] елемента
|
||||
*[other] елементів
|
||||
} з «{ $from }» до «{ $to }» ({ $progress })...
|
||||
} з "{ $from }" до "{ $to }" ({ $progress })...
|
||||
compressed =
|
||||
Стиснуто { $items } { $items ->
|
||||
[one] елемент
|
||||
[few] елементи
|
||||
*[other] елементів
|
||||
} з «{ $from }» до «{ $to }»
|
||||
*[other] елементи
|
||||
} з "{ $from }" до "{ $to }"
|
||||
deleting =
|
||||
Видалення { $items } { $items ->
|
||||
[one] елемента
|
||||
|
|
@ -292,7 +288,6 @@ deleting =
|
|||
deleted =
|
||||
Видалено { $items } { $items ->
|
||||
[one] елемент
|
||||
[few] елементи
|
||||
*[other] елементи
|
||||
} з { trash }
|
||||
extracting =
|
||||
|
|
@ -303,8 +298,7 @@ extracting =
|
|||
extracted =
|
||||
Видобуто { $items } { $items ->
|
||||
[one] елемент
|
||||
[few] елементи
|
||||
*[other] елементів
|
||||
*[other] елементи
|
||||
} з «{ $from }» в «{ $to }»
|
||||
setting-executable-and-launching = Надання «{ $name }» прав на виконання та запуск
|
||||
set-executable-and-launched = «{ $name }» надано права на виконання і відкрито
|
||||
|
|
@ -323,7 +317,7 @@ single-click = Відкривати одним клацанням
|
|||
type-to-search = Введіть для пошуку
|
||||
type-to-search-recursive = Шукає у поточній теці та всіх підтеках
|
||||
type-to-search-enter-path = Вводить шлях до каталогу або файлу
|
||||
compress = Стиснути...
|
||||
compress = Стиснути
|
||||
delete-permanently = Остаточно видалити
|
||||
eject = Безпечно вилучити
|
||||
extract-here = Видобути
|
||||
|
|
@ -349,8 +343,7 @@ permanently-deleting =
|
|||
permanently-deleted =
|
||||
Остаточно вилучено { $items } { $items ->
|
||||
[one] елемент
|
||||
[few] елементи
|
||||
*[other] елементів
|
||||
*[other] елементи
|
||||
}
|
||||
removing-from-recents =
|
||||
Вилучення { $items } { $items ->
|
||||
|
|
@ -360,14 +353,13 @@ removing-from-recents =
|
|||
removed-from-recents =
|
||||
Вилучено { $items } { $items ->
|
||||
[one] елемент
|
||||
[few] елементи
|
||||
*[other] елементів
|
||||
*[other] елементи
|
||||
} з { recents }
|
||||
empty-trash-title = Спорожити смітник?
|
||||
type-to-search-select = Вибирає перший відповідний файл або папку
|
||||
pasted-image = Вставлене зображення
|
||||
pasted-text = Вставлений текст
|
||||
pasted-video = Вставлене відео
|
||||
pasted-image = Вставлене Зображення
|
||||
pasted-text = Вставлений Текст
|
||||
pasted-video = Вставлене Видиво
|
||||
copy-to-button-label = Копіювати
|
||||
move-to-button-label = Перемістити
|
||||
copy-to = Копіювати до…
|
||||
|
|
@ -379,11 +371,3 @@ keywords = Тека;Папка;Провідник;Менеджер;Катало
|
|||
show-recents = Тека «Нещодавні» на бічній панелі
|
||||
copy-path = Копіювати шлях
|
||||
clear-recents-history = Очистити нещодавні
|
||||
context-action-confirm-title = Запустити «{ $name }»?
|
||||
run = Виконати
|
||||
context-action-confirm-warning =
|
||||
Запуститься для { $items } { $items ->
|
||||
[one] елемента
|
||||
*[other] елементів
|
||||
}.
|
||||
context-action = Контекстна дія
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -167,7 +167,7 @@ read-write-execute = 读取、写入和执行
|
|||
|
||||
## Favorite Path Error Dialog
|
||||
|
||||
favorite-path-error = 打开目录时出错
|
||||
favorite-path-error = 打开路径时出错
|
||||
favorite-path-error-description =
|
||||
无法打开 "{ $path }" 。
|
||||
"{ $path }" 可能不存在或您没有权限打开它。
|
||||
|
|
@ -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
|
||||
|
||||
|
|
@ -348,15 +348,15 @@ light = 亮色模式
|
|||
|
||||
type-to-search = 输入即可搜索
|
||||
type-to-search-recursive = 搜索当前文件夹及其所有子文件夹
|
||||
type-to-search-enter-path = 输入文件夹或文件目录
|
||||
type-to-search-enter-path = 输入文件夹或文件路径
|
||||
# Context menu
|
||||
add-to-sidebar = 加入侧边栏
|
||||
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,13 +435,4 @@ comment = COSMIC 桌面的文件管理器
|
|||
keywords = 文件夹;管理器;
|
||||
clear-recents-history = 清除最近访问历史
|
||||
copy-path = 复制文件路径
|
||||
show-recents = 侧边栏中的最近访问
|
||||
mixed = 混合
|
||||
context-action-confirm-title = 运行 “{ $name }” ?
|
||||
run = 运行
|
||||
context-action-confirm-warning =
|
||||
该行动将会在 { $items } { $items ->
|
||||
[one] 项目
|
||||
*[other] 项目
|
||||
} 上运行。
|
||||
context-action = 环境行动
|
||||
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,11 +33,11 @@ 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
|
||||
|
||||
|
|
@ -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,120 +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 = 混合
|
||||
context-action = 環境行動
|
||||
context-action-confirm-title = 執行「{ $name }」嗎?
|
||||
context-action-confirm-warning =
|
||||
該行動將會在 { $items } { $items ->
|
||||
[one] 項目
|
||||
*[other] 項目
|
||||
} 上執行。
|
||||
run = 執行
|
||||
|
|
|
|||
|
|
@ -1,3 +0,0 @@
|
|||
[toolchain]
|
||||
channel = "1.93.0"
|
||||
components = ["clippy", "rustfmt"]
|
||||
|
|
@ -1 +0,0 @@
|
|||
imports_granularity = "Module"
|
||||
1425
src/app.rs
1425
src/app.rs
File diff suppressed because it is too large
Load diff
|
|
@ -1,14 +1,17 @@
|
|||
use crate::mime_icon::mime_for_path;
|
||||
use crate::operation::{Controller, OpReader, OperationError, OperationErrorType, sync_to_disk};
|
||||
use crate::{
|
||||
mime_icon::mime_for_path,
|
||||
operation::{Controller, OpReader, OperationError, OperationErrorType, sync_to_disk},
|
||||
};
|
||||
use chrono::TimeZone;
|
||||
use chrono::{Datelike, Timelike};
|
||||
use cosmic::iced::futures;
|
||||
use jiff::Zoned;
|
||||
use jiff::civil::DateTime;
|
||||
use jiff::tz::TimeZone;
|
||||
use std::collections::HashSet;
|
||||
use std::fs;
|
||||
use std::io::{self, Read, Write};
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::time::SystemTime;
|
||||
use std::{
|
||||
collections::HashSet,
|
||||
fs,
|
||||
io::{self, Read, Write},
|
||||
path::{Path, PathBuf},
|
||||
time::SystemTime,
|
||||
};
|
||||
use zip::result::ZipError;
|
||||
|
||||
pub const SUPPORTED_ARCHIVE_TYPES: &[&str] = &[
|
||||
|
|
@ -110,8 +113,7 @@ fn zip_extract<R: io::Read + io::Seek, P: AsRef<Path>>(
|
|||
password: Option<&str>,
|
||||
controller: Controller,
|
||||
) -> zip::result::ZipResult<()> {
|
||||
use std::ffi::OsString;
|
||||
use std::fs;
|
||||
use std::{ffi::OsString, fs};
|
||||
use zip::result::ZipError;
|
||||
|
||||
fn make_writable_dir_all<T: AsRef<Path>>(
|
||||
|
|
@ -188,8 +190,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)]
|
||||
{
|
||||
|
|
@ -200,15 +200,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
|
||||
|
|
@ -289,25 +285,25 @@ fn zip_extract<R: io::Read + io::Seek, P: AsRef<Path>>(
|
|||
}
|
||||
|
||||
fn zip_date_time_to_system_time(date_time: zip::DateTime) -> Option<SystemTime> {
|
||||
let dt = DateTime::new(
|
||||
date_time.year() as i16,
|
||||
date_time.month() as i8,
|
||||
date_time.day() as i8,
|
||||
date_time.hour() as i8,
|
||||
date_time.minute() as i8,
|
||||
date_time.second() as i8,
|
||||
0,
|
||||
)
|
||||
.ok()?;
|
||||
TimeZone::system()
|
||||
.to_ambiguous_zoned(dt)
|
||||
.later()
|
||||
.ok()
|
||||
let date = chrono::NaiveDate::from_ymd_opt(
|
||||
date_time.year() as i32,
|
||||
date_time.month() as u32,
|
||||
date_time.day() as u32,
|
||||
)?;
|
||||
let time = chrono::NaiveTime::from_hms_opt(
|
||||
date_time.hour() as u32,
|
||||
date_time.minute() as u32,
|
||||
date_time.second() as u32,
|
||||
)?;
|
||||
let naive = chrono::NaiveDateTime::new(date, time);
|
||||
chrono::Local
|
||||
.from_local_datetime(&naive)
|
||||
.latest()
|
||||
.map(SystemTime::from)
|
||||
}
|
||||
|
||||
pub fn system_time_to_zip_date_time(system_time: SystemTime) -> Option<zip::DateTime> {
|
||||
let date_time = Zoned::try_from(system_time).ok()?;
|
||||
let date_time: chrono::DateTime<chrono::Local> = system_time.into();
|
||||
|
||||
zip::DateTime::from_date_and_time(
|
||||
date_time.year() as u16,
|
||||
|
|
|
|||
|
|
@ -1,73 +0,0 @@
|
|||
// Copyright 2025 System76 <info@system76.com>
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
use std::collections::VecDeque;
|
||||
use std::sync::atomic::{AtomicBool, Ordering};
|
||||
use std::sync::{Arc, Mutex};
|
||||
|
||||
/// 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()
|
||||
}
|
||||
}
|
||||
|
|
@ -2,10 +2,12 @@
|
|||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
use cosmic::iced::clipboard::mime::{AllowedMimeTypes, AsMimeTypes};
|
||||
use std::borrow::Cow;
|
||||
use std::error::Error;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::str;
|
||||
use std::{
|
||||
borrow::Cow,
|
||||
error::Error,
|
||||
path::{Path, PathBuf},
|
||||
str,
|
||||
};
|
||||
use url::Url;
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
|
|
@ -126,15 +128,10 @@ impl TryFrom<(Vec<u8>, String)> for ClipboardPaste {
|
|||
// Assume the kind is Copy if not provided by the mime type
|
||||
let mut kind = ClipboardKind::Copy;
|
||||
let mut paths = Vec::new();
|
||||
|
||||
match mime.as_str() {
|
||||
"text/uri-list" => {
|
||||
let text = str::from_utf8(&data)?;
|
||||
|
||||
for line in text.lines().filter(|line| {
|
||||
let line = line.trim();
|
||||
!line.is_empty() && !line.starts_with('#')
|
||||
}) {
|
||||
for line in text.lines() {
|
||||
let url = Url::parse(line)?;
|
||||
match url.to_file_path() {
|
||||
Ok(path) => paths.push(path),
|
||||
|
|
@ -144,7 +141,6 @@ impl TryFrom<(Vec<u8>, String)> for ClipboardPaste {
|
|||
}
|
||||
"x-special/gnome-copied-files" => {
|
||||
let text = str::from_utf8(&data)?;
|
||||
|
||||
for (i, line) in text.lines().enumerate() {
|
||||
if i == 0 {
|
||||
kind = match line {
|
||||
|
|
|
|||
117
src/config.rs
117
src/config.rs
|
|
@ -1,20 +1,20 @@
|
|||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
use std::any::TypeId;
|
||||
use std::num::NonZeroU16;
|
||||
use std::path::PathBuf;
|
||||
use std::{any::TypeId, num::NonZeroU16, path::PathBuf};
|
||||
|
||||
use cosmic::cosmic_config::cosmic_config_derive::CosmicConfigEntry;
|
||||
use cosmic::cosmic_config::{self, CosmicConfigEntry};
|
||||
use cosmic::iced::Subscription;
|
||||
use cosmic::{Application, theme};
|
||||
use cosmic::{
|
||||
Application,
|
||||
cosmic_config::{self, CosmicConfigEntry, cosmic_config_derive::CosmicConfigEntry},
|
||||
iced::Subscription,
|
||||
theme,
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::FxOrderMap;
|
||||
use crate::app::App;
|
||||
use crate::tab::{HeadingOptions, Location, View};
|
||||
|
||||
pub use crate::context_action::{ContextActionPreset, ContextActionSelection};
|
||||
use crate::{
|
||||
FxOrderMap,
|
||||
app::App,
|
||||
tab::{HeadingOptions, Location, View},
|
||||
};
|
||||
|
||||
pub const CONFIG_VERSION: u64 = 1;
|
||||
|
||||
|
|
@ -164,17 +164,11 @@ pub struct Config {
|
|||
pub app_theme: AppTheme,
|
||||
pub dialog: DialogConfig,
|
||||
pub desktop: DesktopConfig,
|
||||
pub context_actions: Vec<ContextActionPreset>,
|
||||
pub thumb_cfg: ThumbCfg,
|
||||
pub favorites: Vec<Favorite>,
|
||||
pub show_details: bool,
|
||||
pub show_recents: bool,
|
||||
pub tab: TabConfig,
|
||||
/// Yoda phase 3: Dolphin-style quick actions toolbar. An ordered list
|
||||
/// of enabled buttons — position in the vec drives the toolbar order.
|
||||
/// Reorder in Settings via drag-drop; items not in the vec are
|
||||
/// hidden. Default = the minimal-6 set from phase 1.
|
||||
pub toolbar: Vec<ToolbarAction>,
|
||||
pub type_to_search: TypeToSearch,
|
||||
}
|
||||
|
||||
|
|
@ -226,7 +220,6 @@ impl Default for Config {
|
|||
app_theme: AppTheme::System,
|
||||
desktop: DesktopConfig::default(),
|
||||
dialog: DialogConfig::default(),
|
||||
context_actions: Vec::new(),
|
||||
thumb_cfg: ThumbCfg::default(),
|
||||
favorites: vec![
|
||||
Favorite::Home,
|
||||
|
|
@ -239,97 +232,11 @@ impl Default for Config {
|
|||
show_details: false,
|
||||
show_recents: true,
|
||||
tab: TabConfig::default(),
|
||||
toolbar: default_toolbar(),
|
||||
type_to_search: TypeToSearch::Recursive,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Yoda phase 3: ordered enum of quick-action toolbar buttons.
|
||||
/// The Config stores `Vec<ToolbarAction>` so the user can pick BOTH
|
||||
/// visibility (just include/exclude the variant) AND order (position in
|
||||
/// the vec). Drag-drop reorder in the Settings page moves items around.
|
||||
#[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)]
|
||||
pub enum ToolbarAction {
|
||||
LocationUp,
|
||||
Reload,
|
||||
NewFolder,
|
||||
NewFile,
|
||||
Rename,
|
||||
Delete,
|
||||
Cut,
|
||||
Copy,
|
||||
Paste,
|
||||
ToggleShowHidden,
|
||||
OpenTerminal,
|
||||
}
|
||||
|
||||
impl ToolbarAction {
|
||||
/// Stable list of every supported action. Ordered roughly by logical
|
||||
/// grouping (location → create/edit → clipboard → view/misc) so that
|
||||
/// the default enabled set follows a sensible shape and the Settings
|
||||
/// row for a not-yet-enabled action lands in a predictable spot.
|
||||
pub const ALL: &'static [Self] = &[
|
||||
Self::LocationUp,
|
||||
Self::Reload,
|
||||
Self::NewFolder,
|
||||
Self::NewFile,
|
||||
Self::Rename,
|
||||
Self::Delete,
|
||||
Self::Cut,
|
||||
Self::Copy,
|
||||
Self::Paste,
|
||||
Self::ToggleShowHidden,
|
||||
Self::OpenTerminal,
|
||||
];
|
||||
|
||||
/// u8 discriminant used to carry the action over a DnD mime payload.
|
||||
pub const fn to_u8(self) -> u8 {
|
||||
match self {
|
||||
Self::LocationUp => 0,
|
||||
Self::Reload => 1,
|
||||
Self::NewFolder => 2,
|
||||
Self::NewFile => 3,
|
||||
Self::Rename => 4,
|
||||
Self::Delete => 5,
|
||||
Self::Cut => 6,
|
||||
Self::Copy => 7,
|
||||
Self::Paste => 8,
|
||||
Self::ToggleShowHidden => 9,
|
||||
Self::OpenTerminal => 10,
|
||||
}
|
||||
}
|
||||
|
||||
pub const fn from_u8(v: u8) -> Option<Self> {
|
||||
match v {
|
||||
0 => Some(Self::LocationUp),
|
||||
1 => Some(Self::Reload),
|
||||
2 => Some(Self::NewFolder),
|
||||
3 => Some(Self::NewFile),
|
||||
4 => Some(Self::Rename),
|
||||
5 => Some(Self::Delete),
|
||||
6 => Some(Self::Cut),
|
||||
7 => Some(Self::Copy),
|
||||
8 => Some(Self::Paste),
|
||||
9 => Some(Self::ToggleShowHidden),
|
||||
10 => Some(Self::OpenTerminal),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Default set shown on a fresh install — same "minimal 6" as phase 1/2.
|
||||
pub fn default_toolbar() -> Vec<ToolbarAction> {
|
||||
vec![
|
||||
ToolbarAction::NewFolder,
|
||||
ToolbarAction::Rename,
|
||||
ToolbarAction::Delete,
|
||||
ToolbarAction::Cut,
|
||||
ToolbarAction::Copy,
|
||||
ToolbarAction::Paste,
|
||||
]
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, CosmicConfigEntry, Deserialize, Serialize)]
|
||||
#[serde(default)]
|
||||
pub struct DesktopConfig {
|
||||
|
|
|
|||
|
|
@ -1,80 +0,0 @@
|
|||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
use std::path::PathBuf;
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::mime_app;
|
||||
use crate::spawn_detached::spawn_detached;
|
||||
|
||||
#[derive(Clone, Copy, Debug, Default, Deserialize, Eq, PartialEq, Serialize)]
|
||||
pub enum ContextActionSelection {
|
||||
#[default]
|
||||
#[serde(alias = "any")]
|
||||
Any,
|
||||
#[serde(alias = "files")]
|
||||
Files,
|
||||
#[serde(alias = "folders")]
|
||||
Folders,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)]
|
||||
#[serde(default)]
|
||||
pub struct ContextActionPreset {
|
||||
pub name: String,
|
||||
pub confirm: bool,
|
||||
pub selection: ContextActionSelection,
|
||||
pub steps: Vec<String>,
|
||||
}
|
||||
|
||||
impl ContextActionPreset {
|
||||
pub fn matches_selection(&self, selected: usize, selected_dir: usize) -> bool {
|
||||
if selected == 0 {
|
||||
return false;
|
||||
}
|
||||
|
||||
match self.selection {
|
||||
ContextActionSelection::Any => true,
|
||||
ContextActionSelection::Files => selected_dir == 0,
|
||||
ContextActionSelection::Folders => selected_dir == selected,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn run(&self, paths: &[PathBuf]) {
|
||||
if self.steps.is_empty() {
|
||||
log::warn!("context action {:?} has no steps", self.name);
|
||||
return;
|
||||
}
|
||||
|
||||
for step in &self.steps {
|
||||
let Some(commands) = mime_app::exec_to_command(step, &self.name, None, paths) else {
|
||||
log::warn!(
|
||||
"failed to parse context action {:?}: invalid Exec {:?}",
|
||||
self.name,
|
||||
step
|
||||
);
|
||||
return;
|
||||
};
|
||||
|
||||
for mut command in commands {
|
||||
if let Err(err) = spawn_detached(&mut command) {
|
||||
log::warn!(
|
||||
"failed to run context action {:?} step {:?}: {}",
|
||||
self.name,
|
||||
step,
|
||||
err
|
||||
);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn run(actions: &[ContextActionPreset], action: usize, paths: &[PathBuf]) {
|
||||
if let Some(preset) = actions.get(action) {
|
||||
preset.run(paths);
|
||||
} else {
|
||||
log::warn!("invalid context action index `{action}`");
|
||||
}
|
||||
}
|
||||
150
src/dialog.rs
150
src/dialog.rs
|
|
@ -1,46 +1,58 @@
|
|||
// Copyright 2023 System76 <info@system76.com>
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
use cosmic::app::cosmic::Cosmic;
|
||||
use cosmic::app::{Core, Task, context_drawer};
|
||||
use cosmic::iced::core::SmolStr;
|
||||
use cosmic::iced::core::widget::operation;
|
||||
use cosmic::iced::futures::{self, SinkExt};
|
||||
use cosmic::iced::keyboard::key::Named;
|
||||
use cosmic::iced::keyboard::{Event as KeyEvent, Key, Modifiers};
|
||||
use cosmic::iced::platform_specific::shell::{self as iced_winit, SurfaceIdWrapper};
|
||||
use cosmic::iced::widget::scrollable;
|
||||
use cosmic::iced::widget::scrollable::AbsoluteOffset;
|
||||
use cosmic::iced::{
|
||||
self, Alignment, Event, Length, Size, Subscription, event, mouse, stream, window,
|
||||
use cosmic::{
|
||||
Application, ApplicationExt, Element,
|
||||
app::{Core, Task, context_drawer, cosmic::Cosmic},
|
||||
cosmic_config, cosmic_theme, executor,
|
||||
iced::{
|
||||
self, Alignment, Event, Length, Size, Subscription,
|
||||
core::SmolStr,
|
||||
event,
|
||||
futures::{self, SinkExt},
|
||||
keyboard::{Event as KeyEvent, Key, Modifiers, key::Named},
|
||||
mouse, stream,
|
||||
widget::scrollable,
|
||||
window,
|
||||
},
|
||||
iced_core::widget::operation,
|
||||
iced_widget::scrollable::AbsoluteOffset,
|
||||
iced_winit::{self, SurfaceIdWrapper},
|
||||
theme,
|
||||
widget::{
|
||||
self, Operation,
|
||||
menu::{Action as MenuAction, KeyBind, key_bind::Modifier},
|
||||
segmented_button,
|
||||
},
|
||||
};
|
||||
use cosmic::widget::menu::key_bind::Modifier;
|
||||
use cosmic::widget::menu::{Action as MenuAction, KeyBind};
|
||||
use cosmic::widget::{self, Operation, segmented_button};
|
||||
use cosmic::{Application, ApplicationExt, Element, cosmic_config, cosmic_theme, executor, theme};
|
||||
use mime_guess::{Mime, mime};
|
||||
use notify_debouncer_full::notify::{self, RecommendedWatcher};
|
||||
use notify_debouncer_full::{DebouncedEvent, Debouncer, RecommendedCache, new_debouncer};
|
||||
use notify_debouncer_full::{
|
||||
DebouncedEvent, Debouncer, RecommendedCache, new_debouncer,
|
||||
notify::{self, RecommendedWatcher},
|
||||
};
|
||||
use recently_used_xbel::update_recently_used;
|
||||
use rustc_hash::{FxHashMap, FxHashSet};
|
||||
use std::any::TypeId;
|
||||
use std::collections::{HashMap, VecDeque};
|
||||
use std::path::PathBuf;
|
||||
use std::time::{self, Instant};
|
||||
use std::{env, fmt, fs};
|
||||
use std::{
|
||||
any::TypeId,
|
||||
collections::{HashMap, VecDeque},
|
||||
env, fmt, fs,
|
||||
path::PathBuf,
|
||||
time::{self, Instant},
|
||||
};
|
||||
|
||||
use crate::app::{
|
||||
Action, ContextPage, Message as AppMessage, PreviewItem, PreviewKind, REPLACE_BUTTON_ID,
|
||||
use crate::{
|
||||
app::{
|
||||
Action, ContextPage, Message as AppMessage, PreviewItem, PreviewKind, REPLACE_BUTTON_ID,
|
||||
},
|
||||
config::{Config, DialogConfig, Favorite, TIME_CONFIG_ID, ThumbCfg, TimeConfig, TypeToSearch},
|
||||
fl, home_dir,
|
||||
key_bind::key_binds,
|
||||
localize::LANGUAGE_SORTER,
|
||||
menu,
|
||||
mounter::{MOUNTERS, MounterItem, MounterItems, MounterKey, MounterMessage},
|
||||
tab::{self, ItemMetadata, Location, SearchLocation, Tab},
|
||||
zoom::{zoom_in_view, zoom_out_view, zoom_to_default},
|
||||
};
|
||||
use crate::config::{
|
||||
Config, DialogConfig, Favorite, TIME_CONFIG_ID, ThumbCfg, TimeConfig, TypeToSearch,
|
||||
};
|
||||
use crate::key_bind::key_binds;
|
||||
use crate::localize::LANGUAGE_SORTER;
|
||||
use crate::mounter::{MOUNTERS, MounterItem, MounterItems, MounterKey, MounterMessage};
|
||||
use crate::tab::{self, ItemMetadata, Location, SearchLocation, Tab};
|
||||
use crate::zoom::{zoom_in_view, zoom_out_view, zoom_to_default};
|
||||
use crate::{fl, home_dir, menu};
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct DialogMessage(cosmic::Action<Message>);
|
||||
|
|
@ -191,7 +203,7 @@ impl<T: AsRef<str>> From<T> for DialogLabel {
|
|||
|
||||
impl<'a, M: Clone + 'static> From<&'a DialogLabel> for Element<'a, M> {
|
||||
fn from(label: &'a DialogLabel) -> Self {
|
||||
let mut iced_spans: Vec<cosmic::iced::core::text::Span<'_, ()>> =
|
||||
let mut iced_spans: Vec<cosmic::iced_core::text::Span<'_, ()>> =
|
||||
Vec::with_capacity(label.spans.len());
|
||||
for span in &label.spans {
|
||||
iced_spans.push(cosmic::iced::widget::span(&span.text).underline(span.underline));
|
||||
|
|
@ -475,7 +487,7 @@ enum Message {
|
|||
TabMessage(tab::Message),
|
||||
TabRescan(
|
||||
Location,
|
||||
Option<Box<tab::Item>>,
|
||||
Option<tab::Item>,
|
||||
Vec<tab::Item>,
|
||||
Option<Vec<PathBuf>>,
|
||||
),
|
||||
|
|
@ -575,7 +587,7 @@ impl App {
|
|||
space_s,
|
||||
space_l,
|
||||
..
|
||||
} = theme::spacing();
|
||||
} = theme::active().cosmic().spacing;
|
||||
let is_condensed = self.core().is_condensed();
|
||||
|
||||
let mut col = widget::column::with_capacity(2).spacing(space_xxs);
|
||||
|
|
@ -583,7 +595,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)),
|
||||
);
|
||||
|
|
@ -706,7 +717,7 @@ impl App {
|
|||
|
||||
match (selected.next(), selected.next()) {
|
||||
// At least two selected items
|
||||
(Some(_), Some(_)) => Some(self.tab.multi_preview_view(None)),
|
||||
(Some(_), Some(_)) => Some(self.tab.multi_preview_view()),
|
||||
// Exactly one selected item
|
||||
(Some(item), None) => Some(item.preview_view(None, military_time)),
|
||||
// No selected items
|
||||
|
|
@ -732,17 +743,11 @@ impl App {
|
|||
fn rescan_tab(&self, selection_paths: Option<Vec<PathBuf>>) -> Task<Message> {
|
||||
let location = self.tab.location.clone();
|
||||
let icon_sizes = self.tab.config.icon_sizes;
|
||||
#[cfg(feature = "gvfs")]
|
||||
let mounter_items = self.mounter_items.clone();
|
||||
Task::future(async move {
|
||||
let location2 = location.clone();
|
||||
match tokio::task::spawn_blocking(move || location2.scan(icon_sizes)).await {
|
||||
Ok((parent_item_opt, items)) => {
|
||||
#[cfg(feature = "gvfs")]
|
||||
let mut items = items;
|
||||
#[cfg(not(feature = "gvfs"))]
|
||||
let items = items;
|
||||
|
||||
Ok((parent_item_opt, mut items)) => {
|
||||
#[cfg(feature = "gvfs")]
|
||||
{
|
||||
let mounter_paths: Box<[_]> = mounter_items
|
||||
|
|
@ -793,7 +798,7 @@ impl App {
|
|||
};
|
||||
|
||||
search_location.map(|search_location| {
|
||||
(
|
||||
return (
|
||||
Location::Search(
|
||||
search_location,
|
||||
term,
|
||||
|
|
@ -801,7 +806,7 @@ impl App {
|
|||
Instant::now(),
|
||||
),
|
||||
true,
|
||||
)
|
||||
);
|
||||
})
|
||||
}
|
||||
None => match &self.tab.location {
|
||||
|
|
@ -831,10 +836,9 @@ impl App {
|
|||
|
||||
fn update_config(&mut self) -> Task<Message> {
|
||||
self.core.window.show_context = self.flags.config.dialog.show_details;
|
||||
let config = self.flags.config.dialog_tab();
|
||||
self.tab.config.view = config.view;
|
||||
self.tab.config = self.flags.config.dialog_tab();
|
||||
self.update_nav_model();
|
||||
self.update(Message::TabMessage(tab::Message::Config(config)))
|
||||
self.update(Message::TabMessage(tab::Message::Config(self.tab.config)))
|
||||
}
|
||||
|
||||
fn with_dialog_config<F: Fn(&mut DialogConfig)>(&mut self, f: F) -> Task<Message> {
|
||||
|
|
@ -885,20 +889,16 @@ impl App {
|
|||
fn update_nav_model(&mut self) {
|
||||
let mut nav_model = segmented_button::ModelBuilder::default();
|
||||
|
||||
if self.flags.config.show_recents {
|
||||
nav_model = nav_model.insert(|b| {
|
||||
b.text(fl!("recents"))
|
||||
.icon(widget::icon::from_name("document-open-recent-symbolic"))
|
||||
.data(Location::Recents)
|
||||
});
|
||||
}
|
||||
nav_model = nav_model.insert(|b| {
|
||||
b.text(fl!("recents"))
|
||||
.icon(widget::icon::from_name("document-open-recent-symbolic"))
|
||||
.data(Location::Recents)
|
||||
});
|
||||
|
||||
for favorite in &self.flags.config.favorites {
|
||||
if let Some(path) = favorite.path_opt() {
|
||||
let name = if matches!(favorite, Favorite::Home) {
|
||||
fl!("home")
|
||||
} else if let Favorite::Network { name, .. } = favorite {
|
||||
name.clone()
|
||||
} else if let Some(file_name) = path.file_name().and_then(|x| x.to_str()) {
|
||||
file_name.to_string()
|
||||
} else {
|
||||
|
|
@ -1122,7 +1122,7 @@ impl Application for App {
|
|||
}
|
||||
|
||||
fn dialog(&self) -> Option<Element<'_, Message>> {
|
||||
let cosmic_theme::Spacing { space_xxs, .. } = theme::spacing();
|
||||
let cosmic_theme::Spacing { space_xxs, .. } = theme::active().cosmic().spacing;
|
||||
|
||||
//TODO: should gallery view just be a dialog?
|
||||
if self.tab.gallery {
|
||||
|
|
@ -1206,9 +1206,7 @@ impl Application for App {
|
|||
.icon(widget::icon::from_name("dialog-question").size(64))
|
||||
.body(fl!("replace-warning"))
|
||||
.primary_action(
|
||||
widget::button::suggested(fl!("replace"))
|
||||
.on_press(Message::DialogComplete)
|
||||
.id(REPLACE_BUTTON_ID.clone()),
|
||||
widget::button::suggested(fl!("replace")).on_press(Message::DialogComplete),
|
||||
)
|
||||
.secondary_action(
|
||||
widget::button::standard(fl!("cancel")).on_press(Message::DialogCancel),
|
||||
|
|
@ -1400,10 +1398,7 @@ impl Application for App {
|
|||
Message::Config(config) => {
|
||||
if config != self.flags.config {
|
||||
log::info!("update config");
|
||||
// Don't overwrite military time
|
||||
let military_time = self.flags.config.tab.military_time;
|
||||
self.flags.config = config;
|
||||
self.flags.config.tab.military_time = military_time;
|
||||
return self.update_config();
|
||||
}
|
||||
}
|
||||
|
|
@ -1458,14 +1453,14 @@ impl Application for App {
|
|||
}
|
||||
Message::Key(modifiers, key, text) => {
|
||||
for (key_bind, action) in &self.key_binds {
|
||||
if key_bind.matches(modifiers, &key, None) {
|
||||
if key_bind.matches(modifiers, &key) {
|
||||
return self.update(Message::from(action.message()));
|
||||
}
|
||||
}
|
||||
|
||||
// Check key binds from accept label
|
||||
if let Some(key_bind) = &self.accept_label.key_bind_opt
|
||||
&& key_bind.matches(modifiers, &key, None)
|
||||
&& key_bind.matches(modifiers, &key)
|
||||
{
|
||||
return self.update(if self.flags.kind.save() {
|
||||
Message::Save(false)
|
||||
|
|
@ -1792,7 +1787,7 @@ impl Application for App {
|
|||
use cctk::wayland_protocols::xdg::shell::client::xdg_positioner::{
|
||||
Anchor, Gravity,
|
||||
};
|
||||
use cosmic::iced::runtime::platform_specific::wayland::popup::{
|
||||
use cosmic::iced_runtime::platform_specific::wayland::popup::{
|
||||
SctkPopupSettings, SctkPositioner,
|
||||
};
|
||||
use cosmic::iced::Rectangle;
|
||||
|
|
@ -1834,7 +1829,6 @@ impl Application for App {
|
|||
&app.key_binds,
|
||||
&app.modifiers,
|
||||
false, // Paste not used in dialogs
|
||||
&app.flags.config.context_actions,
|
||||
)
|
||||
.map(Message::TabMessage)
|
||||
.map(cosmic::Action::App),
|
||||
|
|
@ -1958,16 +1952,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());
|
||||
}
|
||||
}
|
||||
|
|
@ -2017,7 +2001,7 @@ impl Application for App {
|
|||
|
||||
/// Creates a view after each update.
|
||||
fn view(&self) -> Element<'_, Message> {
|
||||
let cosmic_theme::Spacing { space_xxs, .. } = theme::spacing();
|
||||
let cosmic_theme::Spacing { space_xxs, .. } = theme::active().cosmic().spacing;
|
||||
|
||||
let mut col = widget::column::with_capacity(2);
|
||||
|
||||
|
|
@ -2038,7 +2022,7 @@ impl Application for App {
|
|||
|
||||
col = col.push(
|
||||
self.tab
|
||||
.view(&self.key_binds, &self.modifiers, false, &[])
|
||||
.view(&self.key_binds, &self.modifiers, false)
|
||||
.map(Message::TabMessage),
|
||||
);
|
||||
|
||||
|
|
|
|||
|
|
@ -1,10 +1,11 @@
|
|||
use cosmic::iced::core::keyboard::key::Named;
|
||||
use cosmic::iced::keyboard::Key;
|
||||
use cosmic::widget::menu::key_bind::{KeyBind, Modifier};
|
||||
use cosmic::{
|
||||
iced::keyboard::Key,
|
||||
iced_core::keyboard::key::Named,
|
||||
widget::menu::key_bind::{KeyBind, Modifier},
|
||||
};
|
||||
use std::collections::HashMap;
|
||||
|
||||
use crate::app::Action;
|
||||
use crate::tab;
|
||||
use crate::{app::Action, tab};
|
||||
|
||||
//TODO: load from config
|
||||
pub fn key_binds(mode: &tab::Mode) -> HashMap<KeyBind, Action> {
|
||||
|
|
|
|||
|
|
@ -1,7 +1,9 @@
|
|||
use cosmic::widget;
|
||||
use image::ImageReader;
|
||||
use std::collections::{HashMap, HashSet};
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::{
|
||||
collections::{HashMap, HashSet},
|
||||
path::{Path, PathBuf},
|
||||
};
|
||||
|
||||
/// Bytes per pixel in RGBA format (Red, Green, Blue, Alpha = 4 bytes)
|
||||
pub const RGBA_BYTES_PER_PIXEL: u64 = 4;
|
||||
|
|
|
|||
58
src/lib.rs
58
src/lib.rs
|
|
@ -1,23 +1,15 @@
|
|||
// Copyright 2023 System76 <info@system76.com>
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
use cosmic::app::Settings;
|
||||
use cosmic::iced::Limits;
|
||||
use std::path::PathBuf;
|
||||
use std::{env, fs, process};
|
||||
use tracing_subscriber::layer::SubscriberExt;
|
||||
use tracing_subscriber::util::SubscriberInitExt;
|
||||
|
||||
use crate::app::{App, Flags};
|
||||
use crate::config::{Config, State};
|
||||
use crate::tab::Location;
|
||||
use cosmic::{app::Settings, iced::Limits};
|
||||
use std::{env, fs, path::PathBuf, process};
|
||||
|
||||
use app::{App, Flags};
|
||||
pub mod app;
|
||||
mod archive;
|
||||
pub mod channel;
|
||||
pub mod clipboard;
|
||||
use config::Config;
|
||||
pub mod config;
|
||||
mod context_action;
|
||||
pub mod dialog;
|
||||
mod key_bind;
|
||||
pub(crate) mod large_image;
|
||||
|
|
@ -30,15 +22,16 @@ 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>;
|
||||
|
||||
#[cfg(feature = "gvfs")]
|
||||
pub(crate) fn err_str<T: ToString>(err: T) -> String {
|
||||
err.to_string()
|
||||
}
|
||||
|
|
@ -79,22 +72,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();
|
||||
|
||||
|
|
@ -129,21 +107,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();
|
||||
|
||||
|
|
@ -193,7 +157,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),
|
||||
|
|
|
|||
|
|
@ -1,10 +1,11 @@
|
|||
use cosmic::iced::{core as iced_core, widget as iced_widget};
|
||||
use cosmic::{iced_core, iced_widget};
|
||||
use iced_core::event::Event;
|
||||
use iced_core::layout;
|
||||
use iced_core::mouse;
|
||||
use iced_core::overlay;
|
||||
use iced_core::renderer;
|
||||
use iced_core::widget::{Operation, Tree};
|
||||
use iced_core::{
|
||||
Clipboard, Element, Layout, Length, Rectangle, Shell, Vector, Widget, layout, mouse, overlay,
|
||||
renderer,
|
||||
};
|
||||
use iced_core::{Clipboard, Element, Layout, Length, Rectangle, Shell, Vector, Widget};
|
||||
|
||||
pub fn loaded_image<'a, Message: 'static, Theme>(
|
||||
handle: <cosmic::Renderer as iced_core::image::Renderer>::Handle,
|
||||
|
|
|
|||
|
|
@ -1,10 +1,13 @@
|
|||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
use i18n_embed::fluent::{FluentLanguageLoader, fluent_language_loader};
|
||||
use i18n_embed::{DefaultLocalizer, LanguageLoader, Localizer};
|
||||
use icu::collator::options::CollatorOptions;
|
||||
use icu::collator::preferences::CollationNumericOrdering;
|
||||
use icu::collator::{Collator, CollatorBorrowed, CollatorPreferences};
|
||||
use i18n_embed::{
|
||||
DefaultLocalizer, LanguageLoader, Localizer,
|
||||
fluent::{FluentLanguageLoader, fluent_language_loader},
|
||||
};
|
||||
use icu::collator::{
|
||||
Collator, CollatorBorrowed, CollatorPreferences, options::CollatorOptions,
|
||||
preferences::CollationNumericOrdering,
|
||||
};
|
||||
use icu::locale::Locale;
|
||||
use rust_embed::RustEmbed;
|
||||
use std::sync::LazyLock;
|
||||
|
|
|
|||
|
|
@ -6,6 +6,5 @@ use tikv_jemallocator::Jemalloc;
|
|||
static GLOBAL: Jemalloc = Jemalloc;
|
||||
|
||||
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
let _ = jxl_oxide::integration::register_image_decoding_hook();
|
||||
cosmic_files::main()
|
||||
}
|
||||
|
|
|
|||
79
src/menu.rs
79
src/menu.rs
|
|
@ -1,26 +1,29 @@
|
|||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
use cosmic::app::Core;
|
||||
use cosmic::iced::advanced::widget::text::Style as TextStyle;
|
||||
use cosmic::iced::keyboard::Modifiers;
|
||||
use cosmic::iced::{Alignment, Background, Border, Length};
|
||||
use cosmic::widget::menu::key_bind::KeyBind;
|
||||
use cosmic::widget::menu::{self, ItemHeight, ItemWidth, MenuBar};
|
||||
use cosmic::widget::{
|
||||
self, Row, button, column, container, divider, responsive_menu_bar, space, text,
|
||||
use cosmic::{
|
||||
Element,
|
||||
app::Core,
|
||||
iced::{
|
||||
Alignment, Background, Border, Length, advanced::widget::text::Style as TextStyle,
|
||||
keyboard::Modifiers,
|
||||
},
|
||||
theme,
|
||||
widget::{
|
||||
self, Row, button, column, container, divider,
|
||||
menu::{self, ItemHeight, ItemWidth, MenuBar, key_bind::KeyBind},
|
||||
responsive_menu_bar, space, text,
|
||||
},
|
||||
};
|
||||
use cosmic::{Element, theme};
|
||||
#[cfg(feature = "desktop")]
|
||||
use i18n_embed::LanguageLoader;
|
||||
use mime_guess::Mime;
|
||||
use std::collections::HashMap;
|
||||
use std::sync::LazyLock;
|
||||
use std::{collections::HashMap, sync::LazyLock};
|
||||
|
||||
use crate::app::{Action, Message};
|
||||
use crate::config::{Config, ContextActionPreset};
|
||||
use crate::fl;
|
||||
use crate::tab::{self, HeadingOptions, Location, LocationMenuAction, SearchLocation, Tab};
|
||||
use crate::trash::{Trash, TrashExt};
|
||||
use crate::{
|
||||
app::{Action, Message},
|
||||
config::Config,
|
||||
fl,
|
||||
tab::{self, HeadingOptions, Location, LocationMenuAction, SearchLocation, Tab},
|
||||
};
|
||||
|
||||
static MENU_ID: LazyLock<cosmic::widget::Id> =
|
||||
LazyLock::new(|| cosmic::widget::Id::new("responsive-menu"));
|
||||
|
|
@ -34,7 +37,7 @@ macro_rules! menu_button {
|
|||
.height(Length::Fixed(24.0))
|
||||
.align_y(Alignment::Center)
|
||||
)
|
||||
.padding([theme::spacing().space_xxs, 16])
|
||||
.padding([theme::active().cosmic().spacing.space_xxs, 16])
|
||||
.width(Length::Fill)
|
||||
.class(theme::Button::MenuItem)
|
||||
);
|
||||
|
|
@ -57,7 +60,6 @@ pub fn context_menu<'a>(
|
|||
key_binds: &HashMap<KeyBind, Action>,
|
||||
modifiers: &Modifiers,
|
||||
clipboard_paste_available: bool,
|
||||
context_actions: &[ContextActionPreset],
|
||||
) -> Element<'a, tab::Message> {
|
||||
let find_key = |action: &Action| -> String {
|
||||
for (key_bind, key_action) in key_binds {
|
||||
|
|
@ -139,11 +141,12 @@ pub fn context_menu<'a>(
|
|||
Some(Location::Trash) | Some(Location::Search(SearchLocation::Trash, ..)) => {
|
||||
selected_trash_only = true
|
||||
}
|
||||
Some(Location::Path(path))
|
||||
Some(Location::Path(path)) => {
|
||||
if selected == 1
|
||||
&& path.extension().and_then(|s| s.to_str()) == Some("desktop") =>
|
||||
{
|
||||
selected_desktop_entry = Some(&**path);
|
||||
&& path.extension().and_then(|s| s.to_str()) == Some("desktop")
|
||||
{
|
||||
selected_desktop_entry = Some(&**path);
|
||||
}
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
|
|
@ -154,14 +157,6 @@ pub fn context_menu<'a>(
|
|||
selected_types.sort_unstable();
|
||||
selected_types.dedup();
|
||||
selected_trash_only = selected_trash_only && selected == 1;
|
||||
let context_action_items = |selected: usize, selected_dir: usize| {
|
||||
context_actions
|
||||
.iter()
|
||||
.enumerate()
|
||||
.filter(|(_, action)| action.matches_selection(selected, selected_dir))
|
||||
.map(|(i, action)| menu_item(action.name.clone(), Action::RunContextAction(i)).into())
|
||||
.collect::<Vec<Element<'a, tab::Message>>>()
|
||||
};
|
||||
// Parse the desktop entry if it is the only selection
|
||||
#[cfg(feature = "desktop")]
|
||||
let selected_desktop_entry = selected_desktop_entry.and_then(|path| {
|
||||
|
|
@ -188,14 +183,14 @@ 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 {
|
||||
} else if let Some(entry) = selected_desktop_entry {
|
||||
children.push(menu_item(fl!("open"), Action::Open).into());
|
||||
#[cfg(feature = "desktop")]
|
||||
{
|
||||
children.extend(_entry.desktop_actions.into_iter().enumerate().map(
|
||||
children.extend(entry.desktop_actions.into_iter().enumerate().map(
|
||||
|(i, action)| menu_item(action.name, Action::ExecEntryAction(i)).into(),
|
||||
));
|
||||
}
|
||||
|
|
@ -209,11 +204,6 @@ pub fn context_menu<'a>(
|
|||
}
|
||||
// Should this simply bypass trash and remove the shortcut?
|
||||
children.push(menu_item(fl!("move-to-trash"), Action::Delete).into());
|
||||
let action_items = context_action_items(selected, selected_dir);
|
||||
if !action_items.is_empty() {
|
||||
children.push(divider::horizontal::light().into());
|
||||
children.extend(action_items);
|
||||
}
|
||||
} else if selected > 0 {
|
||||
if selected_dir == 1 && selected == 1 || selected_dir == 0 {
|
||||
children.push(menu_item(fl!("open"), Action::Open).into());
|
||||
|
|
@ -225,7 +215,7 @@ pub fn context_menu<'a>(
|
|||
.push(menu_item(fl!("open-in-terminal"), Action::OpenTerminal).into());
|
||||
}
|
||||
}
|
||||
if tab.location.is_recents() || matches!(tab.location, Location::Search(..)) {
|
||||
if tab.location.is_recents() {
|
||||
children.push(
|
||||
menu_item(fl!("open-item-location"), Action::OpenItemLocation).into(),
|
||||
);
|
||||
|
|
@ -236,11 +226,6 @@ pub fn context_menu<'a>(
|
|||
children
|
||||
.push(menu_item(fl!("open-in-new-window"), Action::OpenInNewWindow).into());
|
||||
}
|
||||
let action_items = context_action_items(selected, selected_dir);
|
||||
if !action_items.is_empty() {
|
||||
children.push(divider::horizontal::light().into());
|
||||
children.extend(action_items);
|
||||
}
|
||||
children.push(divider::horizontal::light().into());
|
||||
if selected_mount_point == 0 {
|
||||
children.push(menu_item(fl!("rename"), Action::Rename).into());
|
||||
|
|
@ -574,7 +559,7 @@ pub fn dialog_menu(
|
|||
])
|
||||
.item_height(ItemHeight::Dynamic(40))
|
||||
.item_width(ItemWidth::Uniform(360))
|
||||
.spacing(theme::spacing().space_xxxs.into())
|
||||
.spacing(theme::active().cosmic().spacing.space_xxxs.into())
|
||||
.into()
|
||||
}
|
||||
|
||||
|
|
@ -629,7 +614,7 @@ pub fn menu_bar<'a>(
|
|||
responsive_menu_bar()
|
||||
.item_height(ItemHeight::Dynamic(40))
|
||||
.item_width(ItemWidth::Uniform(360))
|
||||
.spacing(theme::spacing().space_xxxs.into())
|
||||
.spacing(theme::active().cosmic().spacing.space_xxxs.into())
|
||||
.into_element(
|
||||
core,
|
||||
key_binds,
|
||||
|
|
|
|||
317
src/mime_app.rs
317
src/mime_app.rs
|
|
@ -1,123 +1,155 @@
|
|||
// Copyright 2023 System76 <info@system76.com>
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
use bstr::{BString, ByteSlice, ByteVec};
|
||||
#[cfg(feature = "desktop")]
|
||||
use cosmic::desktop;
|
||||
use cosmic::widget;
|
||||
pub use mime_guess::Mime;
|
||||
use rustc_hash::FxHashMap;
|
||||
#[cfg(feature = "desktop")]
|
||||
use std::{cmp::Ordering, fs, io, time::Instant};
|
||||
use std::{
|
||||
cmp::Ordering,
|
||||
ffi::OsStr,
|
||||
os::unix::ffi::OsStrExt,
|
||||
fs, io,
|
||||
path::{Path, PathBuf},
|
||||
process,
|
||||
time::Instant,
|
||||
};
|
||||
|
||||
// Supported exec key field codes
|
||||
const EXEC_HANDLERS: [&str; 4] = ["%f", "%F", "%u", "%U"];
|
||||
// Deprecated field codes. The spec advises to ignore these handlers.
|
||||
const DEPRECATED_HANDLERS: [&str; 6] = ["%d", "%D", "%n", "%N", "%v", "%m"];
|
||||
|
||||
pub fn exec_to_command(
|
||||
exec: &str,
|
||||
entry_name: &str,
|
||||
entry_path: Option<&Path>,
|
||||
path_opt: &[impl AsRef<OsStr>],
|
||||
) -> Option<Vec<process::Command>> {
|
||||
let arguments = shlex::split(exec)?;
|
||||
let args_vec = shlex::split(exec)?;
|
||||
let program = args_vec.first()?;
|
||||
// Skip program to make indexing easier
|
||||
let args_vec = &args_vec[1..];
|
||||
|
||||
if arguments.is_empty() {
|
||||
tracing::error!("command does not contain any arguments");
|
||||
return None;
|
||||
}
|
||||
|
||||
let mut commands = Vec::new();
|
||||
|
||||
let paths = path_opt
|
||||
// Base Command instance(s)
|
||||
// 1. We may need to launch multiple of the same process.
|
||||
// 2. Each of those processes will need to be passed args from exec.
|
||||
// 3. Each of those args may appear in any order.
|
||||
// 4. Arg order should be preserved.
|
||||
//
|
||||
// So, we'll go through exec in two passes. The first pass handles paths (%f etc) and args up
|
||||
// to the field code followed by the second which passes extra, non-% args to each processes.
|
||||
//
|
||||
// While it'd be marginally faster to process everything in one pass, that's problematic:
|
||||
// 1. path_opt may need to be cloned because it may be moved on each iteration (borrowck
|
||||
// doesn't know we'll only use it once)
|
||||
// 2. We have to keep track of which modifier (%f etc) we've used/seen already
|
||||
// 3. We have to keep track of which processes received non-modifier args which gets messy fast
|
||||
// 4. `exec` is likely small so looping over it twice is not a big deal
|
||||
let field_code_pos = args_vec
|
||||
.iter()
|
||||
.map(AsRef::as_ref)
|
||||
.map(Some)
|
||||
// Add a single `None` if no path was given.
|
||||
.chain(std::iter::repeat_n(
|
||||
None,
|
||||
if path_opt.is_empty() { 1 } else { 0 },
|
||||
));
|
||||
.position(|arg| EXEC_HANDLERS.contains(&arg.as_str()));
|
||||
let args_handler = field_code_pos.and_then(|i| args_vec.get(i));
|
||||
// msrv
|
||||
// .inspect(|handler| log::trace!("Found paths handler: {handler} for exec: {exec}"));
|
||||
// Number of args before the field code.
|
||||
// This won't be an off by one err below because take is not zero indexed.
|
||||
let field_code_pos = field_code_pos.unwrap_or_default();
|
||||
let mut processes = match args_handler.map(String::as_str) {
|
||||
Some("%f") => {
|
||||
let mut processes = Vec::with_capacity(path_opt.len());
|
||||
|
||||
for path in paths {
|
||||
let mut batch_process = false;
|
||||
let mut args = Vec::with_capacity(arguments.len());
|
||||
let mut field_code_used = false;
|
||||
|
||||
for argument in arguments.iter().skip(1) {
|
||||
let mut new_argument = BString::new(Vec::with_capacity(argument.capacity()));
|
||||
let mut chars = argument.chars();
|
||||
while let Some(char) = chars.next() {
|
||||
// https://specifications.freedesktop.org/desktop-entry/latest/exec-variables.html
|
||||
if char == '%' {
|
||||
match chars.next() {
|
||||
Some('%') => new_argument.push_char(char),
|
||||
Some('c') => new_argument.push_str(entry_name),
|
||||
Some('k') => {
|
||||
if let Some(path) = entry_path {
|
||||
new_argument.push_str(path.as_os_str().as_bytes());
|
||||
}
|
||||
}
|
||||
|
||||
// %f and %u behave the same in a file manager.
|
||||
Some('f' | 'u') => {
|
||||
if let Some(path) = path
|
||||
&& !field_code_used
|
||||
{
|
||||
// TODO: files on remote file systems should be copied to a temporary local file.
|
||||
batch_process = true;
|
||||
field_code_used = true;
|
||||
new_argument.push_str(path.as_bytes());
|
||||
}
|
||||
}
|
||||
|
||||
// %F and %U behave the same in a file manager.
|
||||
Some('F') | Some('U') => {
|
||||
if !field_code_used && new_argument.is_empty() {
|
||||
field_code_used = true;
|
||||
for path in path_opt.iter().map(AsRef::as_ref) {
|
||||
args.push(BString::new(path.as_bytes().to_owned()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_ => (),
|
||||
}
|
||||
} else {
|
||||
new_argument.push_char(char);
|
||||
for path in path_opt.iter().map(AsRef::as_ref) {
|
||||
// TODO: %f and %F need to handle non-file URLs (see spec)
|
||||
if from_file_or_dir(path).is_none() {
|
||||
log::warn!("Desktop file expects a file path instead of a URL: {path:?}");
|
||||
}
|
||||
|
||||
// Passing multiple paths to %f should open an instance per path
|
||||
let mut process = process::Command::new(program);
|
||||
process.args(
|
||||
args_vec
|
||||
.iter()
|
||||
.map(AsRef::as_ref)
|
||||
.take(field_code_pos)
|
||||
.chain(std::iter::once(path)),
|
||||
);
|
||||
processes.push(process);
|
||||
}
|
||||
|
||||
if !new_argument.is_empty() {
|
||||
args.push(new_argument);
|
||||
}
|
||||
processes
|
||||
}
|
||||
Some("%F") => {
|
||||
// TODO: %f and %F need to handle non-file URLs (see spec)
|
||||
for invalid in path_opt
|
||||
.iter()
|
||||
.map(AsRef::as_ref)
|
||||
.filter(|&path| from_file_or_dir(path).is_none())
|
||||
{
|
||||
log::warn!("Desktop file expects a file path instead of a URL: {invalid:?}");
|
||||
}
|
||||
|
||||
let mut command = process::Command::new(&arguments[0]);
|
||||
// Launch one instance with all args
|
||||
let mut process = process::Command::new(program);
|
||||
process.args(
|
||||
args_vec
|
||||
.iter()
|
||||
.map(OsStr::new)
|
||||
.take(field_code_pos)
|
||||
.chain(path_opt.iter().map(AsRef::as_ref)),
|
||||
);
|
||||
|
||||
for arg in args {
|
||||
match arg.to_os_str() {
|
||||
Ok(arg) => {
|
||||
command.arg(arg);
|
||||
}
|
||||
Err(_) => {
|
||||
tracing::error!("invalid string encoding in command");
|
||||
vec![process]
|
||||
}
|
||||
Some("%u") => path_opt
|
||||
.iter()
|
||||
.map(|path| {
|
||||
let mut process = process::Command::new(program);
|
||||
process.args(
|
||||
args_vec
|
||||
.iter()
|
||||
.map(OsStr::new)
|
||||
.take(field_code_pos)
|
||||
.chain(std::iter::once(path.as_ref())),
|
||||
);
|
||||
process
|
||||
})
|
||||
.collect(),
|
||||
Some("%U") => {
|
||||
let mut process = process::Command::new(program);
|
||||
process.args(
|
||||
args_vec
|
||||
.iter()
|
||||
.map(OsStr::new)
|
||||
.take(field_code_pos)
|
||||
.chain(path_opt.iter().map(AsRef::as_ref)),
|
||||
);
|
||||
vec![process]
|
||||
}
|
||||
Some(invalid) => unreachable!("All valid variants were checked; got: {invalid}"),
|
||||
None => vec![process::Command::new(program)],
|
||||
};
|
||||
|
||||
// Pass 2: Add remaining arguments that are not % to each process
|
||||
for arg in args_vec.iter().skip(field_code_pos) {
|
||||
match arg.as_str() {
|
||||
// Consume path field codes or fail on codes we don't handle yet
|
||||
field_code if arg.starts_with('%') => {
|
||||
if !EXEC_HANDLERS.contains(&field_code)
|
||||
&& !DEPRECATED_HANDLERS.contains(&field_code)
|
||||
{
|
||||
log::warn!("unsupported Exec code {field_code:?} in {exec:?}");
|
||||
return None;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
commands.push(command);
|
||||
|
||||
if !batch_process {
|
||||
break;
|
||||
arg => {
|
||||
for process in &mut processes {
|
||||
process.arg(arg);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(debug_assertions)]
|
||||
for command in &commands {
|
||||
for command in &processes {
|
||||
log::debug!(
|
||||
"Parsed program {} with args: {:?}",
|
||||
command.get_program().to_string_lossy(),
|
||||
|
|
@ -125,7 +157,13 @@ pub fn exec_to_command(
|
|||
);
|
||||
}
|
||||
|
||||
Some(commands)
|
||||
Some(processes)
|
||||
}
|
||||
|
||||
fn from_file_or_dir(path: impl AsRef<Path>) -> Option<url::Url> {
|
||||
url::Url::from_file_path(&path)
|
||||
.ok()
|
||||
.or_else(|| url::Url::from_directory_path(&path).ok())
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
|
|
@ -141,12 +179,7 @@ pub struct MimeApp {
|
|||
impl MimeApp {
|
||||
//TODO: move to libcosmic, support multiple files
|
||||
pub fn command<O: AsRef<OsStr>>(&self, path_opt: &[O]) -> Option<Vec<process::Command>> {
|
||||
exec_to_command(
|
||||
self.exec.as_deref()?,
|
||||
&self.name,
|
||||
self.path.as_deref(),
|
||||
path_opt,
|
||||
)
|
||||
exec_to_command(self.exec.as_deref()?, path_opt)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -366,12 +399,9 @@ impl MimeAppCache {
|
|||
// The current approach works but might not adhere to the spec (yet)
|
||||
|
||||
// Look for and return preferred terminals
|
||||
// Yoda: cosmic-yoterm (our fork) wins over upstream cosmic-term if both
|
||||
// are installed — useful when xdg-mime default is not set.
|
||||
let mut preference_order = vec![
|
||||
"com.aditua.CosmicYoterm".to_string(),
|
||||
"com.system76.CosmicTerm".to_string(),
|
||||
];
|
||||
//TODO: fallback order beyond cosmic-term?
|
||||
|
||||
let mut preference_order = vec!["com.system76.CosmicTerm".to_string()];
|
||||
|
||||
if let Some(id) = self.get_default_terminal() {
|
||||
preference_order.insert(0, id);
|
||||
|
|
@ -445,43 +475,11 @@ impl Default for MimeAppCache {
|
|||
mod tests {
|
||||
use super::exec_to_command;
|
||||
|
||||
#[test]
|
||||
fn keys_within_words() {
|
||||
let exec = "/usr/bin/foo --option=%f";
|
||||
let paths = ["file1"];
|
||||
let commands = exec_to_command(exec, "keys_within_words", None, &paths)
|
||||
.expect("Should parse valid exec");
|
||||
|
||||
assert_eq!(1, commands.len());
|
||||
let command = commands.first().unwrap();
|
||||
|
||||
assert_eq!("/usr/bin/foo", command.get_program().to_str().unwrap());
|
||||
assert_eq!(
|
||||
"--option=file1",
|
||||
command.get_args().next().unwrap().to_str().unwrap()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn no_path_f_field_code() {
|
||||
let exec = "/usr/bin/foo %f";
|
||||
let paths: [&str; 0] = [];
|
||||
let commands = exec_to_command(exec, "no_path_f_field_code", None, &paths)
|
||||
.expect("Should parse valid exec");
|
||||
|
||||
assert_eq!(1, commands.len());
|
||||
let command = commands.first().unwrap();
|
||||
|
||||
assert_eq!("/usr/bin/foo", command.get_program().to_str().unwrap());
|
||||
assert_eq!(0, command.get_args().len());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn one_path_f_field_code() {
|
||||
let exec = "/usr/bin/foo %f";
|
||||
let paths = ["file1"];
|
||||
let commands = exec_to_command(exec, "one_path_f_field_code", None, &paths)
|
||||
.expect("Should parse valid exec");
|
||||
let commands = exec_to_command(exec, &paths).expect("Should parse valid exec");
|
||||
|
||||
assert_eq!(1, commands.len());
|
||||
let command = commands.first().unwrap();
|
||||
|
|
@ -496,40 +494,31 @@ mod tests {
|
|||
#[test]
|
||||
#[allow(non_snake_case)]
|
||||
fn one_path_F_field_code() {
|
||||
let exec = "/usr/bin/cosmic-term -w %F";
|
||||
let paths = ["/home/user"];
|
||||
let commands = exec_to_command(exec, "one_path_F_field_code", None, &paths)
|
||||
.expect("Should parse valid exec");
|
||||
let exec = "/usr/bin/bar %F";
|
||||
let paths = ["cat"];
|
||||
let commands = exec_to_command(exec, &paths).expect("Should parse valid exec");
|
||||
|
||||
assert_eq!(1, commands.len());
|
||||
let command = commands.first().unwrap();
|
||||
let mut args = command.get_args();
|
||||
|
||||
assert_eq!(
|
||||
"/usr/bin/cosmic-term",
|
||||
command.get_program().to_str().unwrap()
|
||||
);
|
||||
assert_eq!("-w", args.next().unwrap().to_str().unwrap());
|
||||
assert_eq!(paths[0], args.next().unwrap().to_str().unwrap());
|
||||
assert_eq!("/usr/bin/bar", command.get_program().to_str().unwrap());
|
||||
assert_eq!("cat", command.get_args().next().unwrap().to_str().unwrap());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn one_path_u_field_code() {
|
||||
let exec = "/usr/bin/cosmic-term -w %u";
|
||||
let paths = ["/home/user"];
|
||||
let commands = exec_to_command(exec, "one_path_u_field_code", None, &paths)
|
||||
.expect("Should parse valid exec");
|
||||
let exec = "/usr/bin/foobar %u";
|
||||
let paths = ["/home/josh/krumpli"];
|
||||
let commands = exec_to_command(exec, &paths).expect("Should parse valid exec");
|
||||
|
||||
assert_eq!(1, commands.len());
|
||||
let command = commands.first().unwrap();
|
||||
let mut args = command.get_args();
|
||||
|
||||
assert_eq!("/usr/bin/foobar", command.get_program().to_str().unwrap());
|
||||
assert_eq!(
|
||||
"/usr/bin/cosmic-term",
|
||||
command.get_program().to_str().unwrap()
|
||||
*paths.first().unwrap(),
|
||||
command.get_args().next().unwrap().to_str().unwrap()
|
||||
);
|
||||
assert_eq!("-w", args.next().unwrap().to_str().unwrap());
|
||||
assert_eq!(paths[0], args.next().unwrap().to_str().unwrap());
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
@ -537,8 +526,7 @@ mod tests {
|
|||
fn one_path_U_field_code() {
|
||||
let exec = "/usr/bin/rmrfbye %U";
|
||||
let paths = ["/"];
|
||||
let commands = exec_to_command(exec, "one_path_U_field_code", None, &paths)
|
||||
.expect("Should parse valid exec");
|
||||
let commands = exec_to_command(exec, &paths).expect("Should parse valid exec");
|
||||
|
||||
assert_eq!(1, commands.len());
|
||||
let command = commands.first().unwrap();
|
||||
|
|
@ -554,8 +542,7 @@ mod tests {
|
|||
"/usr/share/games/psp/miku.iso",
|
||||
"/usr/share/games/psp/eternia.iso",
|
||||
];
|
||||
let commands = exec_to_command(exec, "mult_path_f_field_code", None, &paths)
|
||||
.expect("Should parse valid exec");
|
||||
let commands = exec_to_command(exec, &paths).expect("Should parse valid exec");
|
||||
|
||||
assert_eq!(paths.len(), commands.len());
|
||||
for (command, path) in commands.into_iter().zip(paths.iter()) {
|
||||
|
|
@ -575,8 +562,7 @@ mod tests {
|
|||
"/usr/share/games/doom2/hr.wad",
|
||||
"/usr/share/games/doom2/hrmus.wad",
|
||||
];
|
||||
let commands = exec_to_command(exec, "mult_path_F_field_code", None, &paths)
|
||||
.expect("Should parse valid exec");
|
||||
let commands = exec_to_command(exec, &paths).expect("Should parse valid exec");
|
||||
|
||||
assert_eq!(1, commands.len());
|
||||
let command = commands.first().unwrap();
|
||||
|
|
@ -598,8 +584,7 @@ mod tests {
|
|||
"https://redox-os.org/",
|
||||
"https://system76.com/",
|
||||
];
|
||||
let commands = exec_to_command(exec, "mult_path_u_field_code", None, &paths)
|
||||
.expect("Should parse valid exec");
|
||||
let commands = exec_to_command(exec, &paths).expect("Should parse valid exec");
|
||||
|
||||
assert_eq!(paths.len(), commands.len());
|
||||
for (command, path) in commands.into_iter().zip(paths.iter()) {
|
||||
|
|
@ -622,8 +607,7 @@ mod tests {
|
|||
"frieren01.mkv",
|
||||
"rtmp://example.org/this/video/doesnt/exist.avi",
|
||||
];
|
||||
let commands = exec_to_command(exec, "mult_path_U_field_code", None, &paths)
|
||||
.expect("Should parse valid exec");
|
||||
let commands = exec_to_command(exec, &paths).expect("Should parse valid exec");
|
||||
|
||||
assert_eq!(1, commands.len());
|
||||
let command = commands.first().unwrap();
|
||||
|
|
@ -651,8 +635,7 @@ mod tests {
|
|||
"@@u",
|
||||
];
|
||||
let paths = ["file1.rs", "file2.rs"];
|
||||
let commands = exec_to_command(exec, "flatpak_style_exec", None, &paths)
|
||||
.expect("Should parse valid exec");
|
||||
let commands = exec_to_command(exec, &paths).expect("Should parse valid exec");
|
||||
|
||||
assert_eq!(1, commands.len());
|
||||
let command = commands.first().unwrap();
|
||||
|
|
@ -675,8 +658,7 @@ mod tests {
|
|||
"file:///usr/share/games/roguelike/mods/mod1",
|
||||
"file:///usr/share/games/roguelike/mods/mod2",
|
||||
];
|
||||
let commands = exec_to_command(exec, "multiple_field_codes", None, &paths)
|
||||
.expect("Should parse valid exec");
|
||||
let commands = exec_to_command(exec, &paths).expect("Should parse valid exec");
|
||||
|
||||
assert_eq!(1, commands.len());
|
||||
let command = commands.first().unwrap();
|
||||
|
|
@ -709,8 +691,7 @@ mod tests {
|
|||
];
|
||||
let paths = ["rust_game_dev.pdf", "superhero_ferris.epub"];
|
||||
let args_trailing = ["@@"];
|
||||
let commands = exec_to_command(exec, "sandwiched_field_code", None, &paths)
|
||||
.expect("Should parse valid exec");
|
||||
let commands = exec_to_command(exec, &paths).expect("Should parse valid exec");
|
||||
|
||||
assert_eq!(1, commands.len());
|
||||
let command = commands.first().unwrap();
|
||||
|
|
|
|||
|
|
@ -3,9 +3,11 @@
|
|||
use cosmic::widget::icon;
|
||||
use mime_guess::Mime;
|
||||
use rustc_hash::FxHashMap;
|
||||
use std::fs;
|
||||
use std::path::Path;
|
||||
use std::sync::{LazyLock, Mutex};
|
||||
use std::{
|
||||
fs,
|
||||
path::Path,
|
||||
sync::{LazyLock, Mutex},
|
||||
};
|
||||
|
||||
pub const FALLBACK_MIME_ICON: &str = "text-x-generic";
|
||||
|
||||
|
|
@ -17,7 +19,6 @@ struct MimeIconKey {
|
|||
|
||||
struct MimeIconCache {
|
||||
cache: FxHashMap<MimeIconKey, Option<icon::Handle>>,
|
||||
#[cfg(unix)]
|
||||
shared_mime_info: xdg_mime::SharedMimeInfo,
|
||||
}
|
||||
|
||||
|
|
@ -25,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)
|
||||
|
|
@ -45,7 +39,7 @@ impl MimeIconCache {
|
|||
return None;
|
||||
}
|
||||
let icon_name = icon_names.remove(0);
|
||||
let mut named = icon::from_name(icon_name).prefer_svg(true).size(key.size);
|
||||
let mut named = icon::from_name(icon_name).size(key.size);
|
||||
if !icon_names.is_empty() {
|
||||
let fallback_names =
|
||||
icon_names.into_iter().map(std::borrow::Cow::from).collect();
|
||||
|
|
@ -59,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>,
|
||||
|
|
@ -112,20 +96,12 @@ pub fn mime_icon(mime: Mime, size: u16) -> icon::Handle {
|
|||
let mut mime_icon_cache = MIME_ICON_CACHE.lock().unwrap();
|
||||
match mime_icon_cache.get(MimeIconKey { mime, size }) {
|
||||
Some(handle) => handle,
|
||||
None => icon::from_name(FALLBACK_MIME_ICON)
|
||||
.prefer_svg(true)
|
||||
.size(size)
|
||||
.handle(),
|
||||
None => icon::from_name(FALLBACK_MIME_ICON).size(size).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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,20 +1,18 @@
|
|||
use cosmic::iced::futures::SinkExt;
|
||||
use cosmic::iced::{Subscription, stream};
|
||||
use cosmic::{Task, widget};
|
||||
use gio::glib;
|
||||
use gio::prelude::*;
|
||||
use std::any::TypeId;
|
||||
use std::cell::Cell;
|
||||
use std::future::pending;
|
||||
use std::hash::Hash;
|
||||
use std::path::PathBuf;
|
||||
use std::sync::Arc;
|
||||
use tokio::sync::mpsc;
|
||||
use cosmic::{
|
||||
Task,
|
||||
iced::{Subscription, futures::SinkExt, stream},
|
||||
widget,
|
||||
};
|
||||
use gio::{glib, prelude::*};
|
||||
use std::{any::TypeId, cell::Cell, future::pending, hash::Hash, path::PathBuf, sync::Arc};
|
||||
use tokio::sync::{Mutex, mpsc};
|
||||
|
||||
use super::{Mounter, MounterAuth, MounterItem, MounterItems, MounterMessage};
|
||||
use crate::config::IconSizes;
|
||||
use crate::err_str;
|
||||
use crate::tab::{self, DirSize, ItemMetadata, ItemThumbnail, Location};
|
||||
use crate::{
|
||||
config::IconSizes,
|
||||
err_str,
|
||||
tab::{self, DirSize, ItemMetadata, ItemThumbnail, Location},
|
||||
};
|
||||
|
||||
const TARGET_URI_ATTRIBUTE: &str = "standard::target-uri";
|
||||
|
||||
|
|
@ -201,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,
|
||||
|
|
@ -232,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| {
|
||||
|
|
@ -258,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) {
|
||||
|
|
@ -363,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();
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -409,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();
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -439,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 {
|
||||
|
|
@ -495,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(()));
|
||||
|
|
@ -509,7 +482,7 @@ impl Gvfs {
|
|||
Some(gio::IOErrorEnum::FailedHandled) => Ok(false),
|
||||
_ => Err(format!("{err}"))
|
||||
}}
|
||||
}));
|
||||
})).unwrap();
|
||||
},
|
||||
);
|
||||
break;
|
||||
|
|
@ -525,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(()));
|
||||
|
|
@ -538,7 +508,7 @@ impl Gvfs {
|
|||
Some(gio::IOErrorEnum::FailedHandled) => Ok(false),
|
||||
_ => Err(format!("{err}"))
|
||||
}}
|
||||
}));
|
||||
})).unwrap();
|
||||
}
|
||||
);
|
||||
}
|
||||
|
|
@ -562,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)
|
||||
|
|
@ -573,7 +540,7 @@ impl Gvfs {
|
|||
Some(gio::IOErrorEnum::FailedHandled) => Ok(false),
|
||||
_ => Err(format!("{err}"))
|
||||
}
|
||||
}));
|
||||
})).unwrap();
|
||||
}
|
||||
);
|
||||
} else {
|
||||
|
|
@ -629,7 +596,7 @@ impl Gvfs {
|
|||
});
|
||||
Self {
|
||||
command_tx,
|
||||
event_rx: Arc::new(event_rx),
|
||||
event_rx: Arc::new(Mutex::new(event_rx)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -703,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) {
|
||||
|
|
@ -727,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,13 +1,13 @@
|
|||
use cosmic::iced::Subscription;
|
||||
use cosmic::{Task, widget};
|
||||
use std::collections::BTreeMap;
|
||||
use std::fmt;
|
||||
use std::path::PathBuf;
|
||||
use std::sync::{Arc, LazyLock};
|
||||
use cosmic::{Task, iced::Subscription, widget};
|
||||
use std::{
|
||||
collections::BTreeMap,
|
||||
fmt,
|
||||
path::PathBuf,
|
||||
sync::{Arc, LazyLock},
|
||||
};
|
||||
use tokio::sync::mpsc;
|
||||
|
||||
use crate::config::IconSizes;
|
||||
use crate::tab;
|
||||
use crate::{config::IconSizes, tab};
|
||||
|
||||
#[cfg(feature = "gvfs")]
|
||||
mod gvfs;
|
||||
|
|
@ -75,10 +75,10 @@ impl MounterItem {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn icon(&self, _symbolic: bool) -> Option<widget::icon::Handle> {
|
||||
pub fn icon(&self, symbolic: bool) -> Option<widget::icon::Handle> {
|
||||
match self {
|
||||
#[cfg(feature = "gvfs")]
|
||||
Self::Gvfs(item) => item.icon(_symbolic),
|
||||
Self::Gvfs(item) => item.icon(symbolic),
|
||||
Self::None => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
|
@ -103,7 +103,6 @@ impl MounterItem {
|
|||
pub type MounterItems = Vec<MounterItem>;
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
#[allow(dead_code)]
|
||||
pub enum MounterMessage {
|
||||
Items(MounterItems),
|
||||
MountResult(MounterItem, Result<bool, String>),
|
||||
|
|
|
|||
|
|
@ -3,17 +3,21 @@
|
|||
use std::time::Instant;
|
||||
|
||||
use crate::tab::DOUBLE_CLICK_DURATION;
|
||||
use cosmic::iced::core::border::Border;
|
||||
use cosmic::iced::core::event::Event;
|
||||
use cosmic::iced::core::mouse::{self, click};
|
||||
use cosmic::iced::core::renderer::{self, Quad, Renderer as _};
|
||||
use cosmic::iced::core::widget::{Operation, Tree, tree};
|
||||
use cosmic::iced::core::{
|
||||
Clipboard, Color, Layout, Length, Point, Rectangle, Shell, Size, Vector, Widget, layout,
|
||||
overlay, touch,
|
||||
use cosmic::{
|
||||
Element, Renderer, Theme,
|
||||
iced_core::{
|
||||
Clipboard, Color, Layout, Length, Point, Rectangle, Shell, Size, Vector, Widget,
|
||||
border::Border,
|
||||
event::Event,
|
||||
layout,
|
||||
mouse::{self, click},
|
||||
overlay,
|
||||
renderer::{self, Quad, Renderer as _},
|
||||
touch,
|
||||
widget::{Operation, Tree, tree},
|
||||
},
|
||||
widget::Id,
|
||||
};
|
||||
use cosmic::widget::Id;
|
||||
use cosmic::{Element, Renderer, Theme};
|
||||
|
||||
/// Emit messages on mouse events.
|
||||
#[allow(missing_debug_implementations)]
|
||||
|
|
@ -391,7 +395,7 @@ where
|
|||
|
||||
update(
|
||||
self,
|
||||
event,
|
||||
&event,
|
||||
layout,
|
||||
cursor,
|
||||
shell,
|
||||
|
|
@ -484,7 +488,7 @@ where
|
|||
state: &Tree,
|
||||
layout: Layout<'_>,
|
||||
renderer: &Renderer,
|
||||
dnd_rectangles: &mut cosmic::iced::core::clipboard::DndDestinationRectangles,
|
||||
dnd_rectangles: &mut cosmic::iced_core::clipboard::DndDestinationRectangles,
|
||||
) {
|
||||
self.content.as_widget().drag_destinations(
|
||||
&state.children[0],
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -1,15 +1,20 @@
|
|||
use crate::app::{ArchiveType, DialogPage, Message, REPLACE_BUTTON_ID};
|
||||
use crate::config::IconSizes;
|
||||
use crate::spawn_detached::spawn_detached;
|
||||
use crate::{archive, fl, tab};
|
||||
use cosmic::iced::futures::channel::mpsc::Sender;
|
||||
use cosmic::iced::futures::{self, SinkExt, StreamExt, stream};
|
||||
use std::borrow::Cow;
|
||||
use std::fmt::Formatter;
|
||||
use std::fs;
|
||||
use std::io::{self, Read, Write};
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::sync::Arc;
|
||||
use crate::{
|
||||
app::{ArchiveType, DialogPage, Message, REPLACE_BUTTON_ID},
|
||||
archive,
|
||||
config::IconSizes,
|
||||
fl,
|
||||
spawn_detached::spawn_detached,
|
||||
tab,
|
||||
};
|
||||
use cosmic::iced::futures::{self, SinkExt, StreamExt, channel::mpsc::Sender, stream};
|
||||
use std::{
|
||||
borrow::Cow,
|
||||
fmt::Formatter,
|
||||
fs,
|
||||
io::{self, Read, Write},
|
||||
path::{Path, PathBuf},
|
||||
sync::Arc,
|
||||
};
|
||||
use tokio::sync::{Mutex as TokioMutex, mpsc};
|
||||
use walkdir::WalkDir;
|
||||
use zip::AesMode::Aes256;
|
||||
|
|
@ -17,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;
|
||||
|
||||
|
|
@ -34,7 +36,7 @@ async fn handle_replace(
|
|||
conflict_count: usize,
|
||||
) -> ReplaceResult {
|
||||
let item_from = match tab::item_from_path(file_from, IconSizes::default()) {
|
||||
Ok(ok) => Box::new(ok),
|
||||
Ok(ok) => ok,
|
||||
Err(err) => {
|
||||
log::warn!("{err}");
|
||||
return ReplaceResult::Cancel;
|
||||
|
|
@ -42,7 +44,7 @@ async fn handle_replace(
|
|||
};
|
||||
|
||||
let item_to = match tab::item_from_path(file_to, IconSizes::default()) {
|
||||
Ok(ok) => Box::new(ok),
|
||||
Ok(ok) => ok,
|
||||
Err(err) => {
|
||||
log::warn!("{err}");
|
||||
return ReplaceResult::Cancel;
|
||||
|
|
@ -109,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)| {
|
||||
|
|
@ -127,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());
|
||||
|
||||
|
|
@ -224,7 +216,7 @@ pub async fn sync_to_disk(
|
|||
}
|
||||
}))
|
||||
.buffer_unordered(32)
|
||||
.collect::<()>()
|
||||
.collect::<Vec<_>>()
|
||||
.await;
|
||||
|
||||
// Sync directories to disk
|
||||
|
|
@ -234,7 +226,7 @@ pub async fn sync_to_disk(
|
|||
}
|
||||
}))
|
||||
.buffer_unordered(16)
|
||||
.collect::<()>()
|
||||
.collect::<Vec<_>>()
|
||||
.await;
|
||||
}
|
||||
|
||||
|
|
@ -770,12 +762,13 @@ impl Operation {
|
|||
OperationError::from_err(e, &controller)
|
||||
})?;
|
||||
|
||||
if let Ok(modified) = metadata.modified()
|
||||
&& let Some(last_modified) =
|
||||
if let Ok(modified) = metadata.modified() {
|
||||
if let Some(last_modified) =
|
||||
archive::system_time_to_zip_date_time(modified)
|
||||
{
|
||||
zip_options =
|
||||
zip_options.last_modified_time(last_modified);
|
||||
{
|
||||
zip_options =
|
||||
zip_options.last_modified_time(last_modified);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
|
|
@ -1120,9 +1113,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 } => {
|
||||
|
|
@ -1190,10 +1181,8 @@ impl Operation {
|
|||
.map_err(|s| OperationError::from_state(s, &controller))?;
|
||||
|
||||
let controller_clone = controller.clone();
|
||||
let path_clone = path.clone();
|
||||
compio::runtime::spawn_blocking(move || -> Result<(), OperationError> {
|
||||
let controller = controller_clone;
|
||||
let path = path_clone;
|
||||
//TODO: what to do on non-Unix systems?
|
||||
#[cfg(unix)]
|
||||
{
|
||||
|
|
@ -1208,10 +1197,7 @@ impl Operation {
|
|||
.await
|
||||
.map_err(wrap_compio_spawn_error)?
|
||||
.map_err(|e| OperationError::from_err(e, &controller))?;
|
||||
Ok(OperationSelection {
|
||||
ignored: Vec::new(),
|
||||
selected: vec![path],
|
||||
})
|
||||
Ok(OperationSelection::default())
|
||||
}
|
||||
};
|
||||
|
||||
|
|
@ -1238,23 +1224,28 @@ fn wrap_compio_spawn_error(err: Box<dyn std::any::Any + Send>) -> OperationError
|
|||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::fs::{self, File};
|
||||
use std::io;
|
||||
use std::path::PathBuf;
|
||||
use std::{
|
||||
fs::{self, File},
|
||||
io,
|
||||
path::PathBuf,
|
||||
};
|
||||
|
||||
use cosmic::iced::futures::channel::mpsc;
|
||||
use cosmic::iced::futures::{StreamExt, future};
|
||||
use cosmic::iced::futures::{StreamExt, channel::mpsc, future};
|
||||
use log::debug;
|
||||
use test_log::test;
|
||||
use tokio::sync;
|
||||
|
||||
use super::{Controller, Operation, OperationError, OperationSelection, ReplaceResult};
|
||||
use crate::app::test_utils::{
|
||||
NAME_LEN, NUM_DIRS, NUM_FILES, NUM_HIDDEN, NUM_NESTED, empty_fs, filter_dirs, filter_files,
|
||||
simple_fs,
|
||||
use crate::{
|
||||
app::{
|
||||
DialogPage, Message,
|
||||
test_utils::{
|
||||
NAME_LEN, NUM_DIRS, NUM_FILES, NUM_HIDDEN, NUM_NESTED, empty_fs, filter_dirs,
|
||||
filter_files, simple_fs,
|
||||
},
|
||||
},
|
||||
fl,
|
||||
};
|
||||
use crate::app::{DialogPage, Message};
|
||||
use crate::fl;
|
||||
|
||||
/// Simple wrapper around `[Operation::Copy]`
|
||||
pub async fn operation_copy(
|
||||
|
|
|
|||
|
|
@ -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,5 +1,4 @@
|
|||
use std::path::Path;
|
||||
use std::{fs, io};
|
||||
use std::{fs, io, path::Path};
|
||||
|
||||
use crate::operation::OperationError;
|
||||
|
||||
|
|
|
|||
|
|
@ -1,39 +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;
|
||||
use compio::driver::op::AsyncifyFd;
|
||||
use compio::io::{AsyncReadAt, AsyncWriteAt};
|
||||
use cosmic::iced::futures;
|
||||
#[cfg(feature = "gvfs")]
|
||||
use futures::{FutureExt, StreamExt};
|
||||
use std::cell::Cell;
|
||||
use std::error::Error;
|
||||
use std::fs;
|
||||
use std::future::Future;
|
||||
use std::ops::ControlFlow;
|
||||
use std::path::PathBuf;
|
||||
use std::pin::Pin;
|
||||
use std::rc::Rc;
|
||||
use std::time::Instant;
|
||||
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,
|
||||
|
|
@ -337,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) = dbg!(metadata.modified()) {
|
||||
times = times.set_modified(time);
|
||||
}
|
||||
if let Ok(time) = dbg!(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? {
|
||||
|
|
@ -436,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()
|
||||
&& 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
1118
src/tab.rs
1118
src/tab.rs
File diff suppressed because it is too large
Load diff
|
|
@ -1,14 +1,15 @@
|
|||
use image::DynamicImage;
|
||||
use md5::{Digest, Md5};
|
||||
use rustc_hash::FxHashMap;
|
||||
use std::error::Error;
|
||||
use std::fs::{self, File};
|
||||
use std::io::{self, BufReader, BufWriter};
|
||||
#[cfg(unix)]
|
||||
use std::os::unix::fs::PermissionsExt;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::sync::LazyLock;
|
||||
use std::time::UNIX_EPOCH;
|
||||
use std::{
|
||||
error::Error,
|
||||
fs::{self, File},
|
||||
io::{self, BufReader, BufWriter},
|
||||
os::unix::fs::PermissionsExt,
|
||||
path::{Path, PathBuf},
|
||||
sync::LazyLock,
|
||||
time::UNIX_EPOCH,
|
||||
};
|
||||
use tempfile::NamedTempFile;
|
||||
use url::Url;
|
||||
|
||||
|
|
@ -92,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)?;
|
||||
|
|
@ -127,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,16 +1,15 @@
|
|||
// 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;
|
||||
#[cfg(feature = "desktop")]
|
||||
use std::{fs, time::Instant};
|
||||
use std::{
|
||||
fs,
|
||||
path::Path,
|
||||
process,
|
||||
sync::{LazyLock, Mutex},
|
||||
time::Instant,
|
||||
};
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
|
|
|
|||
153
src/trash.rs
153
src/trash.rs
|
|
@ -1,153 +0,0 @@
|
|||
use cosmic::widget;
|
||||
use regex::Regex;
|
||||
use std::collections::HashSet;
|
||||
use std::path::PathBuf;
|
||||
|
||||
use crate::config::IconSizes;
|
||||
use crate::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;
|
||||
use crate::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 {
|
||||
fn scan_search<F: Fn(SearchItem) -> bool + Sync>(callback: F, regex: &Regex) {
|
||||
log::warn!(
|
||||
"searching trash not supported on this platform for pattern {:?}",
|
||||
regex.as_str()
|
||||
);
|
||||
drop(callback);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,7 +1,6 @@
|
|||
use std::num::NonZeroU16;
|
||||
|
||||
use crate::config::IconSizes;
|
||||
use crate::tab::View;
|
||||
use crate::{config::IconSizes, tab::View};
|
||||
|
||||
static DEFAULT_ZOOM: NonZeroU16 = NonZeroU16::new(100).unwrap();
|
||||
static MIN_ZOOM: NonZeroU16 = NonZeroU16::new(50).unwrap();
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue