Compare commits

..
4 Commits
Author SHA1 Message Date
nvrl 0b81b76cb7 feat(gui): branded app icon in the sidebar header and login
The sidebar header and the login page used the generic folder-remote-symbolic
icon. Ship the app icon from packaging/ in the GUI resources and use it in
both places, so the in-app brand matches the launcher even when the app runs
uninstalled.
2026-09-27 23:28:00 +02:00
nvrl 7470bd89f3 chore(release): 2.2.2 2026-09-27 23:28:00 +02:00
nvrl e6cb52339d fix(fuse,core): list and resolve conflict copies in synced folders, log each once (B99)
The conflict sweep flagged divergent copies anywhere in the tree but kept its
dedupe set in memory, so every daemon start logged them again. The conflict
listing only covered My Files, so copies in on-demand sync folders were never
listed, and the Activity page showed them as resolved while they were still on disk.

List copies in synced folders by their absolute local path, route resolves
to the mount that serves them, flag only what the listing can show, and
consult the activity log before flagging a copy again.
2026-09-27 23:28:00 +02:00
Nils Pukropp 912acc760a chore(aur): update packages to 2.2.1 2026-09-24 18:08:08 +00:00
23 changed files with 217 additions and 55 deletions

No files matched your search

Generated
+4 -4
View File
@@ -3449,7 +3449,7 @@ checksum = "2ee67f1008b1ba2321834326597b8e186293b049a023cdef258527550b9935b4"
[[package]] [[package]]
name = "pdfs-cli" name = "pdfs-cli"
version = "2.2.1" version = "2.2.2"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"clap", "clap",
@@ -3464,7 +3464,7 @@ dependencies = [
[[package]] [[package]]
name = "pdfs-core" name = "pdfs-core"
version = "2.2.1" version = "2.2.2"
dependencies = [ dependencies = [
"async-trait", "async-trait",
"dbus-secret-service-keyring-store", "dbus-secret-service-keyring-store",
@@ -3488,7 +3488,7 @@ dependencies = [
[[package]] [[package]]
name = "pdfs-fuse" name = "pdfs-fuse"
version = "2.2.1" version = "2.2.2"
dependencies = [ dependencies = [
"base64 0.23.1", "base64 0.23.1",
"fuser", "fuser",
@@ -3508,7 +3508,7 @@ dependencies = [
[[package]] [[package]]
name = "pdfs-gui" name = "pdfs-gui"
version = "2.2.1" version = "2.2.2"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"async-channel", "async-channel",
+1 -1
View File
@@ -3,7 +3,7 @@ resolver = "2"
members = ["crates/pdfs-core", "crates/pdfs-fuse", "crates/pdfs-cli", "crates/pdfs-gui"] members = ["crates/pdfs-core", "crates/pdfs-fuse", "crates/pdfs-cli", "crates/pdfs-gui"]
[workspace.package] [workspace.package]
version = "2.2.1" version = "2.2.2"
edition = "2024" edition = "2024"
license = "MIT" license = "MIT"
authors = ["Nils Pukropp <contact@narl.io>"] authors = ["Nils Pukropp <contact@narl.io>"]
+7 -4
View File
@@ -429,10 +429,12 @@ pub enum Request {
/// Retry a backed-off queued op now instead of waiting out its backoff; /// Retry a backed-off queued op now instead of waiting out its backoff;
/// every failed op when `id` is `None`. Replies with [`Response::Ok`]. /// every failed op when `id` is `None`. Replies with [`Response::Ok`].
RetryPendingOp { id: Option<i64> }, RetryPendingOp { id: Option<i64> },
/// List the `(sync-conflict …)` copies under My Files. Replies with /// List the `(sync-conflict …)` copies under My Files and in synced
/// folders. Replies with
/// [`Response::Conflicts`]. /// [`Response::Conflicts`].
ListConflicts, ListConflicts,
/// Resolve one conflict copy (`path` mountpoint-relative or absolute). /// Resolve one conflict copy (`path` mountpoint-relative, or absolute for a
/// copy in a synced folder).
/// Everything removed goes to the Proton trash. Replies with /// Everything removed goes to the Proton trash. Replies with
/// [`Response::Ok`]. /// [`Response::Ok`].
ResolveConflict { path: String, keep: ConflictKeep }, ResolveConflict { path: String, keep: ConflictKeep },
@@ -701,9 +703,10 @@ pub struct DeviceInfo {
/// [`Response::Conflicts`]). /// [`Response::Conflicts`]).
#[derive(Serialize, Deserialize, Debug, Clone)] #[derive(Serialize, Deserialize, Debug, Clone)]
pub struct ConflictInfo { pub struct ConflictInfo {
/// Mountpoint-relative path of the copy. /// Mountpoint-relative path of the copy, or its absolute local path when it
/// sits in a synced folder.
pub path: String, pub path: String,
/// Mountpoint-relative path of the file it is a copy of. /// Path of the file it is a copy of, in the same form as `path`.
pub original_path: String, pub original_path: String,
/// False when the original is gone and only the copy is left. /// False when the original is gone and only the copy is left.
pub original_exists: bool, pub original_exists: bool,
+11
View File
@@ -29,6 +29,17 @@ impl Db {
Ok(()) Ok(())
} }
/// Whether the kept log already holds an entry of `kind` for `target`. Lets
/// the daemon report something once across restarts rather than once per
/// run, without keeping a second table in step with the log.
pub fn activity_has(&self, kind: ActivityKind, target: &str) -> Result<bool> {
let kind = serde_json::to_string(&kind)?;
let conn = self.read();
let mut stmt =
conn.prepare("SELECT 1 FROM activity WHERE kind = ?1 AND target = ?2 LIMIT 1")?;
Ok(stmt.exists(params![kind, target])?)
}
/// The most recent activity, newest first, capped at `limit` entries. Rows /// The most recent activity, newest first, capped at `limit` entries. Rows
/// whose stored `kind` no longer parses (written by an older build) are /// whose stored `kind` no longer parses (written by an older build) are
/// skipped rather than failing the whole read. /// skipped rather than failing the whole read.
+23
View File
@@ -1414,6 +1414,29 @@ fn activity_reads_back_newest_first() {
assert_eq!(db.activity_list(1).unwrap().len(), 1); assert_eq!(db.activity_list(1).unwrap().len(), 1);
} }
#[test]
fn activity_has_finds_only_matching_kind_and_target() {
let db = Db::open_in_memory().unwrap();
db.activity_add(&activity(
"a (sync-conflict 1).txt",
ActivityKind::Conflict,
false,
))
.unwrap();
db.activity_add(&activity("b.txt", ActivityKind::Upload, true))
.unwrap();
assert!(
db.activity_has(ActivityKind::Conflict, "a (sync-conflict 1).txt")
.unwrap()
);
assert!(
!db.activity_has(ActivityKind::Upload, "a (sync-conflict 1).txt")
.unwrap()
);
assert!(!db.activity_has(ActivityKind::Conflict, "b.txt").unwrap());
}
#[test] #[test]
fn activity_prunes_to_the_keep_limit() { fn activity_prunes_to_the_keep_limit() {
let db = Db::open_in_memory().unwrap(); let db = Db::open_in_memory().unwrap();
+28 -5
View File
@@ -14,15 +14,38 @@
//! that is open, being written, or still owed an upload, for the same reason the //! that is open, being written, or still owed an upload, for the same reason the
//! sweep does: the node is about to change under the decision. //! sweep does: the node is about to change under the decision.
//! //!
//! Only copies under My Files are handled here. A copy inside a mirror folder is //! Copies under My Files are named by their mountpoint-relative path. Copies
//! a local file first; the sync engine owns it. //! inside a synced folder are named by their absolute local path, so a resolve
//! request routes to the on-demand mount that serves them. A copy inside a
//! mirror folder is listed too, but it is a local file first: the sync engine
//! owns it, so it is resolved by editing the folder, not through here.
use super::*; use super::*;
use pdfs_core::control::{ConflictInfo, ConflictKeep}; use pdfs_core::control::{ConflictInfo, ConflictKeep};
use sweep::conflict_base_name; use sweep::conflict_base_name;
impl Core { impl Core {
/// Every conflict copy under My Files, with the file it is a copy of. /// Where the user finds a conflict copy: its mountpoint-relative path under
/// My Files, or its absolute local path inside a synced folder. `None` for a
/// node no location of this machine shows, such as one in another
/// computer's device tree.
///
/// The sweep only flags what this can name, and the listing uses it too. A
/// flagged copy the listing cannot see reads as resolved in the activity
/// feed while it still sits in the folder (`docs/BUGS.md` B99).
pub(crate) fn conflict_path(&self, roots: &SearchRoots, uid: &NodeUid) -> Option<String> {
let uid = uid.to_string();
if let Ok(Some(path)) = self
.db
.path_relative_to(&self.primary_root_uid.to_string(), &uid)
{
return Some(path);
}
let drive_path = self.db.node_path(&uid).ok().flatten()?;
roots.resolve(&drive_path)
}
/// Every conflict copy this machine shows, with the file it is a copy of.
pub(crate) fn list_conflicts(&self) -> CoreResult<Vec<ConflictInfo>> { pub(crate) fn list_conflicts(&self) -> CoreResult<Vec<ConflictInfo>> {
let stored = self let stored = self
.db .db
@@ -37,13 +60,13 @@ impl Core {
for node in &nodes { for node in &nodes {
by_parent_name.insert((node.parent_uid.as_ref(), node.name.as_str()), node); by_parent_name.insert((node.parent_uid.as_ref(), node.name.as_str()), node);
} }
let root = self.primary_root_uid.to_string(); let roots = self.search_roots();
let mut items = Vec::new(); let mut items = Vec::new();
for node in &nodes { for node in &nodes {
let Some(base) = conflict_base_name(&node.name) else { let Some(base) = conflict_base_name(&node.name) else {
continue; continue;
}; };
let Ok(Some(path)) = self.db.path_relative_to(&root, &node.uid.to_string()) else { let Some(path) = self.conflict_path(&roots, &node.uid) else {
continue; continue;
}; };
let original = by_parent_name let original = by_parent_name
+40 -6
View File
@@ -185,6 +185,38 @@ fn route_to_mount(core: &Core, mountpoint: &Path, path: &str) -> CoreResult<(Cor
rel_to_mount(mountpoint, path).map(|rel| (core.clone(), rel)) rel_to_mount(mountpoint, path).map(|rel| (core.clone(), rel))
} }
/// [`route_to_mount`] for a conflict copy. The listing names a copy inside a
/// synced folder by its absolute local path (`docs/BUGS.md` B99), so the
/// resolve has to run against the on-demand mount serving it. A mirror folder
/// has no mount to run against: its copy is an ordinary local file.
fn route_conflict(core: &Core, mountpoint: &Path, path: &str) -> CoreResult<(Core, PathBuf)> {
let routed = route_to_mount(core, mountpoint, path).map_err(|e| {
let p = Path::new(path);
let mirrored = core
.db
.sync_folder_list()
.unwrap_or_default()
.iter()
.any(|folder| folder.mode == "mirror" && p.starts_with(&folder.local_path));
if mirrored {
CoreError::invalid(format!(
"{path} is in a mirrored folder; rename or delete the local file there"
))
} else {
e
}
})?;
if routed
.1
.components()
.all(|component| matches!(component, Component::Normal(_) | Component::CurDir))
{
Ok(routed)
} else {
Err(CoreError::invalid(format!("{path} escapes the mountpoint")))
}
}
/// Turn a CLI-supplied path into a mountpoint-relative path. An absolute path /// Turn a CLI-supplied path into a mountpoint-relative path. An absolute path
/// must live under `mountpoint`; a relative path is taken as already relative to /// must live under `mountpoint`; a relative path is taken as already relative to
/// the mount root. /// the mount root.
@@ -1123,13 +1155,15 @@ fn handle_control_conn(core: &Core, username: &str, mountpoint: &Path, stream: U
Ok(items) => CtlResponse::Conflicts { items }, Ok(items) => CtlResponse::Conflicts { items },
Err(e) => CtlResponse::error(e), Err(e) => CtlResponse::error(e),
}, },
Ok(CtlRequest::ResolveConflict { path, keep }) => match rel_to_mount(mountpoint, &path) { Ok(CtlRequest::ResolveConflict { path, keep }) => {
Ok(rel) => match core.resolve_conflict(&rel, &keep) { match route_conflict(core, mountpoint, &path) {
Ok(message) => CtlResponse::Ok { message }, Ok((core, rel)) => match core.resolve_conflict(&rel, &keep) {
Ok(message) => CtlResponse::Ok { message },
Err(e) => CtlResponse::error(e),
},
Err(e) => CtlResponse::error(e), Err(e) => CtlResponse::error(e),
}, }
Err(e) => CtlResponse::error(e), }
},
Ok(CtlRequest::ListPendingOps) => match core.pending_op_infos() { Ok(CtlRequest::ListPendingOps) => match core.pending_op_infos() {
Ok(items) => CtlResponse::PendingOps { items }, Ok(items) => CtlResponse::PendingOps { items },
Err(e) => CtlResponse::error(e), Err(e) => CtlResponse::error(e),
+4 -3
View File
@@ -558,9 +558,10 @@ struct Core {
/// instead of polling or blocking for the whole run. See /// instead of polling or blocking for the whole run. See
/// [`Core::await_trash_refresh`]. /// [`Core::await_trash_refresh`].
trash_progress: Arc<tokio::sync::Notify>, trash_progress: Arc<tokio::sync::Notify>,
/// Conflict copies the sweep has already flagged as needing attention this /// Conflict copies the sweep has already checked against the activity log
/// run, so a divergent `(sync-conflict …)` file is logged once rather than /// this run, so each pass does not ask the database again. The log itself
/// on every sweep pass. See [`Core::run_conflict_sweep_loop`]. /// is what keeps a copy from being flagged again after a restart. See
/// [`Core::run_conflict_sweep_loop`] and `docs/BUGS.md` B99.
conflict_notified: Arc<Mutex<HashSet<NodeUid>>>, conflict_notified: Arc<Mutex<HashSet<NodeUid>>>,
/// Whether the conflict sweep may actually trash the duplicates it finds, or /// Whether the conflict sweep may actually trash the duplicates it finds, or
/// only report them. Resolved once at mount from config + environment /// only report them. Resolved once at mount from config + environment
+22 -3
View File
@@ -109,6 +109,7 @@ impl Core {
by_parent_name.insert((parent, node.name.as_str()), node); by_parent_name.insert((parent, node.name.as_str()), node);
} }
let roots = self.search_roots();
let online = self.online.load(std::sync::atomic::Ordering::Relaxed); let online = self.online.load(std::sync::atomic::Ordering::Relaxed);
for node in &nodes { for node in &nodes {
if node.trashed || node.is_folder() { if node.trashed || node.is_folder() {
@@ -132,7 +133,16 @@ impl Core {
self.remove_conflict_copy(node, &base); self.remove_conflict_copy(node, &base);
} }
} }
SweepAction::Flag(detail) => self.flag_conflict(&node.uid, &node.name, detail), // Only flag what the conflict listing can show. Anything else
// would sit in the feed as "resolved" with no way to act on
// it (`docs/BUGS.md` B99).
SweepAction::Flag(detail) => {
if self.conflict_path(&roots, &node.uid).is_some() {
self.flag_conflict(&node.uid, &node.name, detail);
} else {
debug!(uid = %node.uid, name = %node.name, "conflict sweep: copy is outside every location; not flagged");
}
}
} }
} }
} }
@@ -273,12 +283,21 @@ impl Core {
busy busy
} }
/// Surface a conflict copy that needs the user's attention, at most once per /// Surface a conflict copy that needs the user's attention, once. The
/// daemon run so the activity feed is not spammed each pass. /// in-memory set spares the database each pass; the activity log itself is
/// what keeps a restart from logging the same copy again (`docs/BUGS.md`
/// B99). A copy's name carries its own timestamp, so the name identifies it.
fn flag_conflict(&self, uid: &NodeUid, name: &str, detail: String) { fn flag_conflict(&self, uid: &NodeUid, name: &str, detail: String) {
if !self.conflict_notified.lock().insert(uid.clone()) { if !self.conflict_notified.lock().insert(uid.clone()) {
return; return;
} }
match self.db.activity_has(ActivityKind::Conflict, name) {
Ok(false) => {}
Ok(true) => return,
// Cannot tell: a repeat entry is harmless, a missing one hides a
// conflict.
Err(e) => warn!(%uid, name, error = ?e, "conflict sweep: activity check failed"),
}
debug!(%uid, name, detail, "conflict sweep: flagged divergent conflict copy"); debug!(%uid, name, detail, "conflict sweep: flagged divergent conflict copy");
self.log_activity(ActivityKind::Conflict, name, detail, false); self.log_activity(ActivityKind::Conflict, name, detail, false);
} }
+1
View File
@@ -26,4 +26,5 @@ fn main() {
println!("cargo:rerun-if-changed=resources/pdfs.gresource.xml"); println!("cargo:rerun-if-changed=resources/pdfs.gresource.xml");
println!("cargo:rerun-if-changed=resources/icons"); println!("cargo:rerun-if-changed=resources/icons");
println!("cargo:rerun-if-changed=resources/style.css"); println!("cargo:rerun-if-changed=resources/style.css");
println!("cargo:rerun-if-changed=../../packaging/io.narl.proton-drive-linux.svg");
} }
@@ -9,6 +9,9 @@
<file preprocess="xml-stripblanks">scalable/actions/pdfs-upload-symbolic.svg</file> <file preprocess="xml-stripblanks">scalable/actions/pdfs-upload-symbolic.svg</file>
<file preprocess="xml-stripblanks">scalable/actions/pdfs-offline-symbolic.svg</file> <file preprocess="xml-stripblanks">scalable/actions/pdfs-offline-symbolic.svg</file>
<file preprocess="xml-stripblanks">scalable/actions/pdfs-online-only-symbolic.svg</file> <file preprocess="xml-stripblanks">scalable/actions/pdfs-online-only-symbolic.svg</file>
<!-- The app icon, from the one file packaging installs, so the in-app brand
matches the launcher and resolves even when nothing is installed. -->
<file alias="scalable/apps/io.narl.proton-drive-linux.svg" preprocess="xml-stripblanks">../../../packaging/io.narl.proton-drive-linux.svg</file>
</gresource> </gresource>
<!-- The app stylesheet, loaded once at startup. Colours come from the <!-- The app stylesheet, loaded once at startup. Colours come from the
libadwaita palette so the app follows the system accent, light/dark and libadwaita palette so the app follows the system accent, light/dark and
-1
View File
@@ -9,7 +9,6 @@
/* Brand */ /* Brand */
.brand-title { font-size: 1.6rem; font-weight: 800; } .brand-title { font-size: 1.6rem; font-weight: 800; }
.brand-icon { color: #6d4aff; }
/* Sidebar footer */ /* Sidebar footer */
.sidebar-footer { padding: 6px 12px 12px 12px; } .sidebar-footer { padding: 6px 12px 12px 12px; }
+2 -2
View File
@@ -884,8 +884,8 @@ fn build_sidebar(footer: &gtk4::Box) -> (adw::NavigationPage, gtk4::ListBox) {
}); });
let brand = gtk4::Box::new(gtk4::Orientation::Horizontal, 8); let brand = gtk4::Box::new(gtk4::Orientation::Horizontal, 8);
let icon = gtk4::Image::from_icon_name("folder-remote-symbolic"); let icon = gtk4::Image::from_icon_name(APP_ID);
icon.add_css_class("brand-icon"); icon.set_pixel_size(24);
brand.append(&icon); brand.append(&icon);
let name = gtk4::Label::new(Some("Proton Drive")); let name = gtk4::Label::new(Some("Proton Drive"));
name.add_css_class("heading"); name.add_css_class("heading");
+2 -3
View File
@@ -52,9 +52,8 @@ pub(crate) fn build_login_page() -> (gtk4::Widget, LoginState) {
login_status.add_css_class("dim-label"); login_status.add_css_class("dim-label");
let header = gtk4::Box::new(gtk4::Orientation::Vertical, 4); let header = gtk4::Box::new(gtk4::Orientation::Vertical, 4);
let logo = gtk4::Image::from_icon_name("folder-remote-symbolic"); let logo = gtk4::Image::from_icon_name(APP_ID);
logo.set_pixel_size(64); logo.set_pixel_size(96);
logo.add_css_class("brand-icon");
let title = gtk4::Label::new(Some("Proton Drive")); let title = gtk4::Label::new(Some("Proton Drive"));
title.add_css_class("brand-title"); title.add_css_class("brand-title");
header.append(&logo); header.append(&logo);
+36
View File
@@ -12,6 +12,42 @@ Conventions:
--- ---
## B99 — Conflict copies in synced folders are logged again on every start and shown as "Resolved"
**Status:** Fixed (unverified). No daemon carrying the change has been restarted against the
affected copies yet.
**Found:** 2026-09-27. The Activity page listed the same six conflict copies every day, e.g.
`Conflict [EMBER] Oshi no Ko S2 - 12 (sync-conflict 1786815864).mkv` with `Differs from … · 24 times
since Sep 20, 13:13 · Resolved`. `find` showed all six copies still on disk, in `~/Videos` and
`~/Documents`. Both are on-demand sync folders. `pdfs conflicts` answered `No conflicts.`. The
journal showed the daemon mounting at 17:29:33, and the entries were stamped 17:30. That is the
sweep's 30-second warmup.
**Where:** `crates/pdfs-fuse/src/sweep.rs` (`flag_conflict`, `sweep_conflicts_once`),
`crates/pdfs-fuse/src/conflicts.rs` (`list_conflicts`), `crates/pdfs-fuse/src/control.rs`
(`ResolveConflict`).
**Cause.** There were two faults.
- The sweep kept its "already flagged" set only in memory. Every daemon start forgot it, so the
first pass logged every open conflict again.
- The sweep flags copies anywhere in the account's own tree. `list_conflicts` only reported copies
below My Files, and resolving only accepted paths under the primary mount. A copy in a synced
folder was logged but never listed, so the GUI read it as resolved. It also could not be
resolved from the GUI or the CLI.
**Fix.** `Core::conflict_path` names a copy by its mountpoint-relative path under My Files, or
by its absolute local path inside a synced folder. It reuses the roots that search already
resolves hits through. The listing uses it, and the sweep flags only copies it can name. So the
feed never holds a conflict the listing cannot answer for. `ResolveConflict` routes an absolute
path to the on-demand mount that serves it. For a copy in a mirror folder, it says to edit the
local file instead. Before logging, `flag_conflict` checks the activity log
(`Db::activity_has`), so a restart does not log the same copy again. The copy's name carries its
own timestamp, so the name identifies the copy. No schema change.
Regression test: `activity_has_finds_only_matching_kind_and_target`.
---
## B98 — A superseded upload spins its drain thread forever and shows as "Uploading" at 0 bytes ## B98 — A superseded upload spins its drain thread forever and shows as "Uploading" at 0 bytes
**Status:** Fixed (unverified) — the daemon carrying the change has not been driven against a **Status:** Fixed (unverified) — the daemon carrying the change has not been driven against a
+10
View File
@@ -11,6 +11,15 @@ scratch (user data in `staging/` and `recovery/` is never touched by this).
## [Unreleased] ## [Unreleased]
## [2.2.2] — 2026-09-27
Schema: **32** (unchanged).
### Fixed
- **Conflicts in synced folders.** A conflict copy inside a synced folder is now listed under
Needs attention and can be resolved there or with `pdfs conflicts`. It used to be logged again
after every restart and marked "Resolved" while it was still on disk (B99).
## [2.2.1] — 2026-09-24 ## [2.2.1] — 2026-09-24
Schema: **32** (unchanged). Schema: **32** (unchanged).
@@ -1038,6 +1047,7 @@ First stable release: FUSE files-on-demand mount, sync daemon under `proton-driv
- The outstanding FUSE defects tracked in `docs/BUGS.md`, plus a truncate defect, validated - The outstanding FUSE defects tracked in `docs/BUGS.md`, plus a truncate defect, validated
by a new POSIX compliance suite for the filesystem. by a new POSIX compliance suite for the filesystem.
[2.2.2]: https://github.com/narrrl/proton-drive-linux/releases/tag/v2.2.2
[2.2.1]: https://github.com/narrrl/proton-drive-linux/releases/tag/v2.2.1 [2.2.1]: https://github.com/narrrl/proton-drive-linux/releases/tag/v2.2.1
[2.1.0]: https://github.com/narrrl/proton-drive-linux/releases/tag/v2.1.0 [2.1.0]: https://github.com/narrrl/proton-drive-linux/releases/tag/v2.1.0
[2.0.0]: https://github.com/narrrl/proton-drive-linux/releases/tag/v2.0.0 [2.0.0]: https://github.com/narrrl/proton-drive-linux/releases/tag/v2.0.0
+1 -1
View File
@@ -9,7 +9,7 @@
# Build + install: cd packaging && makepkg -fi # Build + install: cd packaging && makepkg -fi
pkgname=proton-drive-linux pkgname=proton-drive-linux
pkgver=2.2.1 pkgver=2.2.2
pkgrel=1 pkgrel=1
pkgdesc="Unofficial Proton Drive client for Linux: files-on-demand FUSE mount, CLI, GTK4 app + tray" pkgdesc="Unofficial Proton Drive client for Linux: files-on-demand FUSE mount, CLI, GTK4 app + tray"
arch=('x86_64') arch=('x86_64')
@@ -1,6 +1,6 @@
pkgbase = proton-drive-for-linux-bin pkgbase = proton-drive-for-linux-bin
pkgdesc = Unofficial Proton Drive client: FUSE files-on-demand mount, CLI, GTK4 app, tray (binaries) pkgdesc = Unofficial Proton Drive client: FUSE files-on-demand mount, CLI, GTK4 app, tray (binaries)
pkgver = 2.2.0 pkgver = 2.2.1
pkgrel = 1 pkgrel = 1
url = https://github.com/narrrl/proton-drive-linux url = https://github.com/narrrl/proton-drive-linux
arch = x86_64 arch = x86_64
@@ -20,21 +20,21 @@ pkgbase = proton-drive-for-linux-bin
optdepends = xdg-utils: "Open folder" action in the tray menu optdepends = xdg-utils: "Open folder" action in the tray menu
optdepends = fzf: search-as-you-type launcher (pdfs-prompt --fzf) optdepends = fzf: search-as-you-type launcher (pdfs-prompt --fzf)
optdepends = foot: terminal that hosts pdfs-prompt --fzf when started from a keybinding optdepends = foot: terminal that hosts pdfs-prompt --fzf when started from a keybinding
provides = proton-drive-for-linux=2.2.0 provides = proton-drive-for-linux=2.2.1
provides = proton-drive-linux=2.2.0 provides = proton-drive-linux=2.2.1
provides = pdfs provides = pdfs
conflicts = proton-drive-linux conflicts = proton-drive-linux
conflicts = proton-drive-for-linux conflicts = proton-drive-for-linux
conflicts = proton-drive-for-linux-git conflicts = proton-drive-for-linux-git
options = !strip options = !strip
options = !debug options = !debug
source = proton-drive-linux-2.2.0-x86_64.tar.gz::https://github.com/narrrl/proton-drive-linux/releases/download/v2.2.0/proton-drive-linux-2.2.0-x86_64.tar.gz source = proton-drive-linux-2.2.1-x86_64.tar.gz::https://github.com/narrrl/proton-drive-linux/releases/download/v2.2.1/proton-drive-linux-2.2.1-x86_64.tar.gz
source = io.narl.proton-drive-linux.desktop::https://raw.githubusercontent.com/narrrl/proton-drive-linux/v2.2.0/packaging/io.narl.proton-drive-linux.desktop source = io.narl.proton-drive-linux.desktop::https://raw.githubusercontent.com/narrrl/proton-drive-linux/v2.2.1/packaging/io.narl.proton-drive-linux.desktop
source = io.narl.proton-drive-linux-tray.desktop::https://raw.githubusercontent.com/narrrl/proton-drive-linux/v2.2.0/packaging/io.narl.proton-drive-linux-tray.desktop source = io.narl.proton-drive-linux-tray.desktop::https://raw.githubusercontent.com/narrrl/proton-drive-linux/v2.2.1/packaging/io.narl.proton-drive-linux-tray.desktop
source = io.narl.proton-drive-linux.svg::https://raw.githubusercontent.com/narrrl/proton-drive-linux/v2.2.0/packaging/io.narl.proton-drive-linux.svg source = io.narl.proton-drive-linux.svg::https://raw.githubusercontent.com/narrrl/proton-drive-linux/v2.2.1/packaging/io.narl.proton-drive-linux.svg
source = proton-drive.service::https://raw.githubusercontent.com/narrrl/proton-drive-linux/v2.2.0/packaging/proton-drive.service source = proton-drive.service::https://raw.githubusercontent.com/narrrl/proton-drive-linux/v2.2.1/packaging/proton-drive.service
source = LICENSE-2.2.0::https://raw.githubusercontent.com/narrrl/proton-drive-linux/v2.2.0/LICENSE source = LICENSE-2.2.1::https://raw.githubusercontent.com/narrrl/proton-drive-linux/v2.2.1/LICENSE
sha256sums = 4d746d83abd5174acb324236b6146c083a43b8c25e8d30e31c2a6d5b594ba479 sha256sums = 69f0d6996feb95b18d81bb96e9373c61fddb64ab17b50c94cd83ef8d5e0a1425
sha256sums = d190d6771fff0b975271fb62fd3fb0fa8bbf17d7278c10853c18557855b0c123 sha256sums = d190d6771fff0b975271fb62fd3fb0fa8bbf17d7278c10853c18557855b0c123
sha256sums = 1d95e250370220e3bb6aead6ce34d1d6cc759f42c4803b75f1667e78ff47c6c0 sha256sums = 1d95e250370220e3bb6aead6ce34d1d6cc759f42c4803b75f1667e78ff47c6c0
sha256sums = e598a93c2715c3a9fdc7fb937902083d4a1135e8fbdfe6fe7e37f0383ebbd828 sha256sums = e598a93c2715c3a9fdc7fb937902083d4a1135e8fbdfe6fe7e37f0383ebbd828
@@ -1,7 +1,7 @@
# Maintainer: Nils Pukropp <contact@narl.io> # Maintainer: Nils Pukropp <contact@narl.io>
pkgname=proton-drive-for-linux-bin pkgname=proton-drive-for-linux-bin
pkgver=2.2.0 pkgver=2.2.1
pkgrel=1 pkgrel=1
pkgdesc="Unofficial Proton Drive client: FUSE files-on-demand mount, CLI, GTK4 app, tray (binaries)" pkgdesc="Unofficial Proton Drive client: FUSE files-on-demand mount, CLI, GTK4 app, tray (binaries)"
arch=('x86_64') arch=('x86_64')
@@ -30,7 +30,7 @@ source=("proton-drive-linux-$pkgver-x86_64.tar.gz::$url/releases/download/$_tag/
"io.narl.proton-drive-linux.svg::$_raw/packaging/io.narl.proton-drive-linux.svg" "io.narl.proton-drive-linux.svg::$_raw/packaging/io.narl.proton-drive-linux.svg"
"proton-drive.service::$_raw/packaging/proton-drive.service" "proton-drive.service::$_raw/packaging/proton-drive.service"
"LICENSE-$pkgver::$_raw/LICENSE") "LICENSE-$pkgver::$_raw/LICENSE")
sha256sums=('4d746d83abd5174acb324236b6146c083a43b8c25e8d30e31c2a6d5b594ba479' sha256sums=('69f0d6996feb95b18d81bb96e9373c61fddb64ab17b50c94cd83ef8d5e0a1425'
'd190d6771fff0b975271fb62fd3fb0fa8bbf17d7278c10853c18557855b0c123' 'd190d6771fff0b975271fb62fd3fb0fa8bbf17d7278c10853c18557855b0c123'
'1d95e250370220e3bb6aead6ce34d1d6cc759f42c4803b75f1667e78ff47c6c0' '1d95e250370220e3bb6aead6ce34d1d6cc759f42c4803b75f1667e78ff47c6c0'
'e598a93c2715c3a9fdc7fb937902083d4a1135e8fbdfe6fe7e37f0383ebbd828' 'e598a93c2715c3a9fdc7fb937902083d4a1135e8fbdfe6fe7e37f0383ebbd828'
@@ -1,6 +1,6 @@
pkgbase = proton-drive-for-linux-git pkgbase = proton-drive-for-linux-git
pkgdesc = Unofficial Proton Drive client: FUSE files-on-demand mount, CLI, GTK4 app, tray (git) pkgdesc = Unofficial Proton Drive client: FUSE files-on-demand mount, CLI, GTK4 app, tray (git)
pkgver = 2.2.0.r1.g82e9654 pkgver = 2.2.1.r0.g5109427
pkgrel = 1 pkgrel = 1
url = https://github.com/narrrl/proton-drive-linux url = https://github.com/narrrl/proton-drive-linux
arch = x86_64 arch = x86_64
@@ -23,8 +23,8 @@ pkgbase = proton-drive-for-linux-git
optdepends = xdg-utils: "Open folder" action in the tray menu optdepends = xdg-utils: "Open folder" action in the tray menu
optdepends = fzf: search-as-you-type launcher (pdfs-prompt --fzf) optdepends = fzf: search-as-you-type launcher (pdfs-prompt --fzf)
optdepends = foot: terminal that hosts pdfs-prompt --fzf when started from a keybinding optdepends = foot: terminal that hosts pdfs-prompt --fzf when started from a keybinding
provides = proton-drive-for-linux=2.2.0.r1.g82e9654 provides = proton-drive-for-linux=2.2.1.r0.g5109427
provides = proton-drive-linux=2.2.0.r1.g82e9654 provides = proton-drive-linux=2.2.1.r0.g5109427
provides = pdfs provides = pdfs
conflicts = proton-drive-linux conflicts = proton-drive-linux
conflicts = proton-drive-for-linux conflicts = proton-drive-for-linux
@@ -1,7 +1,7 @@
# Maintainer: Nils Pukropp <contact@narl.io> # Maintainer: Nils Pukropp <contact@narl.io>
pkgname=proton-drive-for-linux-git pkgname=proton-drive-for-linux-git
pkgver=2.2.0.r1.g82e9654 pkgver=2.2.1.r0.g5109427
pkgrel=1 pkgrel=1
pkgdesc="Unofficial Proton Drive client: FUSE files-on-demand mount, CLI, GTK4 app, tray (git)" pkgdesc="Unofficial Proton Drive client: FUSE files-on-demand mount, CLI, GTK4 app, tray (git)"
arch=('x86_64') arch=('x86_64')
@@ -1,6 +1,6 @@
pkgbase = proton-drive-for-linux pkgbase = proton-drive-for-linux
pkgdesc = Unofficial Proton Drive client: files-on-demand FUSE mount, CLI, GTK4 app and tray pkgdesc = Unofficial Proton Drive client: files-on-demand FUSE mount, CLI, GTK4 app and tray
pkgver = 2.2.0 pkgver = 2.2.1
pkgrel = 1 pkgrel = 1
url = https://github.com/narrrl/proton-drive-linux url = https://github.com/narrrl/proton-drive-linux
arch = x86_64 arch = x86_64
@@ -22,13 +22,13 @@ pkgbase = proton-drive-for-linux
optdepends = xdg-utils: "Open folder" action in the tray menu optdepends = xdg-utils: "Open folder" action in the tray menu
optdepends = fzf: search-as-you-type launcher (pdfs-prompt --fzf) optdepends = fzf: search-as-you-type launcher (pdfs-prompt --fzf)
optdepends = foot: terminal that hosts pdfs-prompt --fzf when started from a keybinding optdepends = foot: terminal that hosts pdfs-prompt --fzf when started from a keybinding
provides = proton-drive-linux=2.2.0 provides = proton-drive-linux=2.2.1
provides = pdfs provides = pdfs
conflicts = proton-drive-linux conflicts = proton-drive-linux
conflicts = proton-drive-for-linux-bin conflicts = proton-drive-for-linux-bin
conflicts = proton-drive-for-linux-git conflicts = proton-drive-for-linux-git
options = !lto options = !lto
source = proton-drive-for-linux-2.2.0.tar.gz::https://github.com/narrrl/proton-drive-linux/archive/refs/tags/v2.2.0.tar.gz source = proton-drive-for-linux-2.2.1.tar.gz::https://github.com/narrrl/proton-drive-linux/archive/refs/tags/v2.2.1.tar.gz
sha256sums = 73f68211d008894c3254b5adca0acca7f9e1af90e89209c22ae0213aa75add4b sha256sums = 4635716908232896bfb0bf16d0f10300dc2eb59c27c7693607a21eabcac2edff
pkgname = proton-drive-for-linux pkgname = proton-drive-for-linux
@@ -1,7 +1,7 @@
# Maintainer: Nils Pukropp <contact@narl.io> # Maintainer: Nils Pukropp <contact@narl.io>
pkgname=proton-drive-for-linux pkgname=proton-drive-for-linux
pkgver=2.2.0 pkgver=2.2.1
pkgrel=1 pkgrel=1
pkgdesc="Unofficial Proton Drive client: files-on-demand FUSE mount, CLI, GTK4 app and tray" pkgdesc="Unofficial Proton Drive client: files-on-demand FUSE mount, CLI, GTK4 app and tray"
arch=('x86_64') arch=('x86_64')
@@ -22,7 +22,7 @@ conflicts=('proton-drive-linux' 'proton-drive-for-linux-bin' 'proton-drive-for-l
# LTO has broken GTK/Rust links for this workspace; the release profile keeps it off. # LTO has broken GTK/Rust links for this workspace; the release profile keeps it off.
options=('!lto') options=('!lto')
source=("$pkgname-$pkgver.tar.gz::$url/archive/refs/tags/v$pkgver.tar.gz") source=("$pkgname-$pkgver.tar.gz::$url/archive/refs/tags/v$pkgver.tar.gz")
sha256sums=('73f68211d008894c3254b5adca0acca7f9e1af90e89209c22ae0213aa75add4b') sha256sums=('4635716908232896bfb0bf16d0f10300dc2eb59c27c7693607a21eabcac2edff')
_srcname="proton-drive-linux-$pkgver" _srcname="proton-drive-linux-$pkgver"