Merge branch 'master' into master
This commit is contained in:
commit
eb1218a0db
26 changed files with 2278 additions and 1660 deletions
972
Cargo.lock
generated
972
Cargo.lock
generated
File diff suppressed because it is too large
Load diff
44
Cargo.toml
44
Cargo.toml
|
|
@ -1,10 +1,10 @@
|
|||
[package]
|
||||
name = "cosmic-files"
|
||||
version = "1.0.0"
|
||||
version = "1.0.5"
|
||||
authors = ["Jeremy Soller <jeremy@system76.com>"]
|
||||
edition = "2024"
|
||||
license = "GPL-3.0-only"
|
||||
rust-version = "1.85"
|
||||
rust-version = "1.90"
|
||||
|
||||
[dependencies]
|
||||
anyhow = "1"
|
||||
|
|
@ -14,8 +14,6 @@ cctk = { git = "https://github.com/pop-os/cosmic-protocols", package = "cosmic-c
|
|||
cosmic-mime-apps = { git = "https://github.com/pop-os/cosmic-mime-apps.git", optional = true }
|
||||
dirs = "6.0.0"
|
||||
env_logger = "0.11"
|
||||
freedesktop_entry_parser = "1.3"
|
||||
futures = "0.3.31"
|
||||
gio = { version = "0.21", optional = true }
|
||||
glib = { version = "0.21", optional = true }
|
||||
glob = "0.3"
|
||||
|
|
@ -24,9 +22,9 @@ image = "0.25"
|
|||
libc = "0.2"
|
||||
log = "0.4"
|
||||
mime_guess = "2"
|
||||
notify-debouncer-full = "0.6"
|
||||
notify-debouncer-full = "0.7"
|
||||
notify-rust = { version = "4", optional = true }
|
||||
open = "5.3.2"
|
||||
open = "5.3.3"
|
||||
paste = "1.0"
|
||||
regex = "1"
|
||||
rustc-hash = "2.1"
|
||||
|
|
@ -38,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.11", 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.44"
|
||||
lzma-rust2 = { version = "0.15.4", optional = true }
|
||||
ordermap = { version = "1.0.0", features = ["serde"] }
|
||||
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,10 +52,10 @@ i18n-embed = { version = "0.16", features = [
|
|||
] }
|
||||
i18n-embed-fl = "0.10"
|
||||
rust-embed = "8"
|
||||
slotmap = "1.0.7"
|
||||
slotmap = "1.1.1"
|
||||
recently-used-xbel = { git = "https://github.com/pop-os/recently-used-xbel.git" }
|
||||
zip = "7"
|
||||
uzers = "0.12.1"
|
||||
uzers = "0.12.2"
|
||||
md-5 = "0.10.6"
|
||||
png = "0.18"
|
||||
jxl-oxide = { version = "0.12.5", features = ["image"] }
|
||||
|
|
@ -65,22 +63,23 @@ num_cpus = "1.17.0"
|
|||
|
||||
# Completion-based IO runtime to enable io_uring / IOCP file IO support.
|
||||
[dependencies.compio]
|
||||
# Patched to fix mtime: https://github.com/compio-rs/compio/pull/625
|
||||
# version = "0.17.0"
|
||||
git = "https://github.com/jackpot51/compio.git"
|
||||
version = "0.18"
|
||||
default-features = false
|
||||
features = ["fs", "io", "macros", "polling", "runtime"]
|
||||
|
||||
[dependencies.io-uring]
|
||||
version = "0.7.11"
|
||||
default-features = false
|
||||
optional = true
|
||||
|
||||
[dependencies.libcosmic]
|
||||
git = "https://github.com/pop-os/libcosmic.git"
|
||||
default-features = false
|
||||
#TODO: a11y feature crashes
|
||||
features = ["about", "autosize", "multi-window", "tokio", "winit", "surface-message"]
|
||||
features = [
|
||||
"about",
|
||||
"autosize",
|
||||
"desktop",
|
||||
"multi-window",
|
||||
"tokio",
|
||||
"winit",
|
||||
"surface-message",
|
||||
]
|
||||
|
||||
[[example]]
|
||||
name = "gio-list"
|
||||
|
|
@ -107,11 +106,10 @@ default = [
|
|||
"wayland",
|
||||
]
|
||||
dbus-config = ["libcosmic/dbus-config"]
|
||||
desktop = ["libcosmic/desktop", "dep:cosmic-mime-apps", "dep:xdg"]
|
||||
desktop = ["dep:cosmic-mime-apps", "dep:xdg"]
|
||||
desktop-applet = []
|
||||
gvfs = ["dep:gio", "dep:glib"]
|
||||
io-uring = ["compio/io-uring", "dep:io-uring"]
|
||||
io-uring-bindgen = ["io-uring?/bindgen"]
|
||||
io-uring = ["compio/io-uring"]
|
||||
jemalloc = ["dep:tikv-jemallocator"]
|
||||
notify = ["dep:notify-rust"]
|
||||
wayland = ["libcosmic/wayland", "dep:cctk", "dep:wayland-client"]
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "cosmic-files-applet"
|
||||
version = "0.1.0"
|
||||
version = "1.0.5"
|
||||
edition = "2024"
|
||||
|
||||
[dependencies]
|
||||
|
|
|
|||
12
debian/changelog
vendored
12
debian/changelog
vendored
|
|
@ -1,3 +1,15 @@
|
|||
cosmic-files (1.0.5) noble; urgency=medium
|
||||
|
||||
* Epoch 1.0.5 version update
|
||||
|
||||
-- Jeremy Soller <jeremy@system76.com> Fri, 30 Jan 2026 17:16:28 -0700
|
||||
|
||||
cosmic-files (1.0.4) noble; urgency=medium
|
||||
|
||||
* Epoch 1.0.4 version update
|
||||
|
||||
-- Jeremy Soller <jeremy@system76.com> Wed, 21 Jan 2026 10:16:11 -0700
|
||||
|
||||
cosmic-files (1.0.0) jammy; urgency=medium
|
||||
|
||||
* Stable release.
|
||||
|
|
|
|||
|
|
@ -389,9 +389,9 @@ type-to-search-enter-path = Zadává cestu ke složce nebo souboru
|
|||
compress = Komprimovat
|
||||
eject = Vysunout
|
||||
extract-here = Extrahovat
|
||||
change-wallpaper = Změnit pozadí...
|
||||
change-wallpaper = Změnit tapetu...
|
||||
desktop-appearance = Vzhled plochy...
|
||||
display-settings = Nastavení displeje...
|
||||
display-settings = Nastavení obrazovky...
|
||||
reload-folder = Znovu načíst složku
|
||||
sort-z-a = Z-A
|
||||
sort-newest-first = Nejnovější první
|
||||
|
|
|
|||
|
|
@ -0,0 +1,306 @@
|
|||
empty-folder = Kosongkan map
|
||||
empty-folder-hidden = Kosongkan map (memiliki item tersembunyi)
|
||||
no-results = Tidak ada hasil yang ditemukan
|
||||
filesystem = Sistem berkas
|
||||
cosmic-files = Berkas COSMIC
|
||||
home = Beranda
|
||||
networks = Jaringan
|
||||
notification-in-progress = Operasi berkas sedang berlangsung
|
||||
trash = Sampah
|
||||
recents = Terbaru
|
||||
undo = Batalkan
|
||||
today = Hari ini
|
||||
desktop-view-options = Opsi tampilan desktop...
|
||||
show-on-desktop = Tampilkan di Desktop
|
||||
desktop-folder-content = Konten map desktop
|
||||
mounted-drives = Drive terpasang
|
||||
trash-folder-icon = Ikon map sampah
|
||||
icon-size-and-spacing = Ukuran dan jarak ikon
|
||||
icon-size = Ukuran ikon
|
||||
name = Nama
|
||||
grid-spacing = Jarak antar kisi
|
||||
modified = Dimodifikasi
|
||||
trashed-on = Dibuang
|
||||
size = Ukuran
|
||||
details = Rincian
|
||||
dismiss = Abaikan pesan
|
||||
operations-running =
|
||||
{ $running } { $running ->
|
||||
[one] operasi
|
||||
*[other] operasi
|
||||
} berjalan ({ $percent }%)...
|
||||
operations-running-finished =
|
||||
{ $running } { $running ->
|
||||
[one] operasi
|
||||
*[other] operasi
|
||||
} berjalan ({ $percent }%), { $finished } selesai...
|
||||
pause = Jeda
|
||||
resume = Lanjutkan
|
||||
create-archive = Buat arsip
|
||||
extract-password-required = Kata sandi diperlukan
|
||||
extract-to = Ekstrak ke...
|
||||
extract-to-title = Ekstrak ke map
|
||||
empty-trash = Kosongkan sampah
|
||||
empty-trash-title = Kosongkan sampah?
|
||||
empty-trash-warning = Item di map Sampah akan dihapus permanen
|
||||
emptying-trash = Mengosongkan { trash } ({ $progress })...
|
||||
mount-error = Tidak dapat mengakses drive
|
||||
create-new-file = Buat berkas baru
|
||||
create-new-folder = Buat map baru
|
||||
permanently-delete-question = Hapus secara permanen?
|
||||
delete = Hapus
|
||||
sort-by-trashed = Urutkan berdasarkan waktu penghapusan
|
||||
delete-permanently = Hapus secara permanen
|
||||
permanently-delete-warning = { $target } akan dihapus secara permanen. Tindakan ini tidak dapat dibatalkan.
|
||||
deleted =
|
||||
{ $items } { $items ->
|
||||
[one] item
|
||||
*[other] item
|
||||
} dihapus dari { trash }
|
||||
permanently-deleted =
|
||||
{ $items } { $items ->
|
||||
[one] item
|
||||
*[other] item
|
||||
} dihapus secara permanen
|
||||
file-name = Nama berkas
|
||||
folder-name = Nama map
|
||||
file-already-exists = Berkas dengan nama tersebut sudah ada
|
||||
folder-already-exists = Map dengan nama tersebut sudah ada
|
||||
name-hidden = Nama yang diawali dengan "." akan disembunyikan
|
||||
name-invalid = Nama tidak boleh "{ $filename }"
|
||||
name-no-slashes = Nama tidak boleh berisi garis miring
|
||||
cancel = Batalkan
|
||||
create = Buat
|
||||
open = Buka
|
||||
open-file = Buka berkas
|
||||
open-folder = Buka map
|
||||
open-in-new-tab = Buka di tab baru
|
||||
open-in-new-window = Buka di jendela baru
|
||||
open-item-location = Buka lokasi item
|
||||
open-multiple-files = Buka beberapa berkas
|
||||
open-multiple-folders = Buka beberapa map
|
||||
save = Simpan
|
||||
save-file = Simpan berkas
|
||||
open-with-title = Bagaimana anda ingin membuka "{ $name }"?
|
||||
browse-store = Telusuri { $store }
|
||||
other-apps = Aplikasi lainnya
|
||||
related-apps = Aplikasi terkait
|
||||
rename-file = Ganti nama berkas
|
||||
rename-folder = Ganti nama map
|
||||
replace = Ganti
|
||||
replace-title = "{ $filename }" sudah ada di lokasi ini
|
||||
replace-warning-operation = Apakah anda ingin menggantinya? Menggantinya akan menimpa konten tersebut.
|
||||
original-file = Berkas asli
|
||||
replace-with = Ganti dengan
|
||||
apply-to-all = Terapkan ke semua
|
||||
replace-warning = Apakah anda ingin menggantinya dengan yang sedang anda simpan? Menggantinya akan menimpa konten tersebut.
|
||||
keep-both = Pertahankan keduanya
|
||||
skip = Lewati
|
||||
set-executable-and-launch = Atur sebagai dijalankan dan luncurkan
|
||||
set-and-launch = Atur dan luncurkan
|
||||
set-executable-and-launch-description = Apakah anda ingin mengatur "{ $name }" sebagai dijalankan dan luncurkan?
|
||||
open-with = Buka dengan
|
||||
owner = Pemilik
|
||||
group = Grup
|
||||
other = Lainnya
|
||||
none = Tidak ada
|
||||
execute-only = Hanya jalankan
|
||||
write-only = Hanya tulis
|
||||
write-execute = Tulis dan jalankan
|
||||
read-only = Hanya baca
|
||||
read-execute = Baca dan jalankan
|
||||
read-write = Baca dan tulis
|
||||
read-write-execute = Baca, tulis, dan jalankan
|
||||
favorite-path-error = Galat membuka direktori
|
||||
remove = Hapus
|
||||
keep = Pertahankan
|
||||
repository = Repositori
|
||||
favorite-path-error-description =
|
||||
Tidak dapat membuka "{ $path }"
|
||||
"{ $path }" mungkin tidak ada atau anda mungkin tidak memiliki izin untuk membuka
|
||||
|
||||
Apakah anda ingin menghapus dari bilah sisi?
|
||||
support = Dukungan
|
||||
add-network-drive = Tambahkan drive jaringan
|
||||
connect = Sambungkan
|
||||
connect-anonymously = Sambungkan secara anonim
|
||||
connecting = Menyambungkan...
|
||||
domain = Domain
|
||||
enter-server-address = Masukkan alamat server
|
||||
network-drive-description =
|
||||
Alamat server mencakup awalan protokol dan alamat.
|
||||
Contoh: ssh://192.168.0.1, ftp://[2001:db8::1]
|
||||
network-drive-schemes =
|
||||
Protokol yang tersedia,Awalan
|
||||
AppleTalk,afp://
|
||||
File Transfer Protocol,ftp:// atau ftps://
|
||||
Network File System,nfs://
|
||||
Server Message Block,smb://
|
||||
SSH File Transfer Protocol,sftp:// atau ssh://
|
||||
WebDAV,dav:// atau davs://
|
||||
network-drive-error = Tidak dapat mengakses drive jaringan
|
||||
password = Kata sandi
|
||||
remember-password = Ingat kata sandi
|
||||
try-again = Coba lagi
|
||||
username = Nama pengguna
|
||||
cancelled = Dibatalkan
|
||||
edit-history = Sunting riwayat
|
||||
history = Riwayat
|
||||
no-history = Tidak ada item dalam riwayat.
|
||||
pending = Menunggu
|
||||
progress = { $percent }%
|
||||
progress-cancelled = { $percent }%, dibatalkan
|
||||
progress-failed = { $percent }%, gagal
|
||||
progress-paused = { $percent }%, dijeda
|
||||
failed = Gagal
|
||||
complete = Selesai
|
||||
copy_noun = Salin
|
||||
creating = Membuat "{ $name }" di "{ $parent }"
|
||||
created = "{ $name }" dibuat di "{ $parent }"
|
||||
compressing =
|
||||
Mengompres { $items } { $items ->
|
||||
[one] item
|
||||
*[other] item
|
||||
} dari "{ $from }" ke "{ $to }" ({ $progress })...
|
||||
compressed =
|
||||
{ $items } { $items ->
|
||||
[one] item
|
||||
*[other] item
|
||||
} dikompres dari "{ $from }" ke "{ $to }"
|
||||
copied =
|
||||
{ $items } { $items ->
|
||||
[one] item
|
||||
*[other] item
|
||||
} disalin dari "{ $from }" ke "{ $to }"
|
||||
copying =
|
||||
Menyalin { $items } { $items ->
|
||||
[one] item
|
||||
*[other] item
|
||||
} dari "{ $from }" ke "{ $to }" ({ $progress })...
|
||||
deleting =
|
||||
Menghapus { $items } { $items ->
|
||||
[one] item
|
||||
*[other] item
|
||||
} dari { trash } ({ $progress })...
|
||||
emptied-trash = { trash } telah dikosongkan
|
||||
extracting =
|
||||
Mengekstrak { $items } { $items ->
|
||||
[one] item
|
||||
*[other] item
|
||||
} dari "{ $from }" ke "{ $to }" ({ $progress })...
|
||||
extracted =
|
||||
{ $items } { $items ->
|
||||
[one] item
|
||||
*[other] item
|
||||
} diekstrak dari "{ $from }" ke "{ $to }"
|
||||
setting-executable-and-launching = Mengatur "{ $name }" sebagai dijalankan dan meluncurkan
|
||||
set-executable-and-launched = Atur "{ $name }" sebagai dijalankan dan diluncurkan
|
||||
setting-permissions = Mengatur izin untuk "{ $name }" ke { $mode }
|
||||
set-permissions = Atur izin untuk "{ $name }" ke { $mode }
|
||||
menu-open-with = Buka dengan...
|
||||
unknown-folder = map yang tidak diketahui
|
||||
default-app = { $name } (bawaan)
|
||||
show-details = Tampilkan rincian
|
||||
type = Jenis: { $mime }
|
||||
items = Item: { $items }
|
||||
item-size = Ukuran: { $size }
|
||||
moving =
|
||||
Memindahkan { $items } { $items ->
|
||||
[one] item
|
||||
*[other] item
|
||||
} dari "{ $from }" ke "{ $to }" ({ $progress })...
|
||||
moved =
|
||||
{ $items } { $items ->
|
||||
[one] item
|
||||
*[other] item
|
||||
} dipindahkan dari "{ $from }" ke "{ $to }"
|
||||
permanently-deleting =
|
||||
Menghapus { $items } { $items ->
|
||||
[one] item
|
||||
*[other] item
|
||||
} secara permanen
|
||||
removing-from-recents =
|
||||
Menghapus { $items } { $items ->
|
||||
[one] item
|
||||
*[other] item
|
||||
} dari { recents }
|
||||
removed-from-recents =
|
||||
{ $items } { $items ->
|
||||
[one] item
|
||||
*[other] item
|
||||
} dihapus dari { recents }
|
||||
renaming = Mengganti nama "{ $from }" ke "{ $to }"
|
||||
renamed = Nama diganti "{ $from }" ke "{ $to }"
|
||||
restoring =
|
||||
Memulihkan { $items } { $items ->
|
||||
[one] item
|
||||
*[other] item
|
||||
} dari { trash } ({ $progress })...
|
||||
restored =
|
||||
{ $items } { $items ->
|
||||
[one] item
|
||||
*[other] item
|
||||
} dipulihkan dari { trash }
|
||||
item-created = Dibuat: { $created }
|
||||
item-modified = Dimodifikasi: { $modified }
|
||||
item-accessed = Diakses: { $accessed }
|
||||
calculating = Menghitung...
|
||||
settings = Pengaturan
|
||||
single-click = Klik sekali untuk membuka
|
||||
appearance = Tampilan
|
||||
theme = Tema
|
||||
match-desktop = Cocokkan desktop
|
||||
dark = Gelap
|
||||
light = Terang
|
||||
type-to-search = Ketik untuk mencari
|
||||
type-to-search-recursive = Mencari di map saat ini dan semua submap
|
||||
type-to-search-enter-path = Memasukkan jalur ke direktori atau berkas
|
||||
add-to-sidebar = Tambahkan ke bilah sisi
|
||||
compress = Kompres
|
||||
eject = Keluarkan
|
||||
extract-here = Ekstrak
|
||||
new-file = Berkas baru...
|
||||
new-folder = Map baru...
|
||||
open-in-terminal = Buka di terminal
|
||||
move-to-trash = Pindahkan ke sampah
|
||||
restore-from-trash = Pulihkan dari sampah
|
||||
remove-from-sidebar = Hapus dari bilah sisi
|
||||
sort-by-name = Urutkan berdasarkan nama
|
||||
sort-by-modified = Urutkan berdasarkan dimodifikasi
|
||||
sort-by-size = Urutkan berdasarkan ukuran
|
||||
remove-from-recents = Hapus dari terbaru
|
||||
change-wallpaper = Ubah wallpaper...
|
||||
desktop-appearance = Tampilan desktop...
|
||||
display-settings = Pengaturan layar...
|
||||
file = Berkas
|
||||
new-tab = Tab baru
|
||||
new-window = Jendela baru
|
||||
reload-folder = Muat ulang map
|
||||
rename = Ganti nama...
|
||||
close-tab = Tutup tab
|
||||
quit = Keluar
|
||||
edit = Sunting
|
||||
cut = Potong
|
||||
copy = Salin
|
||||
paste = Tempel
|
||||
select-all = Pilih semua
|
||||
zoom-in = Perbesar
|
||||
default-size = Ukuran bawaan
|
||||
zoom-out = Perkecil
|
||||
view = Tampilan
|
||||
grid-view = Tampilan kisi
|
||||
list-view = Tampilan daftar
|
||||
gallery-preview = Tampilan galeri
|
||||
show-hidden-files = Tampilkan berkas tersembunyi
|
||||
list-directories-first = Daftar direktori terlebih dahulu
|
||||
menu-settings = Pengaturan...
|
||||
menu-about = Tentang Berkas COSMIC...
|
||||
sort = Urutkan
|
||||
sort-a-z = A-Z
|
||||
sort-z-a = Z-A
|
||||
sort-newest-first = Terbaru terlebih dahulu
|
||||
sort-oldest-first = Tertua terlebih dahulu
|
||||
sort-smallest-to-largest = Terkecil hingga terbesar
|
||||
sort-largest-to-smallest = Terbesar hingga terkecil
|
||||
selected-items = { $items } item yang dipilih
|
||||
type-to-search-select = Memilih berkas atau map pertama yang cocok
|
||||
|
|
@ -0,0 +1,306 @@
|
|||
cosmic-files = COSMIC файлдары
|
||||
empty-folder = Бос бума
|
||||
empty-folder-hidden = Бос бума (жасырын элементтері бар)
|
||||
no-results = Нәтижелер табылмады
|
||||
filesystem = Файлдық жүйе
|
||||
home = Үй
|
||||
networks = Желілер
|
||||
notification-in-progress = Файлдармен әрекеттер орындалуда
|
||||
trash = Қоқыс шелегі
|
||||
recents = Соңғылар
|
||||
undo = Болдырмау
|
||||
today = Бүгін
|
||||
desktop-view-options = Жұмыс үстелінің көрініс опциялары...
|
||||
show-on-desktop = Жұмыс үстелінде көрсету
|
||||
desktop-folder-content = Жұмыс үстелі бумасының мазмұны
|
||||
mounted-drives = Тіркелген дискілер
|
||||
trash-folder-icon = Қоқыс шелегі бумасының таңбашасы
|
||||
icon-size-and-spacing = Таңбаша өлшемі мен аралықтары
|
||||
icon-size = Таңбаша өлшемі
|
||||
grid-spacing = Тор аралықтары
|
||||
name = Аты
|
||||
modified = Өзгертілген
|
||||
trashed-on = Қоқыс шелегіне тасталған
|
||||
size = Өлшемі
|
||||
details = Ақпараты
|
||||
dismiss = Хабарламаны елемеу
|
||||
operations-running =
|
||||
{ $running } { $running ->
|
||||
[one] әрекет
|
||||
*[other] әрекет
|
||||
} орындалуда ({ $percent }%)...
|
||||
operations-running-finished =
|
||||
{ $running } { $running ->
|
||||
[one] әрекет
|
||||
*[other] әрекет
|
||||
} орындалуда ({ $percent }%), { $finished } аяқталды...
|
||||
pause = Аялдату
|
||||
resume = Жалғастыру
|
||||
create-archive = Архив жасау
|
||||
extract-password-required = Пароль керек
|
||||
extract-to = Шығару...
|
||||
extract-to-title = Бумаға шығару
|
||||
empty-trash = Себетті тазарту
|
||||
empty-trash-title = Себетті тазарту керек пе?
|
||||
empty-trash-warning = Себет бумасындағы элементтер біржола өшіріледі
|
||||
mount-error = Дискіге қол жеткізу мүмкін емес
|
||||
create-new-file = Жаңа файл жасау
|
||||
create-new-folder = Жаңа бума жасау
|
||||
file-name = Файл аты
|
||||
folder-name = Бума аты
|
||||
file-already-exists = Ондай аты бар файл бұрыннан бар
|
||||
folder-already-exists = Ондай аты бар бума бұрыннан бар
|
||||
name-hidden = "." таңбасынан басталатын атаулар жасырын болады
|
||||
name-invalid = Аты "{ $filename }" болуы мүмкін емес
|
||||
name-no-slashes = Атауда қиғаш сызықтар болмауы тиіс
|
||||
cancel = Бас тарту
|
||||
create = Жасау
|
||||
open = Ашу
|
||||
open-file = Файлды ашу
|
||||
open-folder = Буманы ашу
|
||||
open-in-new-tab = Жаңа бетте ашу
|
||||
open-in-new-window = Жаңа терезеде ашу
|
||||
open-item-location = Нысанның орнын ашу
|
||||
open-multiple-files = Бірнеше файлды ашу
|
||||
open-multiple-folders = Бірнеше буманы ашу
|
||||
save = Сақтау
|
||||
save-file = Файлды сақтау
|
||||
open-with-title = "{ $name }" қалай ашқыңыз келеді?
|
||||
browse-store = { $store } шолу
|
||||
other-apps = Басқа қолданбалар
|
||||
related-apps = Қатысты қолданбалар
|
||||
selected-items = Таңдалған { $items } нысан
|
||||
permanently-delete-question = Біржола өшіру керек пе?
|
||||
delete = Өшіру
|
||||
permanently-delete-warning = { $target } біржола өшіріледі. Бұл әрекетті болдырмау мүмкін емес.
|
||||
rename-file = Файлдың атын өзгерту
|
||||
rename-folder = Буманың атын өзгерту
|
||||
replace = Алмастыру
|
||||
replace-title = Бұл жерде "{ $filename }" бұрыннан бар
|
||||
replace-warning = Оны сақталып жатқан файлмен алмастыруды қалайсыз ба? Алмастыру кезінде оның мазмұны қайта жазылады.
|
||||
replace-warning-operation = Оны алмастыруды қалайсыз ба? Алмастыру кезінде оның мазмұны үстінен жазылады.
|
||||
original-file = Түпнұсқа файл
|
||||
replace-with = Келесімен алмастыру
|
||||
apply-to-all = Барлығына іске асыру
|
||||
keep-both = Екеуін де қалдыру
|
||||
skip = Өткізіп жіберу
|
||||
set-executable-and-launch = Орындалатын файл ретінде орнату және жөнелту
|
||||
set-executable-and-launch-description = "{ $name }" нысанын орындалатын файл ретінде орнатып, оны жөнелтуді қалайсыз ба?
|
||||
set-and-launch = Орнату және жөнелту
|
||||
open-with = Көмегімен ашу
|
||||
owner = Иесі
|
||||
group = Топ
|
||||
other = Басқа
|
||||
none = Ештеңе
|
||||
execute-only = Тек орындау
|
||||
write-only = Тек жазу
|
||||
write-execute = Жазу және орындау
|
||||
read-only = Тек оқу
|
||||
read-execute = Оқу және орындау
|
||||
read-write = Оқу және жазу
|
||||
read-write-execute = Оқу, жазу және орындау
|
||||
favorite-path-error = Буманы ашу қатесі
|
||||
favorite-path-error-description =
|
||||
"{ $path }" ашу мүмкін емес
|
||||
"{ $path }" жоқ болуы мүмкін немесе оны ашуға құқығыңыз жоқ
|
||||
|
||||
Оны бүйірлік панельден өшіруді қалайсыз ба?
|
||||
remove = Өшіру
|
||||
keep = Қалдыру
|
||||
repository = Репозиторий
|
||||
support = Қолдау
|
||||
add-network-drive = Желілік дискіні қосу
|
||||
connect = Байланысу
|
||||
connect-anonymously = Анонимді түрде байланысу
|
||||
connecting = Байланысуда...
|
||||
domain = Домен
|
||||
enter-server-address = Сервер адресін енгізіңіз
|
||||
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://
|
||||
network-drive-error = Желілік дискіге қол жеткізу мүмкін емес
|
||||
password = Пароль
|
||||
remember-password = Парольді есте сақтау
|
||||
try-again = Қайтадан көру
|
||||
username = Пайдаланушы аты
|
||||
cancelled = Бас тартылды
|
||||
edit-history = Тарихты түзету
|
||||
history = Тарихы
|
||||
no-history = Тарихта ешқандай элемент жоқ.
|
||||
pending = Күтілуде
|
||||
progress = { $percent }%
|
||||
progress-cancelled = { $percent }%, бас тартылды
|
||||
progress-failed = { $percent }%, сәтсіз аяқталды
|
||||
progress-paused = { $percent }%, аялдатылды
|
||||
failed = Сәтсіз аяқталды
|
||||
complete = Аяқталды
|
||||
compressing =
|
||||
{ $items } { $items ->
|
||||
[one] нәрсені
|
||||
*[other] нәрсені
|
||||
} "{ $from }" ішінен "{ $to }" ішіне сығу ({ $progress })...
|
||||
compressed =
|
||||
{ $items } { $items ->
|
||||
[one] нәрсе
|
||||
*[other] нәрсе
|
||||
} "{ $from }" ішінен "{ $to }" ішіне сығылды
|
||||
copy_noun = Көшіріп алу
|
||||
creating = "{ $parent }" ішінде "{ $name }" жасау
|
||||
created = "{ $parent }" ішінде "{ $name }" жасалды
|
||||
copying =
|
||||
{ $items } { $items ->
|
||||
[one] нәрсені
|
||||
*[other] нәрсені
|
||||
} "{ $from }" ішінен "{ $to }" ішіне көшіру ({ $progress })...
|
||||
copied =
|
||||
{ $items } { $items ->
|
||||
[one] нәрсе
|
||||
*[other] нәрсе
|
||||
} "{ $from }" ішінен "{ $to }" ішіне көшірілді
|
||||
deleting =
|
||||
{ $items } { $items ->
|
||||
[one] нәрсені
|
||||
*[other] нәрсені
|
||||
} { trash } ішінен өшіру ({ $progress })...
|
||||
deleted =
|
||||
{ $items } { $items ->
|
||||
[one] нәрсе
|
||||
*[other] нәрсе
|
||||
} { trash } ішінен өшірілді
|
||||
emptying-trash = { trash } тазартылуда ({ $progress })...
|
||||
emptied-trash = { trash } тазартылды
|
||||
extracting =
|
||||
{ $items } { $items ->
|
||||
[one] нәрсені
|
||||
*[other] нәрсені
|
||||
} "{ $from }" ішінен "{ $to }" ішіне тарқату ({ $progress })...
|
||||
extracted =
|
||||
{ $items } { $items ->
|
||||
[one] нәрсе
|
||||
*[other] нәрсе
|
||||
} "{ $from }" ішінен "{ $to }" ішіне тарқатылды
|
||||
setting-executable-and-launching = "{ $name }" орындалатын файл ретінде орнату және іске қосу
|
||||
set-executable-and-launched = "{ $name }" орындалатын файл ретінде орнатылды және іске қосылды
|
||||
setting-permissions = "{ $name }" үшін рұқсаттарды { $mode } мәніне орнату
|
||||
set-permissions = "{ $name }" үшін рұқсаттар { $mode } мәніне орнатылды
|
||||
moving =
|
||||
{ $items } { $items ->
|
||||
[one] нәрсені
|
||||
*[other] нәрсені
|
||||
} "{ $from }" ішінен "{ $to }" ішіне жылжыту ({ $progress })...
|
||||
moved =
|
||||
{ $items } { $items ->
|
||||
[one] нәрсе
|
||||
*[other] нәрсе
|
||||
} "{ $from }" ішінен "{ $to }" ішіне жылжытылды
|
||||
permanently-deleting =
|
||||
{ $items } { $items ->
|
||||
[one] нәрсені
|
||||
*[other] нәрсені
|
||||
} біржола өшіру
|
||||
permanently-deleted =
|
||||
{ $items } { $items ->
|
||||
[one] нәрсе
|
||||
*[other] нәрсе
|
||||
} біржола өшірілді
|
||||
removing-from-recents =
|
||||
{ $items } { $items ->
|
||||
[one] нәрсені
|
||||
*[other] нәрсені
|
||||
} { recents } тізімінен өшіру
|
||||
removed-from-recents =
|
||||
{ $items } { $items ->
|
||||
[one] нәрсе
|
||||
*[other] нәрсе
|
||||
} { recents } тізімінен өшірілді
|
||||
renaming = "{ $from }" атын "{ $to }" деп өзгерту
|
||||
renamed = "{ $from }" аты "{ $to }" деп өзгертілді
|
||||
restoring =
|
||||
{ $items } { $items ->
|
||||
[one] нәрсені
|
||||
*[other] нәрсені
|
||||
} { trash } ішінен қалпына келтіру ({ $progress })...
|
||||
restored =
|
||||
{ $items } { $items ->
|
||||
[one] нәрсе
|
||||
*[other] нәрсе
|
||||
} { trash } ішінен қалпына келтірілді
|
||||
unknown-folder = белгісіз бума
|
||||
menu-open-with = Көмегімен ашу...
|
||||
default-app = { $name } (әдепкі)
|
||||
show-details = Мәліметтерді көрсету
|
||||
type = Түрі: { $mime }
|
||||
items = Элементтер: { $items }
|
||||
item-size = Өлшемі: { $size }
|
||||
item-created = Жасалған: { $created }
|
||||
item-modified = Өзгертілген: { $modified }
|
||||
item-accessed = Қол жеткізілген: { $accessed }
|
||||
calculating = Есептеу...
|
||||
settings = Баптаулар
|
||||
single-click = Ашу үшін бір рет шерту
|
||||
appearance = Сыртқы түрі
|
||||
theme = Тақырып
|
||||
match-desktop = Жұмыс үстеліне сәйкес келу
|
||||
dark = Күңгірт
|
||||
light = Ашық
|
||||
type-to-search = Іздеу үшін теру
|
||||
type-to-search-recursive = Ағымдағы бума мен барлық ішкі бумаларды іздейді
|
||||
type-to-search-enter-path = Бумаға немесе файлға жолды енгізеді
|
||||
type-to-search-select = Бірінші сәйкес келетін файлды немесе буманы таңдайды
|
||||
add-to-sidebar = Бүйірлік панельге қосу
|
||||
compress = Сығу
|
||||
delete-permanently = Біржолата өшіру
|
||||
eject = Шығару
|
||||
extract-here = Тарқату
|
||||
new-file = Жаңа файл...
|
||||
new-folder = Жаңа бума...
|
||||
open-in-terminal = Терминалда ашу
|
||||
move-to-trash = Қоқыс жәшігіне тастау
|
||||
restore-from-trash = Қоқыс жәшігінен қалпына келтіру
|
||||
remove-from-sidebar = Бүйірлік панельден өшіру
|
||||
sort-by-name = Аты бойынша сұрыптау
|
||||
sort-by-modified = Өзгертілген уақыты бойынша сұрыптау
|
||||
sort-by-size = Өлшемі бойынша сұрыптау
|
||||
sort-by-trashed = Өшірілген уақыты бойынша сұрыптау
|
||||
remove-from-recents = Соңғылардан өшіру
|
||||
change-wallpaper = Тұсқағазды өзгерту...
|
||||
desktop-appearance = Жұмыс үстелінің сыртқы түрі...
|
||||
display-settings = Көрсету баптаулары...
|
||||
file = Файл
|
||||
new-tab = Жаңа бет
|
||||
new-window = Жаңа терезе
|
||||
reload-folder = Буманы қайта жүктеу
|
||||
rename = Атын өзгерту...
|
||||
close-tab = Бетті жабу
|
||||
quit = Шығу
|
||||
edit = Түзету
|
||||
cut = Қиып алу
|
||||
copy = Көшіру
|
||||
paste = Кірістіру
|
||||
select-all = Барлығын таңдау
|
||||
zoom-in = Үлкейту
|
||||
default-size = Әдепкі өлшем
|
||||
zoom-out = Кішірейту
|
||||
view = Көрініс
|
||||
grid-view = Тор көрінісі
|
||||
list-view = Тізім көрінісі
|
||||
show-hidden-files = Жасырын файлдарды көрсету
|
||||
list-directories-first = Алдымен бумаларды тізімдеу
|
||||
gallery-preview = Галереяны алдын ала қарау
|
||||
menu-settings = Баптаулар...
|
||||
menu-about = COSMIC файлдар туралы...
|
||||
sort = Сұрыптау
|
||||
sort-a-z = А-Я
|
||||
sort-z-a = Я-А
|
||||
sort-newest-first = Алдымен жаңалары
|
||||
sort-oldest-first = Алдымен ескілері
|
||||
sort-smallest-to-largest = Кішісінен үлкеніне
|
||||
sort-largest-to-smallest = Үлкенінен кішісіне
|
||||
|
|
@ -68,7 +68,7 @@ dark = 다크
|
|||
light = 라이트
|
||||
# Context menu
|
||||
new-file = 새 파일...
|
||||
new-folder = 새 폴더
|
||||
new-folder = 새 폴더...
|
||||
open-in-terminal = 터미널에서 열기
|
||||
move-to-trash = 휴지통으로 이동
|
||||
restore-from-trash = 휴지통에서 복구
|
||||
|
|
@ -84,7 +84,7 @@ sort-by-size = 크기 순으로 정렬
|
|||
file = 파일
|
||||
new-tab = 새 탭
|
||||
new-window = 새 창
|
||||
rename = 이름 바꾸기
|
||||
rename = 이름 바꾸기...
|
||||
close-tab = 탭 닫기
|
||||
quit = 종료
|
||||
|
||||
|
|
@ -102,7 +102,7 @@ view = 보기
|
|||
grid-view = 그리드 보기
|
||||
list-view = 목록 보기
|
||||
menu-settings = 설정...
|
||||
menu-about = 코스믹 파일에 대하여...
|
||||
menu-about = COSMIC 파일 정보...
|
||||
connect = 연결
|
||||
read-execute = 읽기 및 실행
|
||||
item-modified = 마지막 수정 일자: { $modified }
|
||||
|
|
@ -171,7 +171,7 @@ details = 세부 정보
|
|||
mounted-drives = 마운트된 드라이브
|
||||
mount-error = 드라이브에 접근할 수 없음
|
||||
extract-here = 압축 해제
|
||||
removed-from-recents = { recents }에서 { $items } 항목 제거됨
|
||||
removed-from-recents = { recents } 에서 { $items }개의 항목을 제거했습니다
|
||||
add-to-sidebar = 사이드 바에 추가
|
||||
item-created = 생성 일자: { $created }
|
||||
type-to-search-recursive = 현재 폴더와 하위 폴더 탐색
|
||||
|
|
@ -210,3 +210,64 @@ type-to-search = 입력하여 검색
|
|||
notification-in-progress = 파일 작업이 진행 중입니다
|
||||
permanently-delete-question = 완전히 삭제할까요?
|
||||
selected-items = { $items }개 항목 선택됨
|
||||
sort-newest-first = 새 항목 우선
|
||||
renamed = "{ $from }" 에서 "{ $to }" 로 이름 변경됨
|
||||
deleted = { trash } 에서 { $items }개의 항목을 제거했습니다
|
||||
reload-folder = 폴더 새로고침
|
||||
favorite-path-error = 디렉터리를 여는 중 오류가 발생했습니다
|
||||
remove-from-sidebar = 사이드 바에서 제거
|
||||
restoring = { trash } 에서 { $items }개의 항목을 복구 중 ({ $progress })...
|
||||
gallery-preview = 갤러리 미리보기
|
||||
sort-smallest-to-largest = 작은 항목부터 큰 항목
|
||||
zoom-in = 확대
|
||||
removing-from-recents = { recents } 에서 { $items }개의 항목을 제거 중
|
||||
zoom-out = 축소
|
||||
compressing = "{ $from }"에서 "{ $to }"(으)로 { $items }개의 항목을 압축 중({ $progress })...
|
||||
setting-executable-and-launching = "{ $name }"를 실행 가능으로 설정 및 실행 중
|
||||
default-size = 기본 크기
|
||||
extracted = "{ $from }"에서 "{ $to }"(으)로 { $items }개의 항목을 압축 해제했습니다
|
||||
permanently-deleting = { $items }개의 항목을 영구적으로 제거 중
|
||||
compressed = "{ $from }"에서 "{ $to }"(으)로 { $items }개의 항목을 압축했습니다
|
||||
grid-spacing = 그리드 간격
|
||||
copying = "{ $from }"에서 "{ $to }"(으)로 { $items }개의 항목을 복사 중({ $progress })...
|
||||
sort-oldest-first = 오래된 항목 우선
|
||||
sort-by-trashed = 삭제된 시간 순으로 정렬
|
||||
copied = "{ $from }"에서 "{ $to }"(으)로 { $items }개의 항목을 복사했습니다
|
||||
list-directories-first = 폴더 우선 나열
|
||||
remove-from-recents = 최근 항목에서 제거
|
||||
moving = "{ $from }"에서 "{ $to }"(으)로 { $items }개의 항목을 이동 중 ({ $progress })...
|
||||
change-wallpaper = 배경화면 변경...
|
||||
deleting = { trash } 에서 { $items }개의 항목을 제거 중({ $progress })...
|
||||
set-executable-and-launched = "{ $name }"를 실행 가능으로 설정 및 실행됨
|
||||
sort-a-z = A-Z
|
||||
set-and-launch = 설정 후 실행
|
||||
set-executable-and-launch = 실행 가능으로 설정 후 실행
|
||||
restored = { trash } 에서 { $items }개의 항목을 복구했습니다
|
||||
sort-z-a = Z-A
|
||||
operations-running-finished = { $running }개의 작업 진행 중 ({ $percent }%), { $finished } 완료됨...
|
||||
sort = 정렬
|
||||
show-hidden-files = 숨긴 파일 표시
|
||||
trash-folder-icon = 휴지통 아이콘
|
||||
extracting = "{ $from }"에서 "{ $to }"(으)로 { $items }개의 항목을 압축 해제 중 ({ $progress })...
|
||||
permanently-deleted = { $items }개의 항목을 영구적으로 제거했습니다
|
||||
renaming = "{ $from }" 에서 "{ $to }" 로 이름 변경 중
|
||||
set-executable-and-launch-description = "{ $name }"을 실행 가능으로 설정하고 실행할까요?
|
||||
sort-largest-to-smallest = 큰 항목부터 작은 항목
|
||||
moved = "{ $from }"에서 "{ $to }"(으)로 { $items }개의 항목을 이동했습니다
|
||||
display-settings = 화면 설정...
|
||||
desktop-appearance = 데스크톱 외관...
|
||||
favorite-path-error-description =
|
||||
"{ $path }"을(를) 열 수 없습니다
|
||||
"{ $path }"이(가) 존재하지 않거나 열기 권한이 없을 수 있습니다
|
||||
|
||||
사이드바에서 제거하시겠습니까?
|
||||
operations-running = { $running }개의 작업 진행 중 ({ $percent }%)...
|
||||
network-drive-schemes =
|
||||
지원 프로토콜,접두사(Prefix)
|
||||
AppleTalk,afp://
|
||||
파일 전송 프로토콜 (FTP),ftp:// 또는 ftps://
|
||||
네트워크 파일 시스템 (NFS),nfs://
|
||||
서버 메시지 블록 (SMB),smb://
|
||||
SSH 파일 전송 프로토콜 (SFTP),sftp:// 또는 ssh://
|
||||
WebDAV,dav:// 또는 davs://
|
||||
type-to-search-select = 일치하는 첫 번째 파일 또는 폴더를 선택합니다
|
||||
|
|
|
|||
0
i18n/ms/cosmic_files.ftl
Normal file
0
i18n/ms/cosmic_files.ftl
Normal file
|
|
@ -3,8 +3,8 @@ empty-folder = Lege map
|
|||
empty-folder-hidden = Lege map (met verborgen bestanden)
|
||||
no-results = Geen resultaten gevonden
|
||||
filesystem = Bestandssysteem
|
||||
home = Home
|
||||
networks = Netwerk
|
||||
home = Persoonlijke map
|
||||
networks = Netwerken
|
||||
notification-in-progress = Bestanden worden nog bewerkt
|
||||
trash = Prullenbak
|
||||
recents = Recente bestanden
|
||||
|
|
|
|||
|
|
@ -16,8 +16,8 @@ size = Розмір
|
|||
|
||||
## Empty Trash Dialog
|
||||
|
||||
empty-trash = Спорожнити Смітник
|
||||
empty-trash-warning = Ви впевнені, що хочете остаточно видалити всі елементи зі Смітника?
|
||||
empty-trash = Спорожнити смітник
|
||||
empty-trash-warning = Елементи зі смітника будуть остаточно видалені
|
||||
|
||||
## New File/Folder Dialog
|
||||
|
||||
|
|
@ -25,11 +25,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
|
||||
|
||||
|
|
@ -52,7 +52,7 @@ rename-folder = Перейменувати теку
|
|||
## Replace Dialog
|
||||
|
||||
replace = Замінити
|
||||
replace-title = " { $filename }" вже існує в цьому місці.
|
||||
replace-title = " { $filename }" вже існує в цьому місці
|
||||
replace-warning = Бажаєте замінити його тим, що зберігаєте? Замінювання перезапише його вміст.
|
||||
replace-warning-operation = Бажаєте замінити його? Замінювання перезапише його вміст.
|
||||
original-file = Початковий файл
|
||||
|
|
@ -185,7 +185,7 @@ remove = Вилучити
|
|||
cancelled = Скасовані
|
||||
no-results = Нічого не знайдено
|
||||
networks = Мережі
|
||||
notification-in-progress = Виконуються операції з файлами.
|
||||
notification-in-progress = Триває обробка файлів
|
||||
today = Сьогодні
|
||||
desktop-view-options = Параметри вигляду стільниці...
|
||||
show-on-desktop = Показувати на стільниці
|
||||
|
|
@ -219,9 +219,9 @@ open-with-title = Як ви бажаєте відкрити "{ $name }"?
|
|||
browse-store = Переглянути { $store }
|
||||
other-apps = Інші застосунки
|
||||
related-apps = Пов'язані застосунки
|
||||
permanently-delete-question = Вилучити остаточно
|
||||
permanently-delete-question = Остаточно видалити?
|
||||
delete = Вилучити
|
||||
permanently-delete-warning = Ви впевнені, що хочете остаточно вилучити { $target }? Дію неможливо скасувати.
|
||||
permanently-delete-warning = { $target } буде остаточно видалено. Цю дію не можна скасувати.
|
||||
set-executable-and-launch = Зробити виконуваним і запустити
|
||||
set-executable-and-launch-description = Бажаєте зробити "{ $name }" виконуваним і запустити його?
|
||||
set-and-launch = Зробити і запустити
|
||||
|
|
@ -239,8 +239,8 @@ read-write = Перегляд і запис
|
|||
read-write-execute = Перегляд, запис і виконання
|
||||
favorite-path-error = Помилка при відкритті каталогу
|
||||
favorite-path-error-description =
|
||||
Неможливо відкрити "{ $path }".
|
||||
Можливо, його не існує або у вас немає прав на відкриття.
|
||||
Неможливо відкрити "{ $path }"
|
||||
"{ $path }" можливо, його не існує або у вас немає прав на відкриття
|
||||
|
||||
Вилучити з бічної панелі?
|
||||
keep = Залишити
|
||||
|
|
@ -302,7 +302,7 @@ extracted =
|
|||
} з "{ $from }" до "{ $to }"
|
||||
setting-executable-and-launching = Встановлення "{ $name }" виконуваним і запуск
|
||||
set-executable-and-launched = Встановлено "{ $name }" виконуваним і запущено
|
||||
selected-items = { $items } обраних елементів
|
||||
selected-items = Вибрані { $items } елементи
|
||||
setting-permissions = Встановлення дозволів { $mode } для "{ $name }"
|
||||
set-permissions = Встановлено дозволи { $mode } для "{ $name }"
|
||||
show-details = Показати деталі
|
||||
|
|
@ -355,3 +355,5 @@ removed-from-recents =
|
|||
[one] елемент
|
||||
*[other] елементи
|
||||
} з { recents }
|
||||
empty-trash-title = Спорожити смітник?
|
||||
type-to-search-select = Вибирає перший відповідний файл або папку
|
||||
|
|
|
|||
0
i18n/uz/cosmic_files.ftl
Normal file
0
i18n/uz/cosmic_files.ftl
Normal file
282
src/app.rs
282
src/app.rs
|
|
@ -18,7 +18,9 @@ use cosmic::{
|
|||
Application, ApplicationExt, Element,
|
||||
app::{self, Core, Task, context_drawer},
|
||||
cosmic_config::{self, ConfigSet},
|
||||
cosmic_theme, executor,
|
||||
cosmic_theme,
|
||||
desktop::fde::DesktopEntry,
|
||||
executor,
|
||||
iced::{
|
||||
self, Alignment, Event, Length, Rectangle, Size, Subscription,
|
||||
clipboard::dnd::DndAction,
|
||||
|
|
@ -39,7 +41,7 @@ use cosmic::{
|
|||
dnd_destination::DragId,
|
||||
horizontal_space, icon,
|
||||
menu::{action::MenuAction, key_bind::KeyBind},
|
||||
segmented_button::{self, Entity},
|
||||
segmented_button::{self, Entity, ReorderEvent},
|
||||
vertical_space,
|
||||
},
|
||||
};
|
||||
|
|
@ -72,10 +74,10 @@ use crate::{
|
|||
FxOrderMap,
|
||||
clipboard::{ClipboardCopy, ClipboardKind, ClipboardPaste},
|
||||
config::{
|
||||
AppTheme, Config, DesktopConfig, Favorite, IconSizes, TIME_CONFIG_ID, TabConfig,
|
||||
AppTheme, Config, DesktopConfig, Favorite, IconSizes, State, TIME_CONFIG_ID, TabConfig,
|
||||
TimeConfig, TypeToSearch,
|
||||
},
|
||||
dialog::{Dialog, DialogKind, DialogMessage, DialogResult},
|
||||
dialog::{Dialog, DialogKind, DialogMessage, DialogResult, DialogSettings},
|
||||
fl, home_dir,
|
||||
key_bind::key_binds,
|
||||
localize::LANGUAGE_SORTER,
|
||||
|
|
@ -91,10 +93,6 @@ use crate::{
|
|||
tab::{
|
||||
self, HOVER_DURATION, HeadingOptions, ItemMetadata, Location, SORT_OPTION_FALLBACK, Tab,
|
||||
},
|
||||
};
|
||||
use crate::{
|
||||
config::State,
|
||||
dialog::DialogSettings,
|
||||
zoom::{zoom_in_view, zoom_out_view, zoom_to_default},
|
||||
};
|
||||
|
||||
|
|
@ -396,6 +394,7 @@ pub enum Message {
|
|||
PendingPauseAll(bool),
|
||||
PermanentlyDelete(Option<Entity>),
|
||||
Preview(Option<Entity>),
|
||||
ReorderTab(ReorderEvent),
|
||||
RescanRecents,
|
||||
RescanTrash,
|
||||
RemoveFromRecents(Option<Entity>),
|
||||
|
|
@ -444,7 +443,7 @@ pub enum Message {
|
|||
DndHoverTabTimeout(Entity),
|
||||
DndEnterNav(Entity),
|
||||
DndExitNav,
|
||||
DndEnterTab(Entity),
|
||||
DndEnterTab(Entity, Vec<String>),
|
||||
DndExitTab,
|
||||
DndDropTab(Entity, Option<ClipboardPaste>, DndAction),
|
||||
DndDropNav(Entity, Option<ClipboardPaste>, DndAction),
|
||||
|
|
@ -845,8 +844,8 @@ impl App {
|
|||
|
||||
fn launch_desktop_entries(paths: &[impl AsRef<Path>]) {
|
||||
for path in paths.iter().map(AsRef::as_ref) {
|
||||
match freedesktop_entry_parser::parse_entry(path) {
|
||||
Ok(entry) => match entry.section("Desktop Entry").attr("Exec") {
|
||||
match DesktopEntry::from_path::<&str>(path, None) {
|
||||
Ok(entry) => match entry.exec() {
|
||||
Some(exec) => match mime_app::exec_to_command(exec, &[] as &[&str; 0]) {
|
||||
Some(commands) => {
|
||||
for mut command in commands {
|
||||
|
|
@ -1229,12 +1228,12 @@ impl App {
|
|||
};
|
||||
for item in items {
|
||||
if item.selected {
|
||||
if let Some(path) = item.path_opt() {
|
||||
if op_sel.selected.contains(path) || op_sel.ignored.contains(path) {
|
||||
if let Some(path) = item.path_opt()
|
||||
&& (op_sel.selected.contains(path) || op_sel.ignored.contains(path))
|
||||
{
|
||||
// Ignore if path in selected or ignored paths
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// Return if there is a previous selection not matching
|
||||
return Task::none();
|
||||
|
|
@ -1890,13 +1889,13 @@ impl App {
|
|||
children.push(item.preview_view(Some(&self.mime_app_cache), military_time));
|
||||
}
|
||||
PreviewKind::Location(location) => {
|
||||
if let Some(tab) = self.tab_model.data::<Tab>(entity) {
|
||||
if let Some(items) = tab.items_opt() {
|
||||
if let Some(tab) = self.tab_model.data::<Tab>(entity)
|
||||
&& let Some(items) = tab.items_opt()
|
||||
{
|
||||
for item in items {
|
||||
if item.location_opt.as_ref() == Some(location) {
|
||||
children.push(
|
||||
item.preview_view(Some(&self.mime_app_cache), military_time),
|
||||
);
|
||||
children
|
||||
.push(item.preview_view(Some(&self.mime_app_cache), military_time));
|
||||
// Only show one property view to avoid issues like hangs when generating
|
||||
// preview images on thousands of files
|
||||
break;
|
||||
|
|
@ -1904,10 +1903,10 @@ impl App {
|
|||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
PreviewKind::Selected => {
|
||||
if let Some(tab) = self.tab_model.data::<Tab>(entity) {
|
||||
if let Some(items) = tab.items_opt() {
|
||||
if let Some(tab) = self.tab_model.data::<Tab>(entity)
|
||||
&& let Some(items) = tab.items_opt()
|
||||
{
|
||||
let preview_opt = {
|
||||
let mut selected = items.iter().filter(|item| item.selected);
|
||||
|
||||
|
|
@ -1915,9 +1914,9 @@ impl App {
|
|||
// At least two selected items
|
||||
(Some(_), Some(_)) => Some(tab.multi_preview_view()),
|
||||
// Exactly one selected item
|
||||
(Some(item), None) => Some(
|
||||
item.preview_view(Some(&self.mime_app_cache), military_time),
|
||||
),
|
||||
(Some(item), None) => {
|
||||
Some(item.preview_view(Some(&self.mime_app_cache), military_time))
|
||||
}
|
||||
// No selected items
|
||||
_ => None,
|
||||
}
|
||||
|
|
@ -1927,13 +1926,10 @@ impl App {
|
|||
children.push(preview);
|
||||
}
|
||||
|
||||
if children.is_empty() {
|
||||
if let Some(item) = &tab.parent_item_opt {
|
||||
children.push(
|
||||
item.preview_view(Some(&self.mime_app_cache), military_time),
|
||||
);
|
||||
}
|
||||
}
|
||||
if children.is_empty()
|
||||
&& let Some(item) = &tab.parent_item_opt
|
||||
{
|
||||
children.push(item.preview_view(Some(&self.mime_app_cache), military_time));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -2061,14 +2057,14 @@ impl App {
|
|||
.map(|favorite| {
|
||||
if let Favorite::Path(path) = favorite {
|
||||
for (from, to) in path_changes.iter().map(|(f, t)| (f.as_ref(), t.as_ref())) {
|
||||
if path.starts_with(from) {
|
||||
if let Ok(relative) = path.strip_prefix(from) {
|
||||
if path.starts_with(from)
|
||||
&& let Ok(relative) = path.strip_prefix(from)
|
||||
{
|
||||
favorites_changed = true;
|
||||
return Favorite::from_path(to.join(relative));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
favorite.clone()
|
||||
})
|
||||
.collect();
|
||||
|
|
@ -2234,9 +2230,10 @@ impl Application for App {
|
|||
let mut commands = vec![app.update_config()];
|
||||
|
||||
for location in flags.locations {
|
||||
if let Some(path) = location.path_opt() {
|
||||
if path.is_file() {
|
||||
if let Some(parent) = path.parent() {
|
||||
if let Some(path) = location.path_opt()
|
||||
&& path.is_file()
|
||||
&& let Some(parent) = path.parent()
|
||||
{
|
||||
commands.push(app.open_tab(
|
||||
Location::Path(parent.to_path_buf()),
|
||||
true,
|
||||
|
|
@ -2244,8 +2241,6 @@ impl Application for App {
|
|||
));
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
commands.push(app.open_tab(location, true, None));
|
||||
}
|
||||
for location in flags.uris {
|
||||
|
|
@ -2404,15 +2399,14 @@ impl Application for App {
|
|||
// TODO do we need to choose the correct mounter?
|
||||
self.mounter_items.keys().copied().next()
|
||||
})
|
||||
&& let Some(mounter) = MOUNTERS.get(&key)
|
||||
{
|
||||
if let Some(mounter) = MOUNTERS.get(&key) {
|
||||
return mounter.network_drive(uri.clone()).map(move |()| {
|
||||
cosmic::Action::App(Message::NetworkDriveOpenEntityAfterMount {
|
||||
entity,
|
||||
})
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
log::warn!(
|
||||
"failed to open favorite, path does not exist: {}",
|
||||
|
|
@ -2467,13 +2461,13 @@ impl Application for App {
|
|||
return self.update(message);
|
||||
}
|
||||
}
|
||||
if let Some(data) = self.nav_model.data::<MounterData>(entity) {
|
||||
if let Some(mounter) = MOUNTERS.get(&data.0) {
|
||||
if let Some(data) = self.nav_model.data::<MounterData>(entity)
|
||||
&& let Some(mounter) = MOUNTERS.get(&data.0)
|
||||
{
|
||||
return mounter
|
||||
.mount(data.1.clone())
|
||||
.map(|()| cosmic::action::none());
|
||||
}
|
||||
}
|
||||
Task::none()
|
||||
}
|
||||
|
||||
|
|
@ -2504,12 +2498,12 @@ impl Application for App {
|
|||
}
|
||||
|
||||
// Close gallery mode if open
|
||||
if let Some(tab) = self.tab_model.data_mut::<Tab>(entity) {
|
||||
if tab.gallery {
|
||||
if let Some(tab) = self.tab_model.data_mut::<Tab>(entity)
|
||||
&& tab.gallery
|
||||
{
|
||||
tab.gallery = false;
|
||||
return Task::none();
|
||||
}
|
||||
}
|
||||
|
||||
// Close menus and context panes in order per message
|
||||
// Why: It'd be weird to close everything all at once
|
||||
|
|
@ -2634,8 +2628,9 @@ impl Application for App {
|
|||
}
|
||||
Message::Compress(entity_opt) => {
|
||||
let paths: Box<[_]> = self.selected_paths(entity_opt).collect();
|
||||
if let Some(current_path) = paths.first() {
|
||||
if let Some(destination) = current_path.parent().zip(current_path.file_stem()) {
|
||||
if let Some(current_path) = paths.first()
|
||||
&& let Some(destination) = current_path.parent().zip(current_path.file_stem())
|
||||
{
|
||||
let to = destination.0.to_path_buf();
|
||||
let name = destination.1.to_str().unwrap_or_default().to_string();
|
||||
let archive_type = ArchiveType::default();
|
||||
|
|
@ -2651,7 +2646,6 @@ impl Application for App {
|
|||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
Message::Config(config) => {
|
||||
if config != self.config {
|
||||
log::info!("update config");
|
||||
|
|
@ -2663,11 +2657,11 @@ impl Application for App {
|
|||
}
|
||||
}
|
||||
Message::Copy(entity_opt) => {
|
||||
if let Some(entity) = entity_opt {
|
||||
if let Some(tab) = self.tab_model.data_mut::<Tab>(entity) {
|
||||
if let Some(entity) = entity_opt
|
||||
&& let Some(tab) = self.tab_model.data_mut::<Tab>(entity)
|
||||
{
|
||||
tab.refresh_cut(&[]);
|
||||
}
|
||||
}
|
||||
let paths = self.selected_paths(entity_opt);
|
||||
let contents = ClipboardCopy::new(ClipboardKind::Copy, paths);
|
||||
return clipboard::write_data(contents);
|
||||
|
|
@ -2974,15 +2968,15 @@ impl Application for App {
|
|||
DialogResult::Cancel => {}
|
||||
DialogResult::Open(selected_paths) => {
|
||||
let mut archive_paths = None;
|
||||
if let Some(file_dialog) = &self.file_dialog_opt {
|
||||
if let Some(window) = self.windows.remove(&file_dialog.window_id()) {
|
||||
if let WindowKind::FileDialog(paths) = window.kind {
|
||||
if let Some(file_dialog) = &self.file_dialog_opt
|
||||
&& let Some(window) = self.windows.remove(&file_dialog.window_id())
|
||||
&& let WindowKind::FileDialog(paths) = window.kind
|
||||
{
|
||||
archive_paths = paths;
|
||||
}
|
||||
}
|
||||
}
|
||||
if let Some(archive_paths) = archive_paths {
|
||||
if !selected_paths.is_empty() {
|
||||
if let Some(archive_paths) = archive_paths
|
||||
&& !selected_paths.is_empty()
|
||||
{
|
||||
self.file_dialog_opt = None;
|
||||
return self.operation(Operation::Extract {
|
||||
paths: archive_paths,
|
||||
|
|
@ -2992,7 +2986,6 @@ impl Application for App {
|
|||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
self.file_dialog_opt = None;
|
||||
}
|
||||
Message::FileDialogMessage(dialog_message) => {
|
||||
|
|
@ -3019,12 +3012,11 @@ impl Application for App {
|
|||
&& !modifiers.control()
|
||||
&& !modifiers.alt()
|
||||
&& matches!(key, Key::Character(_))
|
||||
&& let Some(text) = text
|
||||
{
|
||||
if let Some(text) = text {
|
||||
match self.config.type_to_search {
|
||||
TypeToSearch::Recursive => {
|
||||
let mut term =
|
||||
self.search_get().unwrap_or_default().to_string();
|
||||
let mut term = self.search_get().unwrap_or_default().to_string();
|
||||
term.push_str(&text);
|
||||
return self.search_set_active(Some(term));
|
||||
}
|
||||
|
|
@ -3041,8 +3033,7 @@ impl Application for App {
|
|||
tab.edit_location =
|
||||
Some(location.with_uri(uri_string).into());
|
||||
} else if let Some(path) = location.path_opt() {
|
||||
let mut path_string =
|
||||
path.to_string_lossy().into_owned();
|
||||
let mut path_string = path.to_string_lossy().into_owned();
|
||||
path_string.push_str(&text);
|
||||
tab.edit_location =
|
||||
Some(location.with_path(path_string.into()).into());
|
||||
|
|
@ -3051,11 +3042,11 @@ impl Application for App {
|
|||
}
|
||||
TypeToSearch::SelectByPrefix => {
|
||||
// Reset buffer if timeout elapsed
|
||||
if let Some(last_key) = self.type_select_last_key {
|
||||
if last_key.elapsed() >= tab::TYPE_SELECT_TIMEOUT {
|
||||
if let Some(last_key) = self.type_select_last_key
|
||||
&& last_key.elapsed() >= tab::TYPE_SELECT_TIMEOUT
|
||||
{
|
||||
self.type_select_prefix.clear();
|
||||
}
|
||||
}
|
||||
|
||||
// Accumulate character and select
|
||||
self.type_select_prefix.push_str(&text.to_lowercase());
|
||||
|
|
@ -3075,7 +3066,6 @@ impl Application for App {
|
|||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Message::MaybeExit => {
|
||||
if self.core.main_window_id().is_none() && self.pending_operations.is_empty() {
|
||||
// Exit if window is closed and there are no pending operations
|
||||
|
|
@ -3105,24 +3095,25 @@ impl Application for App {
|
|||
let mut unmounted = Vec::new();
|
||||
if let Some(old_items) = self.mounter_items.get(&mounter_key) {
|
||||
for old_item in old_items {
|
||||
if let Some(old_path) = old_item.path() {
|
||||
if old_item.is_mounted() {
|
||||
if let Some(old_path) = old_item.path()
|
||||
&& old_item.is_mounted()
|
||||
{
|
||||
let mut still_mounted = false;
|
||||
for item in &mounter_items {
|
||||
if let Some(path) = item.path() {
|
||||
if path == old_path && item.is_mounted() {
|
||||
if let Some(path) = item.path()
|
||||
&& path == old_path
|
||||
&& item.is_mounted()
|
||||
{
|
||||
still_mounted = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if !still_mounted {
|
||||
unmounted.push(old_path);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Go back to home in any tabs that were unmounted
|
||||
let mut commands = Vec::new();
|
||||
|
|
@ -3253,8 +3244,9 @@ impl Application for App {
|
|||
}
|
||||
Message::NewItem(entity_opt, dir) => {
|
||||
let entity = entity_opt.unwrap_or_else(|| self.tab_model.active());
|
||||
if let Some(tab) = self.tab_model.data_mut::<Tab>(entity) {
|
||||
if let Some(path) = tab.location.path_opt() {
|
||||
if let Some(tab) = self.tab_model.data_mut::<Tab>(entity)
|
||||
&& let Some(path) = tab.location.path_opt()
|
||||
{
|
||||
return Task::batch([
|
||||
self.dialog_pages.push_back(DialogPage::NewItem {
|
||||
parent: path.clone(),
|
||||
|
|
@ -3265,7 +3257,6 @@ impl Application for App {
|
|||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
#[cfg(feature = "notify")]
|
||||
Message::Notification(notification) => {
|
||||
self.notification_opt = Some(notification);
|
||||
|
|
@ -3276,8 +3267,9 @@ impl Application for App {
|
|||
let mut needs_reload = Vec::new();
|
||||
let entities: Box<[_]> = self.tab_model.iter().collect();
|
||||
for entity in entities {
|
||||
if let Some(tab) = self.tab_model.data_mut::<Tab>(entity) {
|
||||
if let Some(path) = tab.location.path_opt() {
|
||||
if let Some(tab) = self.tab_model.data_mut::<Tab>(entity)
|
||||
&& let Some(path) = tab.location.path_opt()
|
||||
{
|
||||
let mut contains_change = false;
|
||||
for event in &events {
|
||||
for event_path in &event.paths {
|
||||
|
|
@ -3329,7 +3321,6 @@ impl Application for App {
|
|||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let commands = needs_reload
|
||||
.into_iter()
|
||||
|
|
@ -3350,22 +3341,23 @@ impl Application for App {
|
|||
if let Some(terminal) = self.mime_app_cache.terminal() {
|
||||
let mut paths = Box::from([]);
|
||||
let entity = entity_opt.unwrap_or_else(|| self.tab_model.active());
|
||||
if let Some(tab) = self.tab_model.data_mut::<Tab>(entity) {
|
||||
if let Some(path) = tab.location.path_opt() {
|
||||
if let Some(tab) = self.tab_model.data_mut::<Tab>(entity)
|
||||
&& let Some(path) = tab.location.path_opt()
|
||||
{
|
||||
if let Some(items) = tab.items_opt() {
|
||||
paths =
|
||||
items
|
||||
paths = items
|
||||
.iter()
|
||||
.filter_map(|item| {
|
||||
.filter_map(
|
||||
|item| {
|
||||
if item.selected { item.path_opt() } else { None }
|
||||
})
|
||||
},
|
||||
)
|
||||
.collect();
|
||||
}
|
||||
if paths.is_empty() {
|
||||
paths = Box::from([path]);
|
||||
}
|
||||
}
|
||||
}
|
||||
for path in paths {
|
||||
if let Some(mut command) = terminal
|
||||
.command::<&str>(&[])
|
||||
|
|
@ -3453,8 +3445,9 @@ impl Application for App {
|
|||
},
|
||||
Message::OpenWithDialog(entity_opt) => {
|
||||
let entity = entity_opt.unwrap_or_else(|| self.tab_model.active());
|
||||
if let Some(tab) = self.tab_model.data::<Tab>(entity) {
|
||||
if let Some(items) = tab.items_opt() {
|
||||
if let Some(tab) = self.tab_model.data::<Tab>(entity)
|
||||
&& let Some(items) = tab.items_opt()
|
||||
{
|
||||
for item in items {
|
||||
if !item.selected {
|
||||
continue;
|
||||
|
|
@ -3479,7 +3472,6 @@ impl Application for App {
|
|||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Message::OpenWithSelection(index) => {
|
||||
if let Some(DialogPage::OpenWith { selected, .. }) = self.dialog_pages.front_mut() {
|
||||
*selected = index;
|
||||
|
|
@ -3487,21 +3479,20 @@ impl Application for App {
|
|||
}
|
||||
Message::Paste(entity_opt) => {
|
||||
let entity = entity_opt.unwrap_or_else(|| self.tab_model.active());
|
||||
if let Some(tab) = self.tab_model.data_mut::<Tab>(entity) {
|
||||
if let Some(path) = tab.location.path_opt() {
|
||||
if let Some(tab) = self.tab_model.data_mut::<Tab>(entity)
|
||||
&& let Some(path) = tab.location.path_opt()
|
||||
{
|
||||
let to = path.clone();
|
||||
return clipboard::read_data::<ClipboardPaste>().map(move |contents_opt| {
|
||||
match contents_opt {
|
||||
Some(contents) => cosmic::action::app(Message::PasteContents(
|
||||
to.clone(),
|
||||
contents,
|
||||
)),
|
||||
Some(contents) => {
|
||||
cosmic::action::app(Message::PasteContents(to.clone(), contents))
|
||||
}
|
||||
None => cosmic::action::none(),
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
Message::PasteContents(to, mut contents) => {
|
||||
contents.paths.retain(|p| *p != to);
|
||||
if !contents.paths.is_empty() {
|
||||
|
|
@ -3732,8 +3723,9 @@ impl Application for App {
|
|||
}
|
||||
Message::Rename(entity_opt) => {
|
||||
let entity = entity_opt.unwrap_or_else(|| self.tab_model.active());
|
||||
if let Some(tab) = self.tab_model.data_mut::<Tab>(entity) {
|
||||
if let Some(items) = tab.items_opt() {
|
||||
if let Some(tab) = self.tab_model.data_mut::<Tab>(entity)
|
||||
&& let Some(items) = tab.items_opt()
|
||||
{
|
||||
let selected: Box<[_]> = items
|
||||
.iter()
|
||||
.filter_map(|item| {
|
||||
|
|
@ -3766,7 +3758,6 @@ impl Application for App {
|
|||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Message::ReplaceResult(replace_result) => {
|
||||
if let Some((dialog_page, task)) = self.dialog_pages.pop_front() {
|
||||
match dialog_page {
|
||||
|
|
@ -3786,8 +3777,9 @@ impl Application for App {
|
|||
Message::RestoreFromTrash(entity_opt) => {
|
||||
let mut trash_items = Vec::new();
|
||||
let entity = entity_opt.unwrap_or_else(|| self.tab_model.active());
|
||||
if let Some(tab) = self.tab_model.data_mut::<Tab>(entity) {
|
||||
if let Some(items) = tab.items_opt() {
|
||||
if let Some(tab) = self.tab_model.data_mut::<Tab>(entity)
|
||||
&& let Some(items) = tab.items_opt()
|
||||
{
|
||||
for item in items {
|
||||
if item.selected {
|
||||
if let ItemMetadata::Trash { entry, .. } = &item.metadata {
|
||||
|
|
@ -3798,7 +3790,6 @@ impl Application for App {
|
|||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if !trash_items.is_empty() {
|
||||
return self.operation(Operation::Restore { items: trash_items });
|
||||
}
|
||||
|
|
@ -3839,14 +3830,14 @@ impl Application for App {
|
|||
|
||||
// Close old context menu
|
||||
let active = self.tab_model.active();
|
||||
if let Some(tab) = self.tab_model.data_mut::<Tab>(active) {
|
||||
if tab.context_menu.is_some() {
|
||||
if let Some(tab) = self.tab_model.data_mut::<Tab>(active)
|
||||
&& tab.context_menu.is_some()
|
||||
{
|
||||
tasks.push(self.update(Message::TabMessage(
|
||||
Some(active),
|
||||
tab::Message::ContextMenu(None, None),
|
||||
)));
|
||||
}
|
||||
}
|
||||
|
||||
// Activate new tab
|
||||
self.tab_model.activate(entity);
|
||||
|
|
@ -3937,7 +3928,6 @@ impl Application for App {
|
|||
config.show_hidden = !config.show_hidden;
|
||||
return self.update(Message::TabConfig(config));
|
||||
}
|
||||
|
||||
Message::TabMessage(entity_opt, tab_message) => {
|
||||
let entity = entity_opt.unwrap_or_else(|| self.tab_model.active());
|
||||
|
||||
|
|
@ -4042,12 +4032,12 @@ impl Application for App {
|
|||
// Destroy previous popup
|
||||
let mut window_ids = Vec::new();
|
||||
for (window_id, window) in &self.windows {
|
||||
if let WindowKind::ContextMenu(e, _) = &window.kind {
|
||||
if *e == entity {
|
||||
if let WindowKind::ContextMenu(e, _) = &window.kind
|
||||
&& *e == entity
|
||||
{
|
||||
window_ids.push(*window_id);
|
||||
}
|
||||
}
|
||||
}
|
||||
for window_id in window_ids {
|
||||
commands.push(self.update(Message::Surface(
|
||||
cosmic::surface::action::destroy_popup(window_id),
|
||||
|
|
@ -4402,12 +4392,14 @@ impl Application for App {
|
|||
}
|
||||
}
|
||||
}
|
||||
Message::DndEnterTab(entity) => {
|
||||
Message::DndEnterTab(entity, mimes) => {
|
||||
if mimes.iter().all(|m| m.as_str() != "x-cosmic-files/tab-dnd") {
|
||||
self.tab_dnd_hover = Some((entity, Instant::now()));
|
||||
return Task::perform(tokio::time::sleep(HOVER_DURATION), move |()| {
|
||||
cosmic::Action::App(Message::DndHoverTabTimeout(entity))
|
||||
});
|
||||
}
|
||||
}
|
||||
Message::DndExitTab => {
|
||||
self.nav_dnd_hover = None;
|
||||
}
|
||||
|
|
@ -4451,14 +4443,14 @@ impl Application for App {
|
|||
}
|
||||
}
|
||||
Message::NavBarClose(entity) => {
|
||||
if let Some(data) = self.nav_model.data::<MounterData>(entity) {
|
||||
if let Some(mounter) = MOUNTERS.get(&data.0) {
|
||||
if let Some(data) = self.nav_model.data::<MounterData>(entity)
|
||||
&& let Some(mounter) = MOUNTERS.get(&data.0)
|
||||
{
|
||||
return mounter
|
||||
.unmount(data.1.clone())
|
||||
.map(|()| cosmic::action::none());
|
||||
}
|
||||
}
|
||||
}
|
||||
Message::NavBarContext(entity) => {
|
||||
self.nav_bar_context_id = entity;
|
||||
|
||||
|
|
@ -4753,8 +4745,8 @@ impl Application for App {
|
|||
if let Some(p) = paths.next() {
|
||||
{
|
||||
for (k, mounter_items) in &self.mounter_items {
|
||||
if let Some(mounter) = MOUNTERS.get(k) {
|
||||
if let Some(item) = mounter_items
|
||||
if let Some(mounter) = MOUNTERS.get(k)
|
||||
&& let Some(item) = mounter_items
|
||||
.iter()
|
||||
.find(|&item| item.path().is_some_and(|path| path == p))
|
||||
{
|
||||
|
|
@ -4767,7 +4759,6 @@ impl Application for App {
|
|||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
#[cfg(all(feature = "wayland", feature = "desktop-applet"))]
|
||||
Message::Focused(id) => {
|
||||
if let Some(w) = self.windows.get(&id) {
|
||||
|
|
@ -4784,8 +4775,8 @@ impl Application for App {
|
|||
}
|
||||
Message::SaveSortNames => {
|
||||
self.must_save_sort_names = false;
|
||||
if let Some(state_handler) = self.state_handler.as_ref() {
|
||||
if let Err(err) = state_handler
|
||||
if let Some(state_handler) = self.state_handler.as_ref()
|
||||
&& let Err(err) = state_handler
|
||||
.set::<&FxOrderMap<String, (HeadingOptions, bool)>>(
|
||||
"sort_names",
|
||||
&self.state.sort_names,
|
||||
|
|
@ -4794,13 +4785,19 @@ impl Application for App {
|
|||
log::warn!("Failed to save sort names: {err:?}");
|
||||
}
|
||||
}
|
||||
}
|
||||
Message::NetworkDriveOpenEntityAfterMount { entity } => {
|
||||
return self.on_nav_select(entity);
|
||||
}
|
||||
Message::NetworkDriveOpenTabAfterMount { location } => {
|
||||
return self.open_tab(location, false, None);
|
||||
}
|
||||
Message::ReorderTab(ReorderEvent {
|
||||
dragged,
|
||||
target,
|
||||
position,
|
||||
}) => {
|
||||
_ = self.tab_model.reorder(dragged, target, position);
|
||||
}
|
||||
}
|
||||
|
||||
Task::none()
|
||||
|
|
@ -4881,14 +4878,14 @@ impl Application for App {
|
|||
fn dialog(&self) -> Option<Element<'_, Message>> {
|
||||
//TODO: should gallery view just be a dialog?
|
||||
let entity = self.tab_model.active();
|
||||
if let Some(tab) = self.tab_model.data::<Tab>(entity) {
|
||||
if tab.gallery {
|
||||
if let Some(tab) = self.tab_model.data::<Tab>(entity)
|
||||
&& tab.gallery
|
||||
{
|
||||
return Some(
|
||||
tab.gallery_view()
|
||||
.map(move |x| Message::TabMessage(Some(entity), x)),
|
||||
);
|
||||
}
|
||||
}
|
||||
let dialog_page = self.dialog_pages.front()?;
|
||||
|
||||
let cosmic_theme::Spacing {
|
||||
|
|
@ -5770,8 +5767,9 @@ impl Application for App {
|
|||
|
||||
let mut tab_column = widget::column::with_capacity(4);
|
||||
|
||||
if self.core.is_condensed() {
|
||||
if let Some(term) = self.search_get() {
|
||||
if self.core.is_condensed()
|
||||
&& let Some(term) = self.search_get()
|
||||
{
|
||||
tab_column = tab_column.push(
|
||||
widget::container(
|
||||
widget::text_input::search_input("", term)
|
||||
|
|
@ -5783,7 +5781,6 @@ impl Application for App {
|
|||
.padding(space_xxs),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if self.tab_model.len() > 1 {
|
||||
tab_column = tab_column.push(
|
||||
|
|
@ -5791,9 +5788,12 @@ impl Application for App {
|
|||
widget::tab_bar::horizontal(&self.tab_model)
|
||||
.button_height(32)
|
||||
.button_spacing(space_xxs)
|
||||
.enable_tab_drag(String::from("x-cosmic-files/tab-dnd"))
|
||||
.on_reorder(move |event| Message::ReorderTab(event))
|
||||
.tab_drag_threshold(25.)
|
||||
.on_activate(Message::TabActivate)
|
||||
.on_close(|entity| Message::TabClose(Some(entity)))
|
||||
.on_dnd_enter(|entity, _| Message::DndEnterTab(entity))
|
||||
.on_dnd_enter(|entity, mimes| Message::DndEnterTab(entity, mimes))
|
||||
.on_dnd_leave(|_| Message::DndExitTab)
|
||||
.on_dnd_drop(|entity, data, action| {
|
||||
Message::DndDropTab(entity, data, action)
|
||||
|
|
@ -6077,16 +6077,16 @@ impl Application for App {
|
|||
let should_rescan =
|
||||
events.iter().any(|event| !event.kind.is_access());
|
||||
|
||||
if should_rescan {
|
||||
if let Err(e) = futures::executor::block_on(async {
|
||||
if should_rescan
|
||||
&& let Err(e) = futures::executor::block_on(async {
|
||||
output.send(Message::RescanTrash).await
|
||||
}) {
|
||||
})
|
||||
{
|
||||
log::warn!(
|
||||
"trash needs to be rescanned but sending message failed: {e:?}"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
log::warn!("failed to watch trash bin for changes: {e:?}");
|
||||
}
|
||||
|
|
@ -6162,8 +6162,7 @@ impl Application for App {
|
|||
|| kind.is_modify()
|
||||
|| kind.is_remove()
|
||||
|| kind.is_other()
|
||||
}) {
|
||||
if let Err(e) = futures::executor::block_on(async {
|
||||
}) && let Err(e) = futures::executor::block_on(async {
|
||||
output.send(Message::RescanRecents).await
|
||||
}) {
|
||||
log::warn!(
|
||||
|
|
@ -6171,7 +6170,6 @@ impl Application for App {
|
|||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
log::warn!("failed to watch recents file for changes: {e:?}")
|
||||
}
|
||||
|
|
@ -6286,17 +6284,16 @@ impl Application for App {
|
|||
let mut selected_previews = Vec::new();
|
||||
match self.mode {
|
||||
Mode::App => {
|
||||
if self.core.window.show_context {
|
||||
if let ContextPage::Preview(entity_opt, PreviewKind::Selected) =
|
||||
if self.core.window.show_context
|
||||
&& let ContextPage::Preview(entity_opt, PreviewKind::Selected) =
|
||||
self.context_page
|
||||
{
|
||||
selected_previews
|
||||
.push(Some(entity_opt.unwrap_or_else(|| self.tab_model.active())));
|
||||
}
|
||||
}
|
||||
}
|
||||
Mode::Desktop => {
|
||||
for window_kind in self.windows.iter().map(|(_, window)| &window.kind) {
|
||||
for window_kind in self.windows.values().map(|window| &window.kind) {
|
||||
if let WindowKind::Preview(entity_opt, _) = window_kind {
|
||||
selected_previews
|
||||
.push(Some(entity_opt.unwrap_or_else(|| self.tab_model.active())));
|
||||
|
|
@ -6311,8 +6308,7 @@ impl Application for App {
|
|||
tab.subscription(
|
||||
selected_previews
|
||||
.iter()
|
||||
.find(|preview| preview.as_ref() == Some(entity).as_ref())
|
||||
.is_some(),
|
||||
.any(|preview| preview.as_ref() == Some(entity).as_ref()),
|
||||
)
|
||||
.with(entity)
|
||||
.map(|(entity, tab_msg)| Message::TabMessage(Some(entity), tab_msg)),
|
||||
|
|
|
|||
105
src/archive.rs
105
src/archive.rs
|
|
@ -1,15 +1,15 @@
|
|||
use std::{
|
||||
collections::VecDeque,
|
||||
fs,
|
||||
io::{self, Read, Write},
|
||||
path::Path,
|
||||
};
|
||||
use zip::result::ZipError;
|
||||
|
||||
use crate::{
|
||||
mime_icon::mime_for_path,
|
||||
operation::{Controller, OpReader, OperationError, OperationErrorType},
|
||||
operation::{Controller, OpReader, OperationError, OperationErrorType, sync_to_disk},
|
||||
};
|
||||
use cosmic::iced::futures;
|
||||
use std::{
|
||||
collections::HashSet,
|
||||
fs,
|
||||
io::{self, Read, Write},
|
||||
path::{Path, PathBuf},
|
||||
};
|
||||
use zip::result::ZipError;
|
||||
|
||||
pub const SUPPORTED_ARCHIVE_TYPES: &[&str] = &[
|
||||
"application/gzip",
|
||||
|
|
@ -113,27 +113,36 @@ fn zip_extract<R: io::Read + io::Seek, P: AsRef<Path>>(
|
|||
use std::{ffi::OsString, fs};
|
||||
use zip::result::ZipError;
|
||||
|
||||
fn make_writable_dir_all<T: AsRef<Path>>(outpath: T) -> Result<(), ZipError> {
|
||||
fs::create_dir_all(outpath.as_ref())?;
|
||||
fn make_writable_dir_all<T: AsRef<Path>>(
|
||||
outpath: T,
|
||||
target_dirs: &mut HashSet<PathBuf>,
|
||||
) -> Result<(), ZipError> {
|
||||
let path = outpath.as_ref();
|
||||
if !path.exists() {
|
||||
fs::create_dir_all(path)?;
|
||||
}
|
||||
if !target_dirs.contains(path) {
|
||||
target_dirs.insert(path.to_path_buf());
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
{
|
||||
// Dirs must be writable until all normal files are extracted
|
||||
use std::os::unix::fs::PermissionsExt;
|
||||
std::fs::set_permissions(
|
||||
outpath.as_ref(),
|
||||
std::fs::Permissions::from_mode(
|
||||
0o700 | std::fs::metadata(outpath.as_ref())?.permissions().mode(),
|
||||
),
|
||||
fs::set_permissions(
|
||||
path,
|
||||
fs::Permissions::from_mode(0o700 | fs::metadata(path)?.permissions().mode()),
|
||||
)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
let mut files_by_unix_mode = Vec::new();
|
||||
let mut buffer = vec![0; 4 * 1024 * 1024];
|
||||
let total_files = archive.len();
|
||||
let mut pending_directory_creates = VecDeque::new();
|
||||
let mut written_files = Vec::with_capacity(total_files);
|
||||
let mut target_dirs = HashSet::new();
|
||||
#[cfg(unix)]
|
||||
let mut files_by_unix_mode = Vec::with_capacity(total_files);
|
||||
|
||||
for i in 0..total_files {
|
||||
futures::executor::block_on(async {
|
||||
|
|
@ -143,7 +152,7 @@ fn zip_extract<R: io::Read + io::Seek, P: AsRef<Path>>(
|
|||
.map_err(|s| io::Error::other(OperationError::from_state(s, &controller)))
|
||||
})?;
|
||||
|
||||
controller.set_progress((i as f32) / total_files as f32);
|
||||
controller.set_progress(i as f32 / total_files as f32);
|
||||
|
||||
let mut file = match password {
|
||||
None => archive.by_index(i),
|
||||
|
|
@ -156,26 +165,22 @@ fn zip_extract<R: io::Read + io::Seek, P: AsRef<Path>>(
|
|||
let outpath = directory.as_ref().join(filepath);
|
||||
|
||||
if file.is_dir() {
|
||||
pending_directory_creates.push_back(outpath.clone());
|
||||
make_writable_dir_all(&outpath, &mut target_dirs)?;
|
||||
|
||||
#[cfg(unix)]
|
||||
if let Some(mode) = file.unix_mode() {
|
||||
files_by_unix_mode.push((outpath, mode));
|
||||
}
|
||||
continue;
|
||||
}
|
||||
let symlink_target = if file.is_symlink() && (cfg!(unix) || cfg!(windows)) {
|
||||
let mut target = Vec::with_capacity(file.size() as usize);
|
||||
file.read_to_end(&mut target)?;
|
||||
Some(target)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
drop(file);
|
||||
if let Some(target) = symlink_target {
|
||||
// create all pending dirs
|
||||
while let Some(pending_dir) = pending_directory_creates.pop_front() {
|
||||
make_writable_dir_all(pending_dir)?;
|
||||
|
||||
if let Some(parent) = outpath.parent() {
|
||||
make_writable_dir_all(parent, &mut target_dirs)?;
|
||||
}
|
||||
|
||||
if let Some(p) = outpath.parent() {
|
||||
make_writable_dir_all(p)?;
|
||||
}
|
||||
if file.is_symlink() && (cfg!(unix) || cfg!(windows)) {
|
||||
let mut target = Vec::with_capacity(file.size() as usize);
|
||||
file.read_to_end(&mut target)?;
|
||||
|
||||
#[cfg(unix)]
|
||||
{
|
||||
|
|
@ -205,21 +210,10 @@ fn zip_extract<R: io::Read + io::Seek, P: AsRef<Path>>(
|
|||
std::os::windows::fs::symlink_file(target_path, outpath.as_path())?;
|
||||
}
|
||||
}
|
||||
|
||||
written_files.push(outpath);
|
||||
continue;
|
||||
}
|
||||
let mut file = match password {
|
||||
None => archive.by_index(i),
|
||||
Some(pwd) => archive.by_index_decrypt(i, pwd.as_bytes()),
|
||||
}?;
|
||||
|
||||
// create all pending dirs
|
||||
while let Some(pending_dir) = pending_directory_creates.pop_front() {
|
||||
make_writable_dir_all(pending_dir)?;
|
||||
}
|
||||
|
||||
if let Some(p) = outpath.parent() {
|
||||
make_writable_dir_all(p)?;
|
||||
}
|
||||
|
||||
let total = file.size();
|
||||
let mut outfile = fs::File::create(&outpath)?;
|
||||
|
|
@ -245,13 +239,14 @@ fn zip_extract<R: io::Read + io::Seek, P: AsRef<Path>>(
|
|||
controller.set_progress(total_progress);
|
||||
}
|
||||
}
|
||||
#[cfg(unix)]
|
||||
{
|
||||
|
||||
// Check for real permissions, which we'll set in a second pass
|
||||
#[cfg(unix)]
|
||||
if let Some(mode) = file.unix_mode() {
|
||||
files_by_unix_mode.push((outpath.clone(), mode));
|
||||
}
|
||||
}
|
||||
|
||||
written_files.push(outpath);
|
||||
}
|
||||
#[cfg(unix)]
|
||||
{
|
||||
|
|
@ -260,11 +255,15 @@ fn zip_extract<R: io::Read + io::Seek, P: AsRef<Path>>(
|
|||
|
||||
if files_by_unix_mode.len() > 1 {
|
||||
// Ensure we update children's permissions before making a parent unwritable
|
||||
files_by_unix_mode.sort_by_key(|(path, _)| Reverse(path.clone()));
|
||||
files_by_unix_mode.sort_by_key(|(path, _)| Reverse(path.components().count()));
|
||||
}
|
||||
for (path, mode) in files_by_unix_mode {
|
||||
fs::set_permissions(&path, fs::Permissions::from_mode(mode))?;
|
||||
}
|
||||
}
|
||||
|
||||
// Flush files to disk
|
||||
futures::executor::block_on(async { sync_to_disk(written_files, target_dirs).await });
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
|||
106
src/dialog.rs
106
src/dialog.rs
|
|
@ -179,12 +179,12 @@ impl<T: AsRef<str>> From<T> for DialogLabel {
|
|||
});
|
||||
}
|
||||
|
||||
if let Some(span) = spans.last_mut() {
|
||||
if underline == span.underline {
|
||||
if let Some(span) = spans.last_mut()
|
||||
&& underline == span.underline
|
||||
{
|
||||
span.text.push(c);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
spans.push(DialogLabelSpan {
|
||||
text: String::from(c),
|
||||
|
|
@ -718,14 +718,14 @@ impl App {
|
|||
children.push(preview);
|
||||
}
|
||||
|
||||
if children.is_empty() {
|
||||
if let Some(item) = &self.tab.parent_item_opt {
|
||||
if children.is_empty()
|
||||
&& let Some(item) = &self.tab.parent_item_opt
|
||||
{
|
||||
children.push(item.preview_view(None, military_time));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
widget::column::with_children(children).into()
|
||||
}
|
||||
|
||||
|
|
@ -1279,13 +1279,13 @@ impl Application for App {
|
|||
return self.update(message);
|
||||
}
|
||||
|
||||
if let Some(data) = self.nav_model.data::<MounterData>(entity) {
|
||||
if let Some(mounter) = MOUNTERS.get(&data.0) {
|
||||
if let Some(data) = self.nav_model.data::<MounterData>(entity)
|
||||
&& let Some(mounter) = MOUNTERS.get(&data.0)
|
||||
{
|
||||
return mounter
|
||||
.mount(data.1.clone())
|
||||
.map(|()| cosmic::action::none());
|
||||
}
|
||||
}
|
||||
Task::none()
|
||||
}
|
||||
|
||||
|
|
@ -1322,11 +1322,11 @@ impl Application for App {
|
|||
|
||||
// Close the dialog if the focused widget is the dialog's main text input instead of
|
||||
// unfocussing the widget.
|
||||
if let operation::Outcome::Some(focused) = operation::focusable::find_focused().finish() {
|
||||
if self.dialog_text_input == focused {
|
||||
if let operation::Outcome::Some(focused) = operation::focusable::find_focused().finish()
|
||||
&& self.dialog_text_input == focused
|
||||
{
|
||||
return self.update(Message::Cancel);
|
||||
}
|
||||
}
|
||||
|
||||
self.update(Message::Cancel)
|
||||
}
|
||||
|
|
@ -1419,23 +1419,23 @@ impl Application for App {
|
|||
}
|
||||
|
||||
// Check key binds from accept label
|
||||
if let Some(key_bind) = &self.accept_label.key_bind_opt {
|
||||
if key_bind.matches(modifiers, &key) {
|
||||
if let Some(key_bind) = &self.accept_label.key_bind_opt
|
||||
&& key_bind.matches(modifiers, &key)
|
||||
{
|
||||
return self.update(if self.flags.kind.save() {
|
||||
Message::Save(false)
|
||||
} else {
|
||||
Message::Open
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Uncaptured keys with only shift modifiers go to the search or location box
|
||||
if !modifiers.logo()
|
||||
&& !modifiers.control()
|
||||
&& !modifiers.alt()
|
||||
&& matches!(key, Key::Character(_))
|
||||
&& let Some(text) = text
|
||||
{
|
||||
if let Some(text) = text {
|
||||
match self.flags.config.type_to_search {
|
||||
TypeToSearch::Recursive => {
|
||||
let mut term = self.search_get().unwrap_or_default().to_string();
|
||||
|
|
@ -1456,11 +1456,11 @@ impl Application for App {
|
|||
}
|
||||
TypeToSearch::SelectByPrefix => {
|
||||
// Reset buffer if timeout elapsed
|
||||
if let Some(last_key) = self.type_select_last_key {
|
||||
if last_key.elapsed() >= tab::TYPE_SELECT_TIMEOUT {
|
||||
if let Some(last_key) = self.type_select_last_key
|
||||
&& last_key.elapsed() >= tab::TYPE_SELECT_TIMEOUT
|
||||
{
|
||||
self.type_select_prefix.clear();
|
||||
}
|
||||
}
|
||||
|
||||
// Accumulate character and select
|
||||
self.type_select_prefix.push_str(&text.to_lowercase());
|
||||
|
|
@ -1477,7 +1477,6 @@ impl Application for App {
|
|||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Message::ModifiersChanged(modifiers) => {
|
||||
self.modifiers = modifiers;
|
||||
}
|
||||
|
|
@ -1486,24 +1485,25 @@ impl Application for App {
|
|||
let mut unmounted = Vec::new();
|
||||
if let Some(old_items) = self.mounter_items.get(&mounter_key) {
|
||||
for old_item in old_items {
|
||||
if let Some(old_path) = old_item.path() {
|
||||
if old_item.is_mounted() {
|
||||
if let Some(old_path) = old_item.path()
|
||||
&& old_item.is_mounted()
|
||||
{
|
||||
let mut still_mounted = false;
|
||||
for item in &mounter_items {
|
||||
if let Some(path) = item.path() {
|
||||
if path == old_path && item.is_mounted() {
|
||||
if let Some(path) = item.path()
|
||||
&& path == old_path
|
||||
&& item.is_mounted()
|
||||
{
|
||||
still_mounted = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if !still_mounted {
|
||||
unmounted.push(Location::Path(old_path));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Go back to home in any tabs that were unmounted
|
||||
let mut commands = Vec::new();
|
||||
|
|
@ -1601,8 +1601,9 @@ impl Application for App {
|
|||
let mut paths = Vec::new();
|
||||
if let Some(items) = self.tab.items_opt() {
|
||||
for item in items {
|
||||
if item.selected {
|
||||
if let Some(path) = item.path_opt() {
|
||||
if item.selected
|
||||
&& let Some(path) = item.path_opt()
|
||||
{
|
||||
paths.push(path.clone());
|
||||
let _ = update_recently_used(
|
||||
path,
|
||||
|
|
@ -1613,7 +1614,6 @@ impl Application for App {
|
|||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure selection is allowed
|
||||
//TODO: improve tab logic so this doesn't block the open button so often
|
||||
|
|
@ -1640,13 +1640,13 @@ impl Application for App {
|
|||
}
|
||||
|
||||
// If we are in directory mode, return the current directory
|
||||
if self.flags.kind.is_dir() {
|
||||
if let Location::Path(tab_path) = &self.tab.location {
|
||||
if self.flags.kind.is_dir()
|
||||
&& let Location::Path(tab_path) = &self.tab.location
|
||||
{
|
||||
self.result_opt = Some(DialogResult::Open(vec![tab_path.clone()]));
|
||||
return window::close(self.flags.window_id);
|
||||
}
|
||||
}
|
||||
}
|
||||
Message::Preview => {
|
||||
self.context_page = ContextPage::Preview(None, PreviewKind::Selected);
|
||||
return self.with_dialog_config(|config| {
|
||||
|
|
@ -1654,15 +1654,15 @@ impl Application for App {
|
|||
});
|
||||
}
|
||||
Message::Save(replace) => {
|
||||
if let DialogKind::SaveFile { filename } = &self.flags.kind {
|
||||
if !filename.is_empty() {
|
||||
if let Some(tab_path) = self.tab.location.path_opt() {
|
||||
if let DialogKind::SaveFile { filename } = &self.flags.kind
|
||||
&& !filename.is_empty()
|
||||
&& let Some(tab_path) = self.tab.location.path_opt()
|
||||
{
|
||||
let path = tab_path.join(filename);
|
||||
if path.is_dir() {
|
||||
// cd to directory
|
||||
let message = Message::TabMessage(tab::Message::Location(
|
||||
Location::Path(path),
|
||||
));
|
||||
let message =
|
||||
Message::TabMessage(tab::Message::Location(Location::Path(path)));
|
||||
return self.update(message);
|
||||
} else if !replace && path.exists() {
|
||||
self.dialog_pages.push_back(DialogPage::Replace {
|
||||
|
|
@ -1674,8 +1674,6 @@ impl Application for App {
|
|||
return window::close(self.flags.window_id);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Message::ScrollTab(scroll_speed) => {
|
||||
return self.update(Message::TabMessage(tab::Message::ScrollTab(
|
||||
f32::from(scroll_speed) / 10.0,
|
||||
|
|
@ -1703,17 +1701,15 @@ impl Application for App {
|
|||
let tab_commands = self.tab.update(tab_message, self.modifiers);
|
||||
|
||||
// Update filename box when anything is selected
|
||||
if let DialogKind::SaveFile { filename } = &mut self.flags.kind {
|
||||
if let Some(click_i) = click_i_opt {
|
||||
if let Some(items) = self.tab.items_opt() {
|
||||
if let Some(item) = items.get(click_i) {
|
||||
if item.selected && !item.metadata.is_dir() {
|
||||
if let DialogKind::SaveFile { filename } = &mut self.flags.kind
|
||||
&& let Some(click_i) = click_i_opt
|
||||
&& let Some(items) = self.tab.items_opt()
|
||||
&& let Some(item) = items.get(click_i)
|
||||
&& item.selected
|
||||
&& !item.metadata.is_dir()
|
||||
{
|
||||
filename.clone_from(&item.name);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let mut commands = Vec::new();
|
||||
for tab_command in tab_commands {
|
||||
|
|
@ -1840,8 +1836,9 @@ impl Application for App {
|
|||
Message::TabRescan(location, parent_item_opt, mut items, selection_paths) => {
|
||||
if location == self.tab.location {
|
||||
// Filter
|
||||
if let Some(filter_i) = self.filter_selected {
|
||||
if let Some(filter) = self.filters.get(filter_i) {
|
||||
if let Some(filter_i) = self.filter_selected
|
||||
&& let Some(filter) = self.filters.get(filter_i)
|
||||
{
|
||||
// Parse globs (Mime implements PartialEq with &str, so no need to parse)
|
||||
let mut parsed_globs = Vec::new();
|
||||
let mut mimes = Vec::new();
|
||||
|
|
@ -1868,7 +1865,6 @@ impl Application for App {
|
|||
|| parsed_globs.iter().any(|glob| glob.matches(&item.name))
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Select based on filename
|
||||
if let DialogKind::SaveFile { filename } = &self.flags.kind {
|
||||
|
|
@ -1944,8 +1940,9 @@ impl Application for App {
|
|||
|
||||
let mut col = widget::column::with_capacity(2);
|
||||
|
||||
if self.core.is_condensed() {
|
||||
if let Some(term) = self.search_get() {
|
||||
if self.core.is_condensed()
|
||||
&& let Some(term) = self.search_get()
|
||||
{
|
||||
col = col.push(
|
||||
widget::container(
|
||||
widget::text_input::search_input("", term)
|
||||
|
|
@ -1957,7 +1954,6 @@ impl Application for App {
|
|||
.padding(space_xxs),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
col = col.push(
|
||||
self.tab
|
||||
|
|
|
|||
|
|
@ -392,8 +392,9 @@ impl LargeImageManager {
|
|||
generation: u64,
|
||||
) -> bool {
|
||||
// Check if this decode is still current (not superseded by a newer one)
|
||||
if let Some(¤t_gen) = self.decode_generations.get(&path) {
|
||||
if generation != current_gen {
|
||||
if let Some(¤t_gen) = self.decode_generations.get(&path)
|
||||
&& generation != current_gen
|
||||
{
|
||||
log::info!(
|
||||
"Discarding outdated decode for {} (generation {} != current {})",
|
||||
path.display(),
|
||||
|
|
@ -402,7 +403,6 @@ impl LargeImageManager {
|
|||
);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
log::info!(
|
||||
"Storing decoded image for {} (generation {})",
|
||||
|
|
@ -556,7 +556,7 @@ impl LargeImageManager {
|
|||
|
||||
/// Check if sufficient memory is available, clearing cache if needed.
|
||||
/// Returns true if memory is available, false otherwise.
|
||||
fn ensure_memory_available(&mut self, path: &PathBuf, width: u32, height: u32) -> bool {
|
||||
fn ensure_memory_available(&mut self, path: &Path, width: u32, height: u32) -> bool {
|
||||
let (has_memory, error_opt) = check_memory_available(width, height);
|
||||
|
||||
if has_memory {
|
||||
|
|
@ -565,7 +565,7 @@ impl LargeImageManager {
|
|||
|
||||
if self.cache_is_empty() {
|
||||
if let Some(error_msg) = error_opt {
|
||||
self.store_error(path.clone(), error_msg);
|
||||
self.store_error(path.to_path_buf(), error_msg);
|
||||
log::warn!(
|
||||
"Cannot load {}: insufficient memory and cache is empty",
|
||||
path.display()
|
||||
|
|
@ -588,7 +588,7 @@ impl LargeImageManager {
|
|||
}
|
||||
|
||||
if let Some(error_msg) = error_opt_after {
|
||||
self.store_error(path.clone(), error_msg);
|
||||
self.store_error(path.to_path_buf(), error_msg);
|
||||
log::warn!(
|
||||
"Cannot load {}: insufficient memory even after cache clear",
|
||||
path.display()
|
||||
|
|
|
|||
|
|
@ -62,13 +62,13 @@ pub static LOCALE: LazyLock<Locale> = LazyLock::new(|| {
|
|||
}
|
||||
|
||||
// Try language-only fallback (e.g., "en" from "en-US")
|
||||
if let Some(lang) = cleaned_locale.split('-').next() {
|
||||
if let Ok(locale) = Locale::try_from_str(lang) {
|
||||
if let Some(lang) = cleaned_locale.split('-').next()
|
||||
&& let Ok(locale) = Locale::try_from_str(lang)
|
||||
{
|
||||
return locale;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
log::warn!("No valid locale found in environment, using fallback");
|
||||
Locale::try_from_str("en-US").expect("Failed to parse fallback locale 'en-US'")
|
||||
});
|
||||
|
|
|
|||
|
|
@ -310,7 +310,7 @@ impl MimeAppCache {
|
|||
for (mime, filenames) in list.removed_associations.iter() {
|
||||
for filename in filenames {
|
||||
log::trace!("remove {mime}={filename}");
|
||||
if let Some(apps) = self.cache.get_mut(&mime) {
|
||||
if let Some(apps) = self.cache.get_mut(mime) {
|
||||
apps.retain(|x| !filename_eq(&x.path, filename));
|
||||
}
|
||||
}
|
||||
|
|
@ -319,7 +319,7 @@ impl MimeAppCache {
|
|||
for (mime, filenames) in list.default_apps.iter() {
|
||||
for filename in filenames {
|
||||
log::trace!("default {mime}={filename}");
|
||||
if let Some(apps) = self.cache.get_mut(&mime) {
|
||||
if let Some(apps) = self.cache.get_mut(mime) {
|
||||
let mut found = false;
|
||||
for app in apps.iter_mut() {
|
||||
if filename_eq(&app.path, filename) {
|
||||
|
|
|
|||
|
|
@ -23,13 +23,12 @@ fn resolve_uri(uri: &str) -> (String, gio::File) {
|
|||
TARGET_URI_ATTRIBUTE,
|
||||
gio::FileQueryInfoFlags::NONE,
|
||||
gio::Cancellable::NONE,
|
||||
) {
|
||||
if let Some(resolved_uri) = file_info.attribute_as_string(TARGET_URI_ATTRIBUTE) {
|
||||
) && let Some(resolved_uri) = file_info.attribute_as_string(TARGET_URI_ATTRIBUTE)
|
||||
{
|
||||
let resolved_uri = String::from(resolved_uri);
|
||||
let file = gio::File::for_uri(&resolved_uri);
|
||||
return (resolved_uri, file);
|
||||
}
|
||||
}
|
||||
|
||||
(uri.to_string(), file)
|
||||
}
|
||||
|
|
@ -60,7 +59,7 @@ fn items(monitor: &gio::VolumeMonitor, sizes: IconSizes) -> MounterItems {
|
|||
gio::Cancellable::NONE,
|
||||
)
|
||||
.ok()
|
||||
.and_then(|info| Some(info.boolean(gio::FILE_ATTRIBUTE_FILESYSTEM_REMOTE)))
|
||||
.map(|info| info.boolean(gio::FILE_ATTRIBUTE_FILESYSTEM_REMOTE))
|
||||
.unwrap_or(true); // Default to remote if query fails
|
||||
|
||||
MounterItem::Gvfs(Item {
|
||||
|
|
@ -457,9 +456,9 @@ impl Gvfs {
|
|||
log::info!("mount {name}: result {res:?}");
|
||||
// Update the mounter_item with mount information after successful mount
|
||||
let mut updated_item = mounter_item.clone();
|
||||
if res.is_ok() {
|
||||
if let MounterItem::Gvfs(ref mut item) = updated_item {
|
||||
if let Some(mount) = volume_for_callback.get_mount() {
|
||||
if res.is_ok()
|
||||
&& let MounterItem::Gvfs(ref mut item) = updated_item
|
||||
&& let Some(mount) = volume_for_callback.get_mount() {
|
||||
let root = MountExt::root(&mount);
|
||||
item.path_opt = root.path();
|
||||
item.is_mounted = true;
|
||||
|
|
@ -469,14 +468,9 @@ impl Gvfs {
|
|||
gio::FILE_ATTRIBUTE_FILESYSTEM_REMOTE,
|
||||
gio::Cancellable::NONE,
|
||||
)
|
||||
.ok()
|
||||
.and_then(|info| {
|
||||
Some(info.boolean(gio::FILE_ATTRIBUTE_FILESYSTEM_REMOTE))
|
||||
})
|
||||
.ok().map(|info| info.boolean(gio::FILE_ATTRIBUTE_FILESYSTEM_REMOTE))
|
||||
.unwrap_or(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
event_tx.send(Event::MountResult(updated_item, match res {
|
||||
Ok(()) => {
|
||||
_ = complete_tx.send(Ok(()));
|
||||
|
|
|
|||
|
|
@ -246,9 +246,10 @@ struct State {
|
|||
|
||||
impl State {
|
||||
fn drag_rect(&self, cursor: mouse::Cursor) -> Option<Rectangle> {
|
||||
if let Some(drag_source) = self.drag_initiated {
|
||||
if let Some(position) = cursor.position().or(self.last_virtual_position) {
|
||||
if position.distance(drag_source) > 1.0 {
|
||||
if let Some(drag_source) = self.drag_initiated
|
||||
&& let Some(position) = cursor.position().or(self.last_virtual_position)
|
||||
&& position.distance(drag_source) > 1.0
|
||||
{
|
||||
let min_x = drag_source.x.min(position.x);
|
||||
let max_x = drag_source.x.max(position.x);
|
||||
let min_y = drag_source.y.min(position.y);
|
||||
|
|
@ -258,8 +259,6 @@ impl State {
|
|||
Size::new(max_x - min_x, max_y - min_y),
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
|
|
@ -527,13 +526,13 @@ fn update<Message: Clone>(
|
|||
let offset = layout.virtual_offset();
|
||||
let layout_bounds = layout.bounds();
|
||||
|
||||
let viewport_changed = state.viewport.map_or(true, |v| v != *viewport);
|
||||
let viewport_changed = state.viewport != Some(*viewport);
|
||||
|
||||
if let Some(message) = widget.on_resize.as_ref() {
|
||||
if viewport_changed {
|
||||
if let Some(message) = widget.on_resize.as_ref()
|
||||
&& viewport_changed
|
||||
{
|
||||
shell.publish(message(*viewport));
|
||||
}
|
||||
}
|
||||
|
||||
state.viewport = Some(*viewport);
|
||||
|
||||
|
|
@ -664,11 +663,12 @@ fn update<Message: Clone>(
|
|||
}
|
||||
}
|
||||
|
||||
if let Some(message) = widget.on_right_press.as_ref() {
|
||||
if matches!(
|
||||
if let Some(message) = widget.on_right_press.as_ref()
|
||||
&& matches!(
|
||||
event,
|
||||
Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Right))
|
||||
) {
|
||||
)
|
||||
{
|
||||
let point_opt = if widget.on_right_press_window_position {
|
||||
cursor.position_over(layout_bounds).map(|mut p| {
|
||||
p.x -= offset.x;
|
||||
|
|
@ -685,93 +685,91 @@ fn update<Message: Clone>(
|
|||
}
|
||||
return event::Status::Captured;
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(message) = widget.on_right_release.as_ref() {
|
||||
if matches!(
|
||||
if let Some(message) = widget.on_right_release.as_ref()
|
||||
&& matches!(
|
||||
event,
|
||||
Event::Mouse(mouse::Event::ButtonReleased(mouse::Button::Right))
|
||||
) {
|
||||
)
|
||||
{
|
||||
shell.publish(message(cursor.position_in(layout_bounds)));
|
||||
|
||||
return event::Status::Captured;
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(message) = widget.on_middle_press.as_ref() {
|
||||
if matches!(
|
||||
if let Some(message) = widget.on_middle_press.as_ref()
|
||||
&& matches!(
|
||||
event,
|
||||
Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Middle))
|
||||
) {
|
||||
)
|
||||
{
|
||||
shell.publish(message(cursor.position_in(layout_bounds)));
|
||||
|
||||
return event::Status::Captured;
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(message) = widget.on_middle_release.as_ref() {
|
||||
if matches!(
|
||||
if let Some(message) = widget.on_middle_release.as_ref()
|
||||
&& matches!(
|
||||
event,
|
||||
Event::Mouse(mouse::Event::ButtonReleased(mouse::Button::Middle))
|
||||
) {
|
||||
)
|
||||
{
|
||||
shell.publish(message(cursor.position_in(layout_bounds)));
|
||||
|
||||
return event::Status::Captured;
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(message) = widget.on_back_press.as_ref() {
|
||||
if matches!(
|
||||
if let Some(message) = widget.on_back_press.as_ref()
|
||||
&& matches!(
|
||||
event,
|
||||
Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Back))
|
||||
) {
|
||||
)
|
||||
{
|
||||
shell.publish(message(cursor.position_in(layout_bounds)));
|
||||
|
||||
return event::Status::Captured;
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(message) = widget.on_back_release.as_ref() {
|
||||
if matches!(
|
||||
if let Some(message) = widget.on_back_release.as_ref()
|
||||
&& matches!(
|
||||
event,
|
||||
Event::Mouse(mouse::Event::ButtonReleased(mouse::Button::Back))
|
||||
) {
|
||||
)
|
||||
{
|
||||
shell.publish(message(cursor.position_in(layout_bounds)));
|
||||
|
||||
return event::Status::Captured;
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(message) = widget.on_forward_press.as_ref() {
|
||||
if matches!(
|
||||
if let Some(message) = widget.on_forward_press.as_ref()
|
||||
&& matches!(
|
||||
event,
|
||||
Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Forward))
|
||||
) {
|
||||
)
|
||||
{
|
||||
shell.publish(message(cursor.position_in(layout_bounds)));
|
||||
|
||||
return event::Status::Captured;
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(message) = widget.on_forward_release.as_ref() {
|
||||
if matches!(
|
||||
if let Some(message) = widget.on_forward_release.as_ref()
|
||||
&& matches!(
|
||||
event,
|
||||
Event::Mouse(mouse::Event::ButtonReleased(mouse::Button::Forward))
|
||||
) {
|
||||
)
|
||||
{
|
||||
shell.publish(message(cursor.position_in(layout_bounds)));
|
||||
|
||||
return event::Status::Captured;
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(on_scroll) = widget.on_scroll.as_ref() {
|
||||
if let Event::Mouse(mouse::Event::WheelScrolled { delta }) = event {
|
||||
if let Some(message) = on_scroll(*delta) {
|
||||
if let Some(on_scroll) = widget.on_scroll.as_ref()
|
||||
&& let Event::Mouse(mouse::Event::WheelScrolled { delta }) = event
|
||||
&& let Some(message) = on_scroll(*delta)
|
||||
{
|
||||
shell.publish(message);
|
||||
return event::Status::Captured;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if let Some((message, drag_rect)) = widget.on_drag.as_ref().zip(state.drag_rect(cursor)) {
|
||||
shell.publish(message(drag_rect.intersection(&layout_bounds).map(
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ use crate::{
|
|||
spawn_detached::spawn_detached,
|
||||
tab,
|
||||
};
|
||||
use cosmic::iced::futures::{SinkExt, channel::mpsc::Sender};
|
||||
use cosmic::iced::futures::{self, SinkExt, StreamExt, channel::mpsc::Sender, stream};
|
||||
use std::{
|
||||
borrow::Cow,
|
||||
fmt::Formatter,
|
||||
|
|
@ -196,6 +196,31 @@ async fn copy_or_move(
|
|||
.map_err(wrap_compio_spawn_error)?
|
||||
}
|
||||
|
||||
pub async fn sync_to_disk(
|
||||
written_files: Vec<PathBuf>,
|
||||
target_dirs: std::collections::HashSet<PathBuf>,
|
||||
) {
|
||||
// Sync files to disk
|
||||
stream::iter(written_files.into_iter().map(|path| async move {
|
||||
if let Ok(file) = compio::fs::OpenOptions::new().write(true).open(&path).await {
|
||||
let _ = file.sync_all().await;
|
||||
}
|
||||
}))
|
||||
.buffer_unordered(32)
|
||||
.collect::<Vec<_>>()
|
||||
.await;
|
||||
|
||||
// Sync directories to disk
|
||||
stream::iter(target_dirs.into_iter().map(|path| async move {
|
||||
if let Ok(dir) = compio::fs::OpenOptions::new().read(true).open(&path).await {
|
||||
let _ = dir.sync_all().await;
|
||||
}
|
||||
}))
|
||||
.buffer_unordered(16)
|
||||
.collect::<Vec<_>>()
|
||||
.await;
|
||||
}
|
||||
|
||||
fn copy_unique_path(from: &Path, to: &Path) -> PathBuf {
|
||||
// List of compound extensions to check
|
||||
const COMPOUND_EXTENSIONS: &[&str] = &[
|
||||
|
|
@ -934,11 +959,11 @@ impl Operation {
|
|||
let dir_name = get_directory_name(file_name);
|
||||
let mut new_dir = to.join(dir_name);
|
||||
|
||||
if new_dir.exists() {
|
||||
if let Some(new_dir_parent) = new_dir.parent() {
|
||||
if new_dir.exists()
|
||||
&& let Some(new_dir_parent) = new_dir.parent()
|
||||
{
|
||||
new_dir = copy_unique_path(&new_dir, new_dir_parent);
|
||||
}
|
||||
}
|
||||
|
||||
op_sel.ignored.push(path.clone());
|
||||
op_sel.selected.push(new_dir.clone());
|
||||
|
|
@ -1185,7 +1210,7 @@ mod tests {
|
|||
path::PathBuf,
|
||||
};
|
||||
|
||||
use cosmic::iced::futures::{StreamExt, channel::mpsc};
|
||||
use cosmic::iced::futures::{StreamExt, channel::mpsc, future};
|
||||
use log::debug;
|
||||
use test_log::test;
|
||||
use tokio::sync;
|
||||
|
|
@ -1239,7 +1264,7 @@ mod tests {
|
|||
}
|
||||
};
|
||||
|
||||
futures::future::join(handle_messages, handle_copy).await.1
|
||||
future::join(handle_messages, handle_copy).await.1
|
||||
}
|
||||
|
||||
#[test(compio::test)]
|
||||
|
|
|
|||
|
|
@ -27,7 +27,7 @@ impl OpReader {
|
|||
|
||||
impl io::Read for OpReader {
|
||||
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
|
||||
futures::executor::block_on(async {
|
||||
cosmic::iced::futures::executor::block_on(async {
|
||||
self.controller
|
||||
.check()
|
||||
.await
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ use std::time::Instant;
|
|||
use std::{cell::Cell, error::Error, fs, ops::ControlFlow, path::PathBuf, rc::Rc};
|
||||
use walkdir::WalkDir;
|
||||
|
||||
use crate::operation::OperationError;
|
||||
use crate::operation::{OperationError, sync_to_disk};
|
||||
|
||||
use super::{Controller, OperationSelection, ReplaceResult, copy_unique_path};
|
||||
|
||||
|
|
@ -57,6 +57,8 @@ impl Context {
|
|||
) -> Result<bool, OperationError> {
|
||||
let mut ops = Vec::new();
|
||||
let mut cleanup_ops = Vec::new();
|
||||
let mut written_files = Vec::new();
|
||||
let mut target_dirs = std::collections::HashSet::new();
|
||||
for (from_parent, to_parent) in from_to_pairs {
|
||||
self.controller
|
||||
.check()
|
||||
|
|
@ -136,10 +138,13 @@ impl Context {
|
|||
}),
|
||||
is_cleanup: false,
|
||||
};
|
||||
if matches!(method, Method::Move { .. }) {
|
||||
if let Some(cleanup_op) = op.move_cleanup_op() {
|
||||
if matches!(method, Method::Move { .. })
|
||||
&& let Some(cleanup_op) = op.move_cleanup_op()
|
||||
{
|
||||
cleanup_ops.push(cleanup_op);
|
||||
}
|
||||
if let Some(parent) = op.to.parent() {
|
||||
target_dirs.insert(parent.to_path_buf());
|
||||
}
|
||||
ops.push(op);
|
||||
}
|
||||
|
|
@ -177,10 +182,19 @@ impl Context {
|
|||
&self.controller,
|
||||
)
|
||||
})? {
|
||||
if matches!(
|
||||
op.kind,
|
||||
OpKind::Copy
|
||||
| OpKind::Move {
|
||||
cross_device_copy: true
|
||||
}
|
||||
) {
|
||||
written_files.push(op.to.clone());
|
||||
}
|
||||
// The from path is ignored in the operation selection if it is a top level item
|
||||
if self.op_sel.ignored.contains(&op.from) {
|
||||
// So add the to path to the selection
|
||||
self.op_sel.selected.push(op.to.clone());
|
||||
self.op_sel.selected.push(op.to);
|
||||
}
|
||||
} else {
|
||||
// Cancelled
|
||||
|
|
@ -188,6 +202,9 @@ impl Context {
|
|||
}
|
||||
}
|
||||
|
||||
// Flush files to disk
|
||||
sync_to_disk(written_files, target_dirs).await;
|
||||
|
||||
Ok(true)
|
||||
}
|
||||
|
||||
|
|
@ -305,7 +322,7 @@ impl Op {
|
|||
}
|
||||
}
|
||||
|
||||
let (from_file, metadata, mut to_file) = futures::try_join!(
|
||||
let (from_file, metadata, mut to_file) = cosmic::iced::futures::try_join!(
|
||||
async {
|
||||
compio::fs::OpenOptions::new()
|
||||
.read(true)
|
||||
|
|
@ -411,8 +428,6 @@ impl Op {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
to_file.sync_all().await?;
|
||||
}
|
||||
OpKind::Move { cross_device_copy } => {
|
||||
// Remove `to` if overwriting and it is an existing file
|
||||
|
|
|
|||
247
src/tab.rs
247
src/tab.rs
|
|
@ -1,5 +1,8 @@
|
|||
use chrono::{Datelike, Timelike, Utc};
|
||||
use cosmic::{
|
||||
Apply, Element, cosmic_theme, font,
|
||||
Apply, Element, cosmic_theme,
|
||||
desktop::fde::{DesktopEntry, get_languages_from_env},
|
||||
font,
|
||||
iced::{
|
||||
Alignment,
|
||||
Border,
|
||||
|
|
@ -37,8 +40,6 @@ use cosmic::{
|
|||
menu::{action::MenuAction, key_bind::KeyBind},
|
||||
},
|
||||
};
|
||||
|
||||
use chrono::{Datelike, Timelike, Utc};
|
||||
use i18n_embed::LanguageLoader;
|
||||
use icu::{
|
||||
datetime::{
|
||||
|
|
@ -615,7 +616,8 @@ pub fn fs_kind(_metadata: &Metadata) -> FsKind {
|
|||
}
|
||||
|
||||
fn get_desktop_file_display_name(path: &Path) -> Option<String> {
|
||||
let entry = match freedesktop_entry_parser::parse_entry(path) {
|
||||
let locales = get_languages_from_env();
|
||||
let entry = match DesktopEntry::from_path(path, Some(&locales)) {
|
||||
Ok(ok) => ok,
|
||||
Err(err) => {
|
||||
log::warn!("failed to parse {}: {}", path.display(), err);
|
||||
|
|
@ -623,14 +625,11 @@ fn get_desktop_file_display_name(path: &Path) -> Option<String> {
|
|||
}
|
||||
};
|
||||
|
||||
entry
|
||||
.section("Desktop Entry")
|
||||
.attr("Name")
|
||||
.map(str::to_string)
|
||||
entry.name(&locales).map(|s| s.into_owned())
|
||||
}
|
||||
|
||||
fn get_desktop_file_icon(path: &Path) -> Option<String> {
|
||||
let entry = match freedesktop_entry_parser::parse_entry(path) {
|
||||
let entry = match DesktopEntry::from_path::<&str>(path, None) {
|
||||
Ok(ok) => ok,
|
||||
Err(err) => {
|
||||
log::warn!("failed to parse {}: {}", path.display(), err);
|
||||
|
|
@ -638,10 +637,7 @@ fn get_desktop_file_icon(path: &Path) -> Option<String> {
|
|||
}
|
||||
};
|
||||
|
||||
entry
|
||||
.section("Desktop Entry")
|
||||
.attr("Icon")
|
||||
.map(str::to_string)
|
||||
entry.icon().map(str::to_string)
|
||||
}
|
||||
|
||||
/// Creates an icon handle from a desktop file's Icon field value.
|
||||
|
|
@ -656,17 +652,17 @@ fn desktop_icon_handle(icon: &str, size: u16) -> widget::icon::Handle {
|
|||
}
|
||||
|
||||
pub fn parse_desktop_file(path: &Path) -> (Option<String>, Option<String>) {
|
||||
let entry = match freedesktop_entry_parser::parse_entry(path) {
|
||||
let locales = get_languages_from_env();
|
||||
let entry = match DesktopEntry::from_path(path, Some(&locales)) {
|
||||
Ok(ok) => ok,
|
||||
Err(err) => {
|
||||
log::warn!("failed to parse {}: {}", path.display(), err);
|
||||
return (None, None);
|
||||
}
|
||||
};
|
||||
let section = entry.section("Desktop Entry");
|
||||
(
|
||||
section.attr("Name").map(str::to_string),
|
||||
section.attr("Icon").map(str::to_string),
|
||||
entry.name(&locales).map(|s| s.into_owned()),
|
||||
entry.icon().map(str::to_string),
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -934,8 +930,9 @@ pub fn scan_path(tab_path: &PathBuf, sizes: IconSizes) -> Vec<Item> {
|
|||
|
||||
#[cfg(feature = "gvfs")]
|
||||
{
|
||||
if let Ok(path_meta) = fs::metadata(tab_path) {
|
||||
if fs_kind(&path_meta) == FsKind::Gvfs {
|
||||
if let Ok(path_meta) = fs::metadata(tab_path)
|
||||
&& fs_kind(&path_meta) == FsKind::Gvfs
|
||||
{
|
||||
let file = gio::File::for_path(tab_path);
|
||||
|
||||
// gio crate expects a comma delimited string
|
||||
|
|
@ -974,7 +971,6 @@ pub fn scan_path(tab_path: &PathBuf, sizes: IconSizes) -> Vec<Item> {
|
|||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if !remote_scannable {
|
||||
match fs::read_dir(tab_path) {
|
||||
|
|
@ -1410,13 +1406,13 @@ impl EditLocation {
|
|||
self.selected = Some(selected);
|
||||
|
||||
// Automatically resolve if there is only one completion
|
||||
if completions.len() == 1 {
|
||||
if let Some(resolved) = self.resolve() {
|
||||
if completions.len() == 1
|
||||
&& let Some(resolved) = self.resolve()
|
||||
{
|
||||
self.location = resolved;
|
||||
self.selected = None;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
self.selected = None;
|
||||
}
|
||||
|
|
@ -2033,11 +2029,11 @@ impl ItemThumbnail {
|
|||
if let Some((item_thumbnail, temp_file)) =
|
||||
Self::generate_thumbnail_external(path, &mime, thumbnail_size, thumbnail_dir)
|
||||
{
|
||||
if let Ok(cache) = thumbnail_cacher {
|
||||
if let Err(err) = cache.update_with_temp_file(temp_file) {
|
||||
if let Ok(cache) = thumbnail_cacher
|
||||
&& let Err(err) = cache.update_with_temp_file(temp_file)
|
||||
{
|
||||
log::warn!("failed to update cache for {}: {}", path.display(), err);
|
||||
}
|
||||
}
|
||||
return item_thumbnail;
|
||||
}
|
||||
|
||||
|
|
@ -2076,17 +2072,16 @@ impl ItemThumbnail {
|
|||
// If we weren't able to create a thumbnail, but we should have
|
||||
// been able to, create a fail marker so that it isn't tried the
|
||||
// next time.
|
||||
if let Ok(cacher) = thumbnail_cacher {
|
||||
if tried_supported_file {
|
||||
if let Err(err) = cacher.create_fail_marker() {
|
||||
if let Ok(cacher) = thumbnail_cacher
|
||||
&& tried_supported_file
|
||||
&& let Err(err) = cacher.create_fail_marker()
|
||||
{
|
||||
log::warn!(
|
||||
"failed to create thumbnail fail marker for {}: {}",
|
||||
path.display(),
|
||||
err
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Self::NotImage
|
||||
}
|
||||
|
|
@ -2273,14 +2268,14 @@ impl Item {
|
|||
widget::button::icon(widget::icon::from_name("go-next-symbolic"))
|
||||
.on_press(Message::ItemRight),
|
||||
);
|
||||
if self.can_gallery() {
|
||||
if let Some(_path) = self.path_opt() {
|
||||
if self.can_gallery()
|
||||
&& let Some(_path) = self.path_opt()
|
||||
{
|
||||
row = row.push(
|
||||
widget::button::icon(widget::icon::from_name("view-fullscreen-symbolic"))
|
||||
.on_press(Message::Gallery(true)),
|
||||
);
|
||||
}
|
||||
}
|
||||
row.into()
|
||||
}
|
||||
|
||||
|
|
@ -2445,19 +2440,21 @@ impl Item {
|
|||
}
|
||||
}
|
||||
|
||||
if let Some(path) = self.path_opt() {
|
||||
if let Ok(img) = image::image_dimensions(path) {
|
||||
if let Some(path) = self.path_opt()
|
||||
&& let Ok(img) = image::image_dimensions(path)
|
||||
{
|
||||
let (width, height) = img;
|
||||
details = details.push(widget::text::body(format!("{width}x{height}")));
|
||||
}
|
||||
}
|
||||
column = column.push(details);
|
||||
|
||||
if let Some(path) = self.path_opt() {
|
||||
if self.selected {
|
||||
column = column.push(
|
||||
widget::button::standard(fl!("open")).on_press(Message::Open(Some(path.clone()))),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if !settings.is_empty() {
|
||||
let mut section = widget::settings::section();
|
||||
|
|
@ -2635,13 +2632,12 @@ async fn calculate_dir_size(path: &Path, controller: Controller) -> Result<u64,
|
|||
.map_err(|s| OperationError::from_state(s, &controller))?;
|
||||
|
||||
//TODO: report more errors?
|
||||
if let Ok(entry) = entry_res {
|
||||
if let Ok(metadata) = entry.metadata() {
|
||||
if metadata.is_file() {
|
||||
if let Ok(entry) = entry_res
|
||||
&& let Ok(metadata) = entry.metadata()
|
||||
&& metadata.is_file()
|
||||
{
|
||||
total += metadata.len();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Yield in case this process takes a while.
|
||||
tokio::task::yield_now().await;
|
||||
|
|
@ -2766,12 +2762,12 @@ impl Tab {
|
|||
let selected = self.selected_locations();
|
||||
for item in &mut items {
|
||||
item.selected = false;
|
||||
if let Some(location) = &item.location_opt {
|
||||
if selected.contains(location) {
|
||||
if let Some(location) = &item.location_opt
|
||||
&& selected.contains(location)
|
||||
{
|
||||
item.selected = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
self.items_opt = Some(items);
|
||||
}
|
||||
|
||||
|
|
@ -2788,14 +2784,13 @@ impl Tab {
|
|||
for item in items.iter_mut() {
|
||||
item.cut = false;
|
||||
if let Some(location_path) = item.location_opt.as_ref().and_then(Location::path_opt)
|
||||
&& locations.contains(location_path)
|
||||
{
|
||||
if locations.contains(location_path) {
|
||||
item.cut = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn selected_locations(&self) -> Vec<Location> {
|
||||
if let Some(ref items) = self.items_opt {
|
||||
|
|
@ -2881,15 +2876,15 @@ impl Tab {
|
|||
if let Some(ref mut items) = self.items_opt {
|
||||
for (i, item) in items.iter_mut().enumerate() {
|
||||
item.selected = false;
|
||||
if let Some(path) = item.path_opt() {
|
||||
if paths.contains(path) {
|
||||
if let Some(path) = item.path_opt()
|
||||
&& paths.contains(path)
|
||||
{
|
||||
item.selected = true;
|
||||
self.select_focus = Some(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn select_position(&mut self, row: usize, col: usize, mod_shift: bool) -> bool {
|
||||
let mut start = (row, col);
|
||||
|
|
@ -3085,11 +3080,11 @@ impl Tab {
|
|||
return Vec::new();
|
||||
};
|
||||
|
||||
if let Some((w, h)) = original_dims {
|
||||
if !should_use_tiling(*w, *h) {
|
||||
if let Some((w, h)) = original_dims
|
||||
&& !should_use_tiling(*w, *h)
|
||||
{
|
||||
return Vec::new();
|
||||
}
|
||||
}
|
||||
|
||||
let Some(path) = item.path_opt() else {
|
||||
return Vec::new();
|
||||
|
|
@ -3389,8 +3384,7 @@ impl Tab {
|
|||
self.date_time_formatter = date_time_formatter(self.config.military_time);
|
||||
self.time_formatter = time_formatter(self.config.military_time);
|
||||
}
|
||||
if show_hidden_changed {
|
||||
if let Location::Search(path, term, ..) = &self.location {
|
||||
if show_hidden_changed && let Location::Search(path, term, ..) = &self.location {
|
||||
cd = Some(Location::Search(
|
||||
path.clone(),
|
||||
term.clone(),
|
||||
|
|
@ -3398,7 +3392,6 @@ impl Tab {
|
|||
Instant::now(),
|
||||
));
|
||||
}
|
||||
}
|
||||
// Unhighlight all items when config changes
|
||||
if let Some(ref mut items) = self.items_opt {
|
||||
for item in items.iter_mut() {
|
||||
|
|
@ -3418,14 +3411,15 @@ impl Tab {
|
|||
self.location_context_menu_index = None;
|
||||
|
||||
//TODO: hack for clearing selecting when right clicking empty space
|
||||
if self.context_menu.is_some() && self.last_right_click.take().is_none() {
|
||||
if let Some(ref mut items) = self.items_opt {
|
||||
if self.context_menu.is_some()
|
||||
&& self.last_right_click.take().is_none()
|
||||
&& let Some(ref mut items) = self.items_opt
|
||||
{
|
||||
for item in items.iter_mut() {
|
||||
item.selected = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Message::LocationContextMenuPoint(point_opt) => {
|
||||
self.context_menu = None;
|
||||
self.location_context_menu_point = point_opt;
|
||||
|
|
@ -3510,13 +3504,13 @@ impl Tab {
|
|||
}
|
||||
}
|
||||
Message::EditLocationComplete(selected) => {
|
||||
if let Some(mut edit_location) = self.edit_location.take() {
|
||||
if !matches!(edit_location.location, Location::Network(..)) {
|
||||
if let Some(mut edit_location) = self.edit_location.take()
|
||||
&& !matches!(edit_location.location, Location::Network(..))
|
||||
{
|
||||
edit_location.selected = Some(selected);
|
||||
cd = edit_location.resolve();
|
||||
}
|
||||
}
|
||||
}
|
||||
Message::EditLocationEnable => {
|
||||
commands.push(Command::Iced(
|
||||
widget::text_input::focus(self.edit_location_id.clone()).into(),
|
||||
|
|
@ -3530,11 +3524,11 @@ impl Tab {
|
|||
&& edit_location
|
||||
.completions
|
||||
.as_ref()
|
||||
.map_or(false, |completions| !completions.is_empty())
|
||||
.is_some_and(|completions| !completions.is_empty())
|
||||
&& edit_location
|
||||
.location
|
||||
.path_opt()
|
||||
.map_or(false, |path| !path.exists())
|
||||
.is_some_and(|path| !path.exists())
|
||||
{
|
||||
edit_location.selected = Some(0);
|
||||
}
|
||||
|
|
@ -3632,21 +3626,21 @@ impl Tab {
|
|||
}
|
||||
}
|
||||
Message::GoNext => {
|
||||
if let Some(history_i) = self.history_i.checked_add(1) {
|
||||
if let Some(location) = self.history.get(history_i) {
|
||||
if let Some(history_i) = self.history_i.checked_add(1)
|
||||
&& let Some(location) = self.history.get(history_i)
|
||||
{
|
||||
cd = Some(location.clone());
|
||||
history_i_opt = Some(history_i);
|
||||
}
|
||||
}
|
||||
}
|
||||
Message::GoPrevious => {
|
||||
if let Some(history_i) = self.history_i.checked_sub(1) {
|
||||
if let Some(location) = self.history.get(history_i) {
|
||||
if let Some(history_i) = self.history_i.checked_sub(1)
|
||||
&& let Some(location) = self.history.get(history_i)
|
||||
{
|
||||
cd = Some(location.clone());
|
||||
history_i_opt = Some(history_i);
|
||||
}
|
||||
}
|
||||
}
|
||||
Message::ItemDown => {
|
||||
if let Some(edit_location) = &mut self.edit_location {
|
||||
edit_location.select(true);
|
||||
|
|
@ -3823,12 +3817,12 @@ impl Tab {
|
|||
Message::LocationUp => {
|
||||
// Sets location to the path's parent
|
||||
// Does nothing if path is root or location is Trash
|
||||
if let Location::Path(ref path) = self.location {
|
||||
if let Some(parent) = path.parent() {
|
||||
if let Location::Path(ref path) = self.location
|
||||
&& let Some(parent) = path.parent()
|
||||
{
|
||||
cd = Some(Location::Path(parent.to_owned()));
|
||||
}
|
||||
}
|
||||
}
|
||||
Message::Open(path_opt) => {
|
||||
match path_opt {
|
||||
Some(path) => {
|
||||
|
|
@ -3868,27 +3862,25 @@ impl Tab {
|
|||
match mode {
|
||||
Mode::App => {
|
||||
if is_only_one_selected {
|
||||
return ResolveResult::Cd(location.clone());
|
||||
ResolveResult::Cd(location.clone())
|
||||
} else {
|
||||
return ResolveResult::OpenInTab(path_opt.cloned());
|
||||
ResolveResult::OpenInTab(path_opt.cloned())
|
||||
}
|
||||
}
|
||||
Mode::Desktop => {
|
||||
return match location {
|
||||
Mode::Desktop => match location {
|
||||
Location::Trash => ResolveResult::OpenTrash,
|
||||
_ => ResolveResult::Open(path_opt.cloned()),
|
||||
};
|
||||
}
|
||||
},
|
||||
Mode::Dialog(_) => {
|
||||
if is_only_one_selected {
|
||||
return ResolveResult::Cd(location.clone());
|
||||
ResolveResult::Cd(location.clone())
|
||||
} else {
|
||||
return ResolveResult::Skip;
|
||||
ResolveResult::Skip
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return ResolveResult::Open(path_opt.cloned());
|
||||
ResolveResult::Open(path_opt.cloned())
|
||||
}
|
||||
}
|
||||
let mut open_files = Vec::new();
|
||||
|
|
@ -3933,8 +3925,8 @@ impl Tab {
|
|||
if mod_ctrl || mod_shift {
|
||||
self.update(Message::Click(click_i_opt), modifiers);
|
||||
}
|
||||
if let Some(ref mut items) = self.items_opt {
|
||||
if !click_i_opt
|
||||
if let Some(ref mut items) = self.items_opt
|
||||
&& !click_i_opt
|
||||
.is_some_and(|click_i| items.get(click_i).is_some_and(|x| x.selected))
|
||||
{
|
||||
// If item not selected, clear selection on other items
|
||||
|
|
@ -3942,7 +3934,6 @@ impl Tab {
|
|||
item.selected = Some(i) == click_i_opt;
|
||||
}
|
||||
}
|
||||
}
|
||||
//TODO: hack for clearing selecting when right clicking empty space
|
||||
self.last_right_click = click_i_opt;
|
||||
}
|
||||
|
|
@ -3988,13 +3979,13 @@ impl Tab {
|
|||
}
|
||||
Message::Resize(viewport) => {
|
||||
// Scroll to ensure focused item still in view
|
||||
if self.viewport_opt.map(|v| v.size()) != Some(viewport.size()) {
|
||||
if let Some(offset) = self.select_focus_scroll() {
|
||||
if self.viewport_opt.map(|v| v.size()) != Some(viewport.size())
|
||||
&& let Some(offset) = self.select_focus_scroll()
|
||||
{
|
||||
commands.push(Command::Iced(
|
||||
scrollable::scroll_to(self.scrollable_id.clone(), offset).into(),
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
self.viewport_opt = Some(viewport);
|
||||
}
|
||||
|
|
@ -4097,14 +4088,13 @@ impl Tab {
|
|||
}
|
||||
}
|
||||
Message::SelectLast => {
|
||||
if let Some(ref items) = self.items_opt {
|
||||
if let Some(last_pos) = items.iter().filter_map(|item| item.pos_opt.get()).max()
|
||||
if let Some(ref items) = self.items_opt
|
||||
&& let Some(last_pos) = items.iter().filter_map(|item| item.pos_opt.get()).max()
|
||||
&& self.select_position(last_pos.0, last_pos.1, mod_shift)
|
||||
{
|
||||
if self.select_position(last_pos.0, last_pos.1, mod_shift) {
|
||||
if let Some(offset) = self.select_focus_scroll() {
|
||||
commands.push(Command::Iced(
|
||||
scrollable::scroll_to(self.scrollable_id.clone(), offset)
|
||||
.into(),
|
||||
scrollable::scroll_to(self.scrollable_id.clone(), offset).into(),
|
||||
));
|
||||
}
|
||||
if let Some(id) = self.select_focus_id() {
|
||||
|
|
@ -4112,8 +4102,6 @@ impl Tab {
|
|||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Message::SetOpenWith(mime, id) => {
|
||||
commands.push(Command::SetOpenWith(mime, id));
|
||||
}
|
||||
|
|
@ -4134,15 +4122,15 @@ impl Tab {
|
|||
}
|
||||
}
|
||||
Message::TabComplete(path, completions) => {
|
||||
if let Some(edit_location) = &mut self.edit_location {
|
||||
if edit_location.location.path_opt() == Some(&path) {
|
||||
if let Some(edit_location) = &mut self.edit_location
|
||||
&& edit_location.location.path_opt() == Some(&path)
|
||||
{
|
||||
edit_location.completions = Some(completions);
|
||||
commands.push(Command::Iced(
|
||||
widget::text_input::focus(self.edit_location_id.clone()).into(),
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
Message::Thumbnail(path, thumbnail) => {
|
||||
if let Some(ref mut items) = self.items_opt {
|
||||
let location = Location::Path(path);
|
||||
|
|
@ -4274,11 +4262,11 @@ impl Tab {
|
|||
}
|
||||
Message::DirectorySize(path, dir_size) => {
|
||||
let location = Location::Path(path);
|
||||
if let Some(ref mut item) = self.parent_item_opt {
|
||||
if item.location_opt.as_ref() == Some(&location) {
|
||||
if let Some(ref mut item) = self.parent_item_opt
|
||||
&& item.location_opt.as_ref() == Some(&location)
|
||||
{
|
||||
item.dir_size.clone_from(&dir_size);
|
||||
}
|
||||
}
|
||||
if let Some(ref mut items) = self.items_opt {
|
||||
for item in items.iter_mut() {
|
||||
if item.location_opt.as_ref() == Some(&location) {
|
||||
|
|
@ -4315,14 +4303,13 @@ impl Tab {
|
|||
} else {
|
||||
// Select parent if location is not directory
|
||||
let mut selected_paths = None;
|
||||
if let Some(path) = location.path_opt() {
|
||||
if !path.is_dir() {
|
||||
if let Some(parent) = path.parent() {
|
||||
if let Some(path) = location.path_opt()
|
||||
&& !path.is_dir()
|
||||
&& let Some(parent) = path.parent()
|
||||
{
|
||||
selected_paths = Some(vec![path.clone()]);
|
||||
location = location.with_path(parent.to_path_buf());
|
||||
}
|
||||
}
|
||||
}
|
||||
if location != self.location || selected_paths.is_some() {
|
||||
if location.path_opt().is_none_or(|path| path.is_dir()) {
|
||||
if selected_paths.is_none() {
|
||||
|
|
@ -4529,9 +4516,10 @@ impl Tab {
|
|||
//TODO: display error messages when image not found?
|
||||
let mut name_opt = None;
|
||||
let mut element_opt: Option<Element<Message>> = None;
|
||||
if let Some(index) = self.select_focus {
|
||||
if let Some(items) = &self.items_opt {
|
||||
if let Some(item) = items.get(index) {
|
||||
if let Some(index) = self.select_focus
|
||||
&& let Some(items) = &self.items_opt
|
||||
&& let Some(item) = items.get(index)
|
||||
{
|
||||
name_opt = Some(widget::text::heading(&item.display_name));
|
||||
match item
|
||||
.thumbnail_opt
|
||||
|
|
@ -4597,8 +4585,7 @@ impl Tab {
|
|||
widget::image(image_handle).into()
|
||||
};
|
||||
|
||||
element_opt =
|
||||
Some(widget::container(content).center(Length::Fill).into());
|
||||
element_opt = Some(widget::container(content).center(Length::Fill).into());
|
||||
}
|
||||
ItemThumbnail::Svg(handle) => {
|
||||
element_opt = Some(
|
||||
|
|
@ -4610,21 +4597,15 @@ impl Tab {
|
|||
}
|
||||
ItemThumbnail::Text(text) => {
|
||||
element_opt = Some(
|
||||
widget::container(
|
||||
widget::text_editor(text).padding(space_xxs).class(
|
||||
cosmic::theme::iced::TextEditor::Custom(Box::new(
|
||||
text_editor_class,
|
||||
)),
|
||||
),
|
||||
)
|
||||
widget::container(widget::text_editor(text).padding(space_xxs).class(
|
||||
cosmic::theme::iced::TextEditor::Custom(Box::new(text_editor_class)),
|
||||
))
|
||||
.center(Length::Fill)
|
||||
.into(),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let mut column = widget::column::with_capacity(2);
|
||||
column = column.push(widget::Space::with_height(Length::Fixed(space_m.into())));
|
||||
|
|
@ -4860,8 +4841,9 @@ impl Tab {
|
|||
);
|
||||
let mut popover =
|
||||
widget::popover(text_input).position(widget::popover::Position::Bottom);
|
||||
if let Some(completions) = &edit_location.completions {
|
||||
if !completions.is_empty() {
|
||||
if let Some(completions) = &edit_location.completions
|
||||
&& !completions.is_empty()
|
||||
{
|
||||
let mut column =
|
||||
widget::column::with_capacity(completions.len()).padding(space_xxs);
|
||||
for (i, (name, _path)) in completions.iter().enumerate() {
|
||||
|
|
@ -4886,7 +4868,6 @@ impl Tab {
|
|||
.max_width(size.width - 140.0),
|
||||
);
|
||||
}
|
||||
}
|
||||
row = row.push(popover);
|
||||
let mut column = widget::column::with_capacity(4).padding([0, space_s]);
|
||||
column = column.push(row);
|
||||
|
|
@ -5911,14 +5892,14 @@ impl Tab {
|
|||
.wayland_on_right_press_window_position();
|
||||
|
||||
let mut popover = widget::popover(mouse_area);
|
||||
if let Some(point) = self.context_menu {
|
||||
if !cfg!(feature = "wayland") || !crate::is_wayland() {
|
||||
let context_menu = menu::context_menu(self, key_binds, &modifiers);
|
||||
if let Some(point) = self.context_menu
|
||||
&& (!cfg!(feature = "wayland") || !crate::is_wayland())
|
||||
{
|
||||
let context_menu = menu::context_menu(self, key_binds, modifiers);
|
||||
popover = popover
|
||||
.popup(context_menu)
|
||||
.position(widget::popover::Position::Point(point));
|
||||
}
|
||||
}
|
||||
|
||||
let mut tab_column = widget::column::with_capacity(3);
|
||||
if let Some(location_view) = location_view_opt {
|
||||
|
|
@ -5937,8 +5918,9 @@ impl Tab {
|
|||
}
|
||||
match &self.location {
|
||||
Location::Trash => {
|
||||
if let Some(items) = self.items_opt() {
|
||||
if !items.is_empty() {
|
||||
if let Some(items) = self.items_opt()
|
||||
&& !items.is_empty()
|
||||
{
|
||||
tab_column = tab_column.push(
|
||||
widget::layer_container(widget::row::with_children([
|
||||
widget::horizontal_space().into(),
|
||||
|
|
@ -5953,7 +5935,6 @@ impl Tab {
|
|||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
Location::Network(uri, _display_name, _path) if uri == "network:///" => {
|
||||
tab_column = tab_column.push(
|
||||
widget::layer_container(widget::row::with_children([
|
||||
|
|
@ -6248,11 +6229,11 @@ impl Tab {
|
|||
let mut selected_items: Vec<&Item> =
|
||||
items.iter().filter(|item| item.selected).collect();
|
||||
|
||||
if selected_items.is_empty() {
|
||||
if let Some(p) = self.parent_item_opt.as_ref() {
|
||||
if selected_items.is_empty()
|
||||
&& let Some(p) = self.parent_item_opt.as_ref()
|
||||
{
|
||||
selected_items.push(p)
|
||||
}
|
||||
}
|
||||
for item in selected_items {
|
||||
// Item must have a path
|
||||
if let Some(path) = item.path_opt().cloned() {
|
||||
|
|
|
|||
|
|
@ -66,11 +66,11 @@ impl ThumbnailCacher {
|
|||
if let (Some(cache_base_dir), Ok(metadata)) = (
|
||||
THUMBNAIL_CACHE_BASE_DIR.as_ref(),
|
||||
std::fs::metadata(&self.file_path),
|
||||
) {
|
||||
if metadata.is_file() && self.file_path.starts_with(cache_base_dir) {
|
||||
) && metadata.is_file()
|
||||
&& self.file_path.starts_with(cache_base_dir)
|
||||
{
|
||||
return CachedThumbnail::Valid((self.file_path.clone(), None));
|
||||
}
|
||||
}
|
||||
|
||||
// Use cached thumbnail if it is valid.
|
||||
if self.is_thumbnail_valid(&self.thumbnail_path) {
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
// Copyright 2023 System76 <info@system76.com>
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
use cosmic::desktop::fde::GenericEntry;
|
||||
use mime_guess::Mime;
|
||||
use rustc_hash::FxHashMap;
|
||||
use std::{
|
||||
|
|
@ -115,7 +116,7 @@ impl ThumbnailerCache {
|
|||
|
||||
//TODO: handle directory specific behavior
|
||||
for path in thumbnailer_paths {
|
||||
let entry = match freedesktop_entry_parser::parse_entry(&path) {
|
||||
let entry = match GenericEntry::from_path(&path) {
|
||||
Ok(ok) => ok,
|
||||
Err(err) => {
|
||||
log::warn!("failed to parse {}: {}", path.display(), err);
|
||||
|
|
@ -124,12 +125,18 @@ impl ThumbnailerCache {
|
|||
};
|
||||
|
||||
//TODO: use TryExec?
|
||||
let section = entry.section("Thumbnailer Entry");
|
||||
let Some(exec) = section.attr("Exec") else {
|
||||
let Some(section) = entry.group("Thumbnailer Entry") else {
|
||||
log::warn!(
|
||||
"missing Thumbnailer Entry section for thumbnailer {}",
|
||||
path.display()
|
||||
);
|
||||
continue;
|
||||
};
|
||||
let Some(exec) = section.entry("Exec") else {
|
||||
log::warn!("missing Exec attribute for thumbnailer {}", path.display());
|
||||
continue;
|
||||
};
|
||||
let Some(mime_types) = section.attr("MimeType") else {
|
||||
let Some(mime_types) = section.entry("MimeType") else {
|
||||
log::warn!(
|
||||
"missing MimeType attribute for thumbnailer {}",
|
||||
path.display()
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue