mirror of
https://github.com/narrrl/proton-drive-linux.git
synced 2026-11-03 16:41:26 +01:00
Merge pull request #44 from narrrl/automate-updates
feat: COPR repository and opt-in update check
This commit is contained in:
30 files changed
+2469
-1594
No files matched your search
@@ -0,0 +1,66 @@
|
||||
name: COPR
|
||||
|
||||
on:
|
||||
# Called by release.yml once the GitHub release and its assets exist: COPR
|
||||
# downloads the tag archive and the vendored crates from the release.
|
||||
workflow_call:
|
||||
inputs:
|
||||
version:
|
||||
required: true
|
||||
type: string
|
||||
secrets:
|
||||
COPR_CONFIG:
|
||||
required: true
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
version:
|
||||
description: 'Release to build, such as 3.5.0. It must already have its vendor tarball.'
|
||||
required: true
|
||||
type: string
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
concurrency:
|
||||
group: copr
|
||||
cancel-in-progress: false
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: Build in COPR
|
||||
runs-on: ubuntu-24.04
|
||||
container: fedora:latest
|
||||
env:
|
||||
VERSION: ${{ inputs.version }}
|
||||
COPR_PROJECT: narl-rs/proton-drive-linux
|
||||
|
||||
steps:
|
||||
- name: Install packaging tools
|
||||
run: dnf -y install copr-cli rpm-build rpmdevtools
|
||||
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v7
|
||||
|
||||
- name: Configure COPR access
|
||||
env:
|
||||
COPR_CONFIG: ${{ secrets.COPR_CONFIG }}
|
||||
run: |
|
||||
install -d -m700 ~/.config
|
||||
printf '%s\n' "$COPR_CONFIG" > ~/.config/copr
|
||||
chmod 600 ~/.config/copr
|
||||
|
||||
# COPR rebuilds the source package without our --define, so the version
|
||||
# has to be written into the spec itself.
|
||||
- name: Build the source package
|
||||
run: |
|
||||
version="${VERSION#v}"
|
||||
spec=packaging/proton-drive-linux.spec
|
||||
sed -i -E "s/^(%\{!\?version: %global version )[^}]*\}/\1${version}}/" "$spec"
|
||||
grep -qF "%global version ${version}}" "$spec"
|
||||
mkdir -p rpmbuild/SOURCES
|
||||
spectool -g -C rpmbuild/SOURCES "$spec"
|
||||
rpmbuild -bs --define "_topdir $PWD/rpmbuild" "$spec"
|
||||
|
||||
# Waits for the build, so a failed COPR build fails this job.
|
||||
- name: Submit to COPR
|
||||
run: copr-cli build "$COPR_PROJECT" rpmbuild/SRPMS/*.src.rpm
|
||||
@@ -52,8 +52,10 @@ jobs:
|
||||
tag_version="${GITHUB_REF_NAME#v}"
|
||||
manifest_version="$(sed -n 's/^version = "\([^"]*\)"/\1/p' Cargo.toml | head -n1)"
|
||||
pkgbuild_version="$(sed -n 's/^pkgver=//p' packaging/PKGBUILD | head -n1)"
|
||||
spec_version="$(sed -n 's/^%{!?version: %global version \([^}]*\)}/\1/p' packaging/proton-drive-linux.spec)"
|
||||
test "$tag_version" = "$manifest_version"
|
||||
test "$tag_version" = "$pkgbuild_version"
|
||||
test "$tag_version" = "$spec_version"
|
||||
|
||||
- name: Release quality gates
|
||||
run: |
|
||||
@@ -129,6 +131,12 @@ jobs:
|
||||
# Create tarball
|
||||
tar -czf proton-drive-linux-${{ env.VERSION }}-x86_64.tar.gz -C dist/binaries .
|
||||
|
||||
# Every crate the build needs, so COPR can build the spec without network.
|
||||
- name: Package vendored crates (.tar.xz)
|
||||
run: |
|
||||
cargo vendor --locked dist/vendor > /dev/null
|
||||
tar -cJf proton-drive-linux-vendor-${{ env.VERSION }}.tar.xz -C dist vendor
|
||||
|
||||
- name: Package Debian package (.deb)
|
||||
run: |
|
||||
mkdir -p deb/DEBIAN
|
||||
@@ -230,6 +238,7 @@ jobs:
|
||||
path: |
|
||||
*.deb
|
||||
*.tar.gz
|
||||
*.tar.xz
|
||||
*.rpm
|
||||
if-no-files-found: error
|
||||
|
||||
@@ -297,6 +306,7 @@ jobs:
|
||||
files: |
|
||||
*.deb
|
||||
*.tar.gz
|
||||
*.tar.xz
|
||||
*.rpm
|
||||
draft: false
|
||||
prerelease: false
|
||||
@@ -312,3 +322,13 @@ jobs:
|
||||
version: ${{ github.ref_name }}
|
||||
secrets:
|
||||
AUR_SSH_PRIVATE_KEY: ${{ secrets.AUR_SSH_PRIVATE_KEY }}
|
||||
|
||||
copr:
|
||||
name: Build COPR packages
|
||||
needs: publish
|
||||
if: startsWith(github.ref, 'refs/tags/')
|
||||
uses: ./.github/workflows/copr.yml
|
||||
with:
|
||||
version: ${{ github.ref_name }}
|
||||
secrets:
|
||||
COPR_CONFIG: ${{ secrets.COPR_CONFIG }}
|
||||
@@ -74,14 +74,15 @@ scriptable CLI.
|
||||
| Distribution | Package |
|
||||
|---|---|
|
||||
| Debian, Ubuntu 24.04 or newer | `.deb` from the [latest release](https://github.com/narrrl/proton-drive-linux/releases/latest) |
|
||||
| Fedora 44 or newer | `.rpm` from the [latest release](https://github.com/narrrl/proton-drive-linux/releases/latest) |
|
||||
| Fedora 44 or newer | [`narl-rs/proton-drive-linux`](https://copr.fedorainfracloud.org/coprs/narl-rs/proton-drive-linux/) in COPR, or the `.rpm` from the [latest release](https://github.com/narrrl/proton-drive-linux/releases/latest) |
|
||||
| Arch Linux | [`proton-drive-for-linux`](https://aur.archlinux.org/packages/proton-drive-for-linux) (also `-bin` and `-git`) in the AUR |
|
||||
| NixOS | The flake in this repository: see [Install → NixOS](docs/INSTALL.md#nixos-flake) |
|
||||
| Anything else | [Build from source](docs/INSTALL.md#build-from-source) |
|
||||
|
||||
```bash
|
||||
sudo apt install ./proton-drive-linux_*.deb # Debian, Ubuntu
|
||||
sudo dnf install ./proton-drive-linux-*.rpm # Fedora
|
||||
sudo dnf copr enable narl-rs/proton-drive-linux # Fedora
|
||||
sudo dnf install proton-drive-linux
|
||||
paru -S proton-drive-for-linux # Arch
|
||||
```
|
||||
|
||||
|
||||
@@ -190,6 +190,24 @@ pub struct AppConfig {
|
||||
/// the field.
|
||||
#[serde(default)]
|
||||
pub local_first: Option<bool>,
|
||||
/// Ask GitHub once a day whether a newer release is out, and say so in the
|
||||
/// desktop app. Off by default: GitHub then sees the address and version
|
||||
/// asking. Never used where a package manager installs updates (see
|
||||
/// [`crate::update::updates_managed`]). Defaulted for configs predating the
|
||||
/// field.
|
||||
#[serde(default)]
|
||||
pub update_check: bool,
|
||||
/// When the update check last asked GitHub, in Unix seconds, so a restart
|
||||
/// does not ask again within [`crate::update::CHECK_INTERVAL`]. Defaulted
|
||||
/// for configs predating the field.
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub update_last_checked: Option<i64>,
|
||||
/// The latest release that check found, such as `3.6.0`, so the app can
|
||||
/// say so again after a restart without asking. Older than or equal to the
|
||||
/// running version once the update is installed. Defaulted for configs
|
||||
/// predating the field.
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub update_latest: Option<String>,
|
||||
}
|
||||
|
||||
/// The My Files page's view choices, remembered between runs.
|
||||
@@ -753,6 +771,9 @@ mod tests {
|
||||
language: Some("de".to_string()),
|
||||
online_map: true,
|
||||
local_first: Some(false),
|
||||
update_check: true,
|
||||
update_last_checked: Some(1_760_000_000),
|
||||
update_latest: Some("3.6.0".to_string()),
|
||||
};
|
||||
let json = serde_json::to_string(&config).unwrap();
|
||||
let decoded: AppConfig = serde_json::from_str(&json).unwrap();
|
||||
@@ -764,6 +785,9 @@ mod tests {
|
||||
assert_eq!(decoded.proton_theme, Some(true));
|
||||
assert!(decoded.tray_hidden);
|
||||
assert!(decoded.online_map);
|
||||
assert!(decoded.update_check);
|
||||
assert_eq!(decoded.update_last_checked, Some(1_760_000_000));
|
||||
assert_eq!(decoded.update_latest.as_deref(), Some("3.6.0"));
|
||||
assert_eq!(decoded.language.as_deref(), Some("de"));
|
||||
assert_eq!(decoded.upload_limit, Some(500_000));
|
||||
assert_eq!(decoded.download_limit, None);
|
||||
|
||||
@@ -27,6 +27,7 @@ pub mod syncignore;
|
||||
pub mod syncroot;
|
||||
pub mod takeout;
|
||||
pub mod tray;
|
||||
pub mod update;
|
||||
|
||||
pub use access::{Access, access_for, perm_bits};
|
||||
pub use error::{CoreError, CoreResult, Error, Result};
|
||||
|
||||
@@ -0,0 +1,178 @@
|
||||
//! The opt-in check for a newer release ([`crate::config::AppConfig::update_check`]).
|
||||
//!
|
||||
//! Only the decisions live here: what GitHub's answer says, which version is
|
||||
//! newer, when to ask again, and whether a package manager already owns
|
||||
//! updates. The desktop app does the request itself.
|
||||
|
||||
use std::path::Path;
|
||||
use std::time::Duration;
|
||||
|
||||
/// GitHub's latest published release. Drafts and pre-releases never show up
|
||||
/// here, so a tagged release candidate does not reach anyone.
|
||||
pub const RELEASES_LATEST_API: &str =
|
||||
"https://api.github.com/repos/narrrl/proton-drive-linux/releases/latest";
|
||||
|
||||
/// How often the app asks, across restarts.
|
||||
pub const CHECK_INTERVAL: Duration = Duration::from_secs(24 * 60 * 60);
|
||||
|
||||
/// Installed next to the binaries' `share/` by packages whose package manager
|
||||
/// brings new releases (COPR, the AUR, Nix). The release `.deb`, `.rpm` and
|
||||
/// `.tar.gz` do not ship it, since nothing updates them.
|
||||
pub const MANAGED_MARKER: &str = "share/proton-drive-linux/managed-updates";
|
||||
|
||||
/// The version of the release in a `/releases/latest` answer, without the
|
||||
/// tag's `v`. `None` for an answer that does not parse or a tag that is not
|
||||
/// `X.Y.Z`, so a changed API can never claim an update.
|
||||
pub fn latest_version(body: &[u8]) -> Option<String> {
|
||||
let json: serde_json::Value = serde_json::from_slice(body).ok()?;
|
||||
let tag = json.get("tag_name")?.as_str()?;
|
||||
let version = tag.strip_prefix('v').unwrap_or(tag);
|
||||
parse(version).map(|_| version.to_owned())
|
||||
}
|
||||
|
||||
/// Whether `version` is a later release than `current`. Anything that is not
|
||||
/// `X.Y.Z` on either side is not.
|
||||
pub fn is_newer(version: &str, current: &str) -> bool {
|
||||
matches!((parse(version), parse(current)), (Some(v), Some(c)) if v > c)
|
||||
}
|
||||
|
||||
/// The release page of `version` on GitHub.
|
||||
pub fn release_url(version: &str) -> String {
|
||||
format!("https://github.com/narrrl/proton-drive-linux/releases/tag/v{version}")
|
||||
}
|
||||
|
||||
/// `X.Y.Z` as numbers, so `3.10.0` sorts after `3.9.0`.
|
||||
fn parse(version: &str) -> Option<(u64, u64, u64)> {
|
||||
let mut parts = version.split('.').map(|p| p.parse::<u64>().ok());
|
||||
let triple = (parts.next()??, parts.next()??, parts.next()??);
|
||||
parts.next().is_none().then_some(triple)
|
||||
}
|
||||
|
||||
/// How long until the next check is due, given the last one in Unix seconds.
|
||||
/// Zero when it is due now. A clock set back past the last check counts as
|
||||
/// due, so a wrong clock cannot stop checks.
|
||||
pub fn next_check_in(last_checked: Option<i64>, now: i64) -> Duration {
|
||||
let interval = CHECK_INTERVAL.as_secs() as i64;
|
||||
match last_checked {
|
||||
Some(last) if (0..interval).contains(&(now - last)) => {
|
||||
Duration::from_secs((interval - (now - last)) as u64)
|
||||
}
|
||||
_ => Duration::ZERO,
|
||||
}
|
||||
}
|
||||
|
||||
/// Whether the binary at `exe` was installed by a package manager that updates
|
||||
/// it, which [`MANAGED_MARKER`] next to its `bin/` says.
|
||||
pub fn updates_managed(exe: &Path) -> bool {
|
||||
exe.parent()
|
||||
.and_then(Path::parent)
|
||||
.is_some_and(|prefix| prefix.join(MANAGED_MARKER).exists())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
fn latest(tag: &str) -> Vec<u8> {
|
||||
format!(
|
||||
r#"{{"tag_name":"{tag}","html_url":"https://github.com/narrrl/proton-drive-linux/releases/tag/{tag}","draft":false}}"#
|
||||
)
|
||||
.into_bytes()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn the_latest_release_is_read_without_its_v() {
|
||||
assert_eq!(latest_version(&latest("v3.6.0")).as_deref(), Some("3.6.0"));
|
||||
assert_eq!(latest_version(&latest("3.6.0")).as_deref(), Some("3.6.0"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn an_answer_that_does_not_parse_has_no_version() {
|
||||
assert_eq!(latest_version(b"not json"), None);
|
||||
assert_eq!(latest_version(b"{}"), None);
|
||||
assert_eq!(latest_version(br#"{"tag_name":7}"#), None);
|
||||
assert_eq!(latest_version(&latest("nightly")), None);
|
||||
assert_eq!(latest_version(&latest("v4.0.0-rc1")), None);
|
||||
assert_eq!(latest_version(&latest("v4.0")), None);
|
||||
assert_eq!(latest_version(&latest("v4.0.0.1")), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn only_a_later_release_is_newer() {
|
||||
assert!(is_newer("3.6.0", "3.5.0"));
|
||||
assert!(!is_newer("3.5.0", "3.5.0"));
|
||||
assert!(!is_newer("3.4.1", "3.5.0"));
|
||||
assert!(!is_newer("2.99.99", "3.0.0"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn versions_compare_as_numbers() {
|
||||
assert!(is_newer("3.10.0", "3.9.0"));
|
||||
assert!(is_newer("3.5.10", "3.5.9"));
|
||||
assert!(is_newer("4.0.0", "3.99.99"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_version_that_is_not_x_y_z_is_never_newer() {
|
||||
assert!(!is_newer("4.0.0", "3.5.0-dev"));
|
||||
assert!(!is_newer("garbage", "3.5.0"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn the_release_page_uses_the_tag() {
|
||||
assert_eq!(
|
||||
release_url("3.6.0"),
|
||||
"https://github.com/narrrl/proton-drive-linux/releases/tag/v3.6.0"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_check_is_due_once_a_day() {
|
||||
let day = CHECK_INTERVAL.as_secs() as i64;
|
||||
assert_eq!(next_check_in(None, 1_000), Duration::ZERO);
|
||||
assert_eq!(next_check_in(Some(1_000), 1_000), CHECK_INTERVAL);
|
||||
assert_eq!(
|
||||
next_check_in(Some(1_000), 1_000 + day - 1),
|
||||
Duration::from_secs(1)
|
||||
);
|
||||
assert_eq!(next_check_in(Some(1_000), 1_000 + day), Duration::ZERO);
|
||||
assert_eq!(
|
||||
next_check_in(Some(1_000), 999),
|
||||
Duration::ZERO,
|
||||
"a clock set back must not stop checks"
|
||||
);
|
||||
}
|
||||
|
||||
/// A unique temp directory removed on drop; avoids a dev-dependency, as in
|
||||
/// [`crate::syncignore`]'s tests.
|
||||
struct TempDir(std::path::PathBuf);
|
||||
impl TempDir {
|
||||
fn new() -> Self {
|
||||
use std::sync::atomic::{AtomicU64, Ordering};
|
||||
static N: AtomicU64 = AtomicU64::new(0);
|
||||
let p = std::env::temp_dir().join(format!(
|
||||
"pdfs-update-{}-{}",
|
||||
std::process::id(),
|
||||
N.fetch_add(1, Ordering::Relaxed)
|
||||
));
|
||||
std::fs::create_dir_all(&p).unwrap();
|
||||
TempDir(p)
|
||||
}
|
||||
}
|
||||
impl Drop for TempDir {
|
||||
fn drop(&mut self) {
|
||||
let _ = std::fs::remove_dir_all(&self.0);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn the_marker_next_to_bin_means_a_package_manager_updates() {
|
||||
let prefix = TempDir::new();
|
||||
let exe = prefix.0.join("bin/pdfs-app");
|
||||
assert!(!updates_managed(&exe));
|
||||
let marker = prefix.0.join(MANAGED_MARKER);
|
||||
std::fs::create_dir_all(marker.parent().unwrap()).unwrap();
|
||||
std::fs::write(&marker, "").unwrap();
|
||||
assert!(updates_managed(&exe));
|
||||
}
|
||||
}
|
||||
@@ -10,6 +10,7 @@ pub(crate) mod pages;
|
||||
pub(crate) mod reload;
|
||||
#[path = "../theme.rs"]
|
||||
pub(crate) mod theme;
|
||||
pub(crate) mod update;
|
||||
pub(crate) mod widgets;
|
||||
|
||||
use compat::*;
|
||||
@@ -33,6 +34,7 @@ use pages::trash::*;
|
||||
use pages::verify::*;
|
||||
use reload::{LoadTicket, Loader, replace_items};
|
||||
use theme::{load_resources, proton_theme_active, set_proton_theme};
|
||||
use update::*;
|
||||
use widgets::compat::*;
|
||||
use widgets::crumb::*;
|
||||
use widgets::details::*;
|
||||
@@ -155,6 +157,7 @@ struct Ui {
|
||||
pub(crate) locations: LocationsState,
|
||||
pub(crate) activity: ActivityState,
|
||||
pub(crate) takeout: TakeoutState,
|
||||
pub(crate) update: UpdateState,
|
||||
}
|
||||
|
||||
impl Ui {
|
||||
@@ -365,7 +368,8 @@ fn build_window(app: &adw::Application) {
|
||||
// Sidebar: the signed-in destinations. Selecting a row swaps the page stack;
|
||||
// `sync_sidebar` pushes the other way when navigation happens elsewhere (e.g.
|
||||
// login lands on Files).
|
||||
let (sidebar_page, sidebar_list) = build_sidebar(&main_widgets.footer);
|
||||
let update = UpdateState::new();
|
||||
let (sidebar_page, sidebar_list) = build_sidebar(&main_widgets.footer, &update.banner);
|
||||
|
||||
// Every page brings its own header bar (see `page_frame`), so the content
|
||||
// side is the bare stack.
|
||||
@@ -432,6 +436,7 @@ fn build_window(app: &adw::Application) {
|
||||
theme_row: main_widgets.theme_row.clone(),
|
||||
online_map_row: main_widgets.online_map_row.clone(),
|
||||
tray_row: main_widgets.tray_row.clone(),
|
||||
update_row: main_widgets.update_row.clone(),
|
||||
language_row: main_widgets.language_row.clone(),
|
||||
settings_suppress: Cell::new(false),
|
||||
budget_source: RefCell::new(None),
|
||||
@@ -654,6 +659,7 @@ fn build_window(app: &adw::Application) {
|
||||
running: Cell::new(false),
|
||||
dry_run: Cell::new(false),
|
||||
},
|
||||
update,
|
||||
});
|
||||
wire_login(&ui);
|
||||
if let Some(e) = keyring_error {
|
||||
@@ -700,6 +706,7 @@ fn build_window(app: &adw::Application) {
|
||||
wire_activity(&ui, &activity_widgets);
|
||||
wire_takeout(&ui, &takeout_widgets);
|
||||
wire_retry(&ui);
|
||||
wire_update(&ui);
|
||||
|
||||
// Lazily load the Files / Photos pages the first time they're shown, so the
|
||||
// network round-trip only happens on demand rather than on every refresh.
|
||||
@@ -871,10 +878,13 @@ const DESTINATIONS: [(&str, &str, &str); 7] = [
|
||||
/// Index of the first row in the sidebar's sync section.
|
||||
const SIDEBAR_SYNC_SECTION: i32 = 6;
|
||||
|
||||
/// The navigation sidebar: the destinations over a footer carrying the sync
|
||||
/// status, the account quota and the account menu. Returns the page (for the
|
||||
/// The navigation sidebar: the destinations under the update banner and over a
|
||||
/// footer carrying the sync status, the account quota and the account menu. Returns the page (for the
|
||||
/// split view) and the list (to drive + reflect the current page).
|
||||
fn build_sidebar(footer: >k4::Box) -> (adw::NavigationPage, gtk4::ListBox) {
|
||||
fn build_sidebar(
|
||||
footer: >k4::Box,
|
||||
update_banner: &adw::Banner,
|
||||
) -> (adw::NavigationPage, gtk4::ListBox) {
|
||||
let list = gtk4::ListBox::new();
|
||||
list.set_selection_mode(gtk4::SelectionMode::Single);
|
||||
list.add_css_class("navigation-sidebar");
|
||||
@@ -927,6 +937,7 @@ fn build_sidebar(footer: >k4::Box) -> (adw::NavigationPage, gtk4::ListBox) {
|
||||
.build();
|
||||
let toolbar = adw::ToolbarView::new();
|
||||
toolbar.add_top_bar(&header);
|
||||
toolbar.add_top_bar(update_banner);
|
||||
toolbar.set_content(Some(&scroll));
|
||||
toolbar.add_bottom_bar(footer);
|
||||
|
||||
|
||||
@@ -52,6 +52,9 @@ pub(crate) struct StatusState {
|
||||
pub(crate) online_map_row: adw::SwitchRow,
|
||||
/// "Show tray icon" toggle. Guarded by [`Self::settings_suppress`].
|
||||
pub(crate) tray_row: adw::SwitchRow,
|
||||
/// "Check for updates" toggle, not shown where a package manager brings
|
||||
/// updates. Guarded by [`Self::settings_suppress`].
|
||||
pub(crate) update_row: adw::SwitchRow,
|
||||
/// Language picker: "System Default", then [`i18n::LANGUAGES`]. Guarded by
|
||||
/// [`Self::settings_suppress`].
|
||||
pub(crate) language_row: adw::ComboRow,
|
||||
@@ -164,6 +167,7 @@ pub(crate) struct MainWidgets {
|
||||
pub(crate) theme_row: adw::SwitchRow,
|
||||
pub(crate) online_map_row: adw::SwitchRow,
|
||||
pub(crate) tray_row: adw::SwitchRow,
|
||||
pub(crate) update_row: adw::SwitchRow,
|
||||
pub(crate) language_row: adw::ComboRow,
|
||||
}
|
||||
|
||||
@@ -250,6 +254,17 @@ pub(crate) fn build_main_page() -> MainWidgets {
|
||||
let language_row = build_language_row();
|
||||
appearance_group.add(&language_row);
|
||||
|
||||
let updates_group = adw::PreferencesGroup::builder()
|
||||
.title(gettext("Updates"))
|
||||
.build();
|
||||
let update_row = adw::SwitchRow::builder()
|
||||
.title(gettext("Check for updates"))
|
||||
.subtitle(gettext(
|
||||
"Ask GitHub once a day whether a new version is out. GitHub learns your IP address and which version you use.",
|
||||
))
|
||||
.build();
|
||||
updates_group.add(&update_row);
|
||||
|
||||
let general = adw::PreferencesPage::builder()
|
||||
.title(gettext("General"))
|
||||
.icon_name("preferences-system-symbolic")
|
||||
@@ -258,6 +273,10 @@ pub(crate) fn build_main_page() -> MainWidgets {
|
||||
general.add(&location_group);
|
||||
general.add(&network_group);
|
||||
general.add(&appearance_group);
|
||||
// A package manager brings new versions, so there is nothing to tell.
|
||||
if !updates_managed_here() {
|
||||
general.add(&updates_group);
|
||||
}
|
||||
|
||||
// ---- Preferences: Storage
|
||||
let storage_group = adw::PreferencesGroup::builder()
|
||||
@@ -442,6 +461,7 @@ pub(crate) fn build_main_page() -> MainWidgets {
|
||||
theme_row,
|
||||
online_map_row,
|
||||
tray_row,
|
||||
update_row,
|
||||
language_row,
|
||||
}
|
||||
}
|
||||
@@ -541,6 +561,7 @@ pub(crate) fn wire_settings(
|
||||
.set_active(config.proton_theme.unwrap_or(false));
|
||||
ui.status.online_map_row.set_active(config.online_map);
|
||||
ui.status.tray_row.set_active(!config.tray_hidden);
|
||||
ui.status.update_row.set_active(config.update_check);
|
||||
ui.status
|
||||
.language_row
|
||||
.set_selected(language_index(config.language.as_deref()));
|
||||
@@ -690,6 +711,26 @@ pub(crate) fn wire_settings(
|
||||
}
|
||||
});
|
||||
|
||||
// Update check: saved, and the first check runs right away if one is due.
|
||||
let ui_update = ui.clone();
|
||||
ui.status.update_row.connect_active_notify(move |row| {
|
||||
if ui_update.status.settings_suppress.get() {
|
||||
return;
|
||||
}
|
||||
let mut config = ui_update.dirs.load_config();
|
||||
config.update_check = row.is_active();
|
||||
if let Err(e) = ui_update.dirs.save_config(&config) {
|
||||
toast_error(
|
||||
&ui_update,
|
||||
&gettext("Couldn't save the update setting"),
|
||||
&e.to_string(),
|
||||
);
|
||||
return;
|
||||
}
|
||||
show_latest(&ui_update);
|
||||
start_update_checks(&ui_update);
|
||||
});
|
||||
|
||||
// Tray icon: the tray reads the same flag at start, so hiding it here also
|
||||
// keeps the login autostart from bringing it back.
|
||||
let ui_tray = ui.clone();
|
||||
|
||||
@@ -0,0 +1,168 @@
|
||||
//! Tells the user when a newer release is out, if they turned on Preferences →
|
||||
//! Updates. The decisions are [`pdfs_core::update`]'s; this asks GitHub and
|
||||
//! shows the banner.
|
||||
|
||||
use crate::*;
|
||||
|
||||
use std::time::{SystemTime, UNIX_EPOCH};
|
||||
|
||||
use pdfs_core::update::{
|
||||
RELEASES_LATEST_API, is_newer, latest_version, next_check_in, release_url, updates_managed,
|
||||
};
|
||||
|
||||
/// How long one request to GitHub may take.
|
||||
const FETCH_TIMEOUT: Duration = Duration::from_secs(10);
|
||||
|
||||
/// How long to wait after a failed check, such as one made offline, before
|
||||
/// trying again. Not saved, so a restart tries again straight away.
|
||||
const RETRY_AFTER: Duration = Duration::from_secs(60 * 60);
|
||||
|
||||
pub(crate) struct UpdateState {
|
||||
/// Over the sidebar, so it shows whichever page is open.
|
||||
pub(crate) banner: adw::Banner,
|
||||
/// The check loop is running. Turning the setting off and on again while it
|
||||
/// sleeps must not start a second one.
|
||||
running: Cell<bool>,
|
||||
/// The release page the banner's button opens.
|
||||
url: RefCell<String>,
|
||||
}
|
||||
|
||||
impl UpdateState {
|
||||
pub(crate) fn new() -> Self {
|
||||
Self {
|
||||
banner: adw::Banner::builder()
|
||||
.button_label(gettext("Download"))
|
||||
.build(),
|
||||
running: Cell::new(false),
|
||||
url: RefCell::new(String::new()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Whether a package manager brings new releases to this install. The app then
|
||||
/// never asks, and Preferences does not offer to.
|
||||
pub(crate) fn updates_managed_here() -> bool {
|
||||
std::env::current_exe().is_ok_and(|exe| updates_managed(&exe))
|
||||
}
|
||||
|
||||
/// Wire the banner, show a release an earlier check found, and start checking
|
||||
/// if the user asked for it.
|
||||
pub(crate) fn wire_update(ui: &Rc<Ui>) {
|
||||
let ui_open = ui.clone();
|
||||
ui.update
|
||||
.banner
|
||||
.connect_button_clicked(move |_| open_uri(&ui_open.update.url.borrow()));
|
||||
show_latest(ui);
|
||||
start_update_checks(ui);
|
||||
}
|
||||
|
||||
/// Start the daily check, unless it is off, managed elsewhere, or running.
|
||||
pub(crate) fn start_update_checks(ui: &Rc<Ui>) {
|
||||
if updates_managed_here()
|
||||
|| !ui.dirs.load_config().update_check
|
||||
|| ui.update.running.replace(true)
|
||||
{
|
||||
return;
|
||||
}
|
||||
let ui = ui.clone();
|
||||
glib::spawn_future_local(async move {
|
||||
// Also kept here, so a config that cannot be saved does not make every
|
||||
// pass ask GitHub again.
|
||||
let mut checked = None;
|
||||
loop {
|
||||
let config = ui.dirs.load_config();
|
||||
if !config.update_check {
|
||||
break;
|
||||
}
|
||||
let wait = next_check_in(config.update_last_checked.max(checked), unix_now());
|
||||
if !wait.is_zero() {
|
||||
glib::timeout_future(wait).await;
|
||||
continue;
|
||||
}
|
||||
match fetch_latest().await {
|
||||
Ok(latest) => {
|
||||
checked = Some(unix_now());
|
||||
let mut config = ui.dirs.load_config();
|
||||
config.update_last_checked = checked;
|
||||
config.update_latest = latest;
|
||||
if let Err(e) = ui.dirs.save_config(&config) {
|
||||
tracing::warn!("update check: cannot save the result: {e}");
|
||||
}
|
||||
show_latest(&ui);
|
||||
}
|
||||
Err(e) => {
|
||||
tracing::warn!("update check: {e}");
|
||||
glib::timeout_future(RETRY_AFTER).await;
|
||||
}
|
||||
}
|
||||
}
|
||||
ui.update.running.set(false);
|
||||
});
|
||||
}
|
||||
|
||||
/// Reveal the banner if the last check found a release newer than this one,
|
||||
/// and hide it otherwise.
|
||||
pub(crate) fn show_latest(ui: &Ui) {
|
||||
let config = ui.dirs.load_config();
|
||||
let newer = config
|
||||
.update_latest
|
||||
.filter(|latest| config.update_check && is_newer(latest, env!("CARGO_PKG_VERSION")));
|
||||
match newer {
|
||||
Some(version) => {
|
||||
ui.update.banner.set_title(&gettext_f(
|
||||
"Proton Drive {version} is available",
|
||||
&[("version", &version)],
|
||||
));
|
||||
*ui.update.url.borrow_mut() = release_url(&version);
|
||||
ui.update.banner.set_revealed(!updates_managed_here());
|
||||
}
|
||||
None => ui.update.banner.set_revealed(false),
|
||||
}
|
||||
}
|
||||
|
||||
fn unix_now() -> i64 {
|
||||
SystemTime::now()
|
||||
.duration_since(UNIX_EPOCH)
|
||||
.map_or(0, |d| d.as_secs() as i64)
|
||||
}
|
||||
|
||||
/// The latest release's version, asked on a worker thread so the window never
|
||||
/// waits on GitHub. `Ok(None)` when the answer names no `X.Y.Z` release.
|
||||
async fn fetch_latest() -> Result<Option<String>, String> {
|
||||
let (tx, rx) = async_channel::bounded(1);
|
||||
std::thread::spawn(move || {
|
||||
let _ = tx.send_blocking(fetch_latest_blocking());
|
||||
});
|
||||
rx.recv().await.map_err(|e| e.to_string())?
|
||||
}
|
||||
|
||||
fn fetch_latest_blocking() -> Result<Option<String>, String> {
|
||||
let runtime = tokio::runtime::Builder::new_current_thread()
|
||||
.enable_all()
|
||||
.build()
|
||||
.map_err(|e| format!("no runtime: {e}"))?;
|
||||
let client = reqwest::Client::builder()
|
||||
.user_agent(concat!(
|
||||
"proton-drive-linux/",
|
||||
env!("CARGO_PKG_VERSION"),
|
||||
" (",
|
||||
env!("CARGO_PKG_REPOSITORY"),
|
||||
")"
|
||||
))
|
||||
.timeout(FETCH_TIMEOUT)
|
||||
.build()
|
||||
.map_err(|e| format!("no HTTP client: {e}"))?;
|
||||
runtime.block_on(async {
|
||||
let reply = client
|
||||
.get(RELEASES_LATEST_API)
|
||||
.header("Accept", "application/vnd.github+json")
|
||||
.send()
|
||||
.await
|
||||
.map_err(|e| e.to_string())?;
|
||||
if !reply.status().is_success() {
|
||||
return Err(format!("GitHub answered {}", reply.status()));
|
||||
}
|
||||
let body = reply.bytes().await.map_err(|e| e.to_string())?;
|
||||
Ok(latest_version(&body))
|
||||
})
|
||||
}
|
||||
@@ -11,6 +11,15 @@ scratch (user data in `staging/` and `recovery/` is never touched by this).
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
### Added
|
||||
- **A COPR repository for Fedora**, so `dnf upgrade` brings each new release:
|
||||
`sudo dnf copr enable narl-rs/proton-drive-linux`. Every release also ships the vendored crates
|
||||
as `proton-drive-linux-vendor-<version>.tar.xz`, which the COPR build uses offline (#42).
|
||||
- **The app can tell you about a new release.** Turn on **Check for updates** in Preferences, and
|
||||
the app asks GitHub once a day and shows a banner with a **Download** button when a newer
|
||||
version is out. It is off by default, and not offered for packages that a package manager
|
||||
updates (COPR, the AUR, Nix) (#42).
|
||||
|
||||
## [3.5.0] — 2026-10-16
|
||||
|
||||
Schema: **44**. Queued changes get an index by kind and name, and each synced file remembers the
|
||||
|
||||
@@ -61,6 +61,7 @@ fix it.
|
||||
"proton_theme": true,
|
||||
"online_map": false,
|
||||
"tray_hidden": false,
|
||||
"update_check": false,
|
||||
"files_view": { "list": true, "sort": "modified", "descending": true, "folders_first": true },
|
||||
"window": { "width": 980, "height": 680, "maximized": false },
|
||||
"open_with": {
|
||||
@@ -87,6 +88,9 @@ fix it.
|
||||
| `proton_theme` | bool | follow system | General → Appearance | Proton's colours instead of the system theme |
|
||||
| `online_map` | bool | `false` | General → Appearance | Street map for Places from OpenFreeMap |
|
||||
| `tray_hidden` | bool | `false` | General → Appearance | Hide the tray icon |
|
||||
| `update_check` | bool | `false` | General → Updates | Ask GitHub once a day for a newer release and show it over the sidebar. Not offered where a package manager installs updates |
|
||||
| `update_last_checked` | Unix seconds | none | — | Written by the update check, so a restart does not ask again within a day |
|
||||
| `update_latest` | version | none | — | The latest release the update check found |
|
||||
| `files_view` | object | grid, by name | My files view menu | `list`, `sort` (`name`, `size`, `modified`), `descending`, `folders_first` |
|
||||
| `window` | object | 980 × 680 | Resizing the app window | `width`, `height` (the unmaximized size), `maximized` |
|
||||
| `open_with` | object | `xdg-open` | — | See [`open_with`](#open_with) |
|
||||
|
||||
+11
-8
@@ -150,22 +150,25 @@ to the GitHub wiki, so keep their file names.
|
||||
|
||||
Releases are cut from `main` by pushing a `v*` tag. `.github/workflows/release.yml` then:
|
||||
|
||||
1. checks that the tag, the workspace version in `Cargo.toml` and `pkgver` in
|
||||
`packaging/PKGBUILD` agree;
|
||||
1. checks that the tag, the workspace version in `Cargo.toml`, `pkgver` in
|
||||
`packaging/PKGBUILD` and the default version in `packaging/proton-drive-linux.spec` agree;
|
||||
2. runs the quality gates;
|
||||
3. builds the release binaries and packages a `.tar.gz`, a `.deb` and a GPG-signed `.rpm`, and
|
||||
builds the Nix flake at the tag, which is what a flake input pinned to `vX.Y.Z` gets, and
|
||||
pushes it to the `proton-drive-linux` Cachix cache (secret `CACHIX_AUTH_TOKEN`);
|
||||
3. builds the release binaries and packages a `.tar.gz`, a `.deb`, a GPG-signed `.rpm` and a
|
||||
`.tar.xz` of the vendored crates, and builds the Nix flake at the tag, which is what a flake
|
||||
input pinned to `vX.Y.Z` gets, and pushes it to the `proton-drive-linux` Cachix cache (secret
|
||||
`CACHIX_AUTH_TOKEN`);
|
||||
4. publishes them as a GitHub release, with the version's section of `docs/CHANGELOG.md` as the
|
||||
notes;
|
||||
5. calls `.github/workflows/aur.yml`, which updates and pushes the AUR recipes and commits them
|
||||
back to `main`.
|
||||
back to `main`;
|
||||
6. calls `.github/workflows/copr.yml`, which builds the release in the
|
||||
[COPR repository](../packaging/copr/README.md) (secret `COPR_CONFIG`).
|
||||
|
||||
To prepare a release:
|
||||
|
||||
```bash
|
||||
# 1. Bump the version in Cargo.toml, packaging/PKGBUILD, po/pdfs.pot and the pinned flake
|
||||
# URL in docs/INSTALL.md, then refresh the lock file.
|
||||
# 1. Bump the version in Cargo.toml, packaging/PKGBUILD, packaging/proton-drive-linux.spec,
|
||||
# po/pdfs.pot and the pinned flake URL in docs/INSTALL.md, then refresh the lock file.
|
||||
cargo update --workspace
|
||||
# 2. Move the [Unreleased] notes in docs/CHANGELOG.md under the new version.
|
||||
# 3. Commit, tag and push.
|
||||
|
||||
@@ -59,6 +59,17 @@ sudo apt install ./proton-drive-linux_*_amd64.deb
|
||||
|
||||
### Fedora (44 or newer)
|
||||
|
||||
The [COPR repository](https://copr.fedorainfracloud.org/coprs/narl-rs/proton-drive-linux/) builds
|
||||
every release, so `dnf upgrade` keeps the client up to date:
|
||||
|
||||
```bash
|
||||
sudo dnf copr enable narl-rs/proton-drive-linux
|
||||
sudo dnf install proton-drive-linux
|
||||
```
|
||||
|
||||
The `.rpm` from the release page installs the same files, but you have to download each new
|
||||
version yourself:
|
||||
|
||||
```bash
|
||||
sudo dnf install ./proton-drive-linux-*.x86_64.rpm
|
||||
```
|
||||
@@ -277,6 +288,13 @@ starts; to switch at once:
|
||||
systemctl --user restart proton-drive.service
|
||||
```
|
||||
|
||||
With the `.deb`, the release `.rpm` or the `.tar.gz`, nothing tells you about a new release.
|
||||
Turn on **Preferences → General → Check for updates** and the app asks GitHub once a day and
|
||||
shows a banner over the sidebar when a newer version is out. Packages that a package manager
|
||||
updates do not offer the setting. A package shows this by installing the empty file
|
||||
`share/proton-drive-linux/managed-updates` under the prefix of its `bin/` directory, for example
|
||||
`/usr/share/proton-drive-linux/managed-updates`.
|
||||
|
||||
Coming from 2.8.0 or older, re-enable the service once. Those releases hooked it into
|
||||
`default.target`, which starts it before the desktop session is ready; on KDE that crashes
|
||||
`ksecretd` at every login. Signing in again or toggling "Start on login" does this as well:
|
||||
|
||||
@@ -60,8 +60,12 @@ The client connects only to:
|
||||
- **Proton**, for the Drive and account API, and for the CAPTCHA page when Proton asks for one.
|
||||
- **OpenFreeMap**, for the street map of Places in Photos, only if you turn it on in Preferences. It is off by default, because the tile server learns which parts of the
|
||||
world you look at. The default map is drawn from data shipped with the app.
|
||||
- **GitHub**, once a day, to ask for the latest release, only if you turn on **Check for updates**
|
||||
in Preferences. It is off by default, because GitHub learns your IP address and, from the user
|
||||
agent, which version you run. Packages updated by a package manager (COPR, the AUR, Nix) do
|
||||
not offer the setting and never ask.
|
||||
|
||||
There is no telemetry, no analytics and no update check. The SDK can report timings of its own
|
||||
There is no telemetry and no analytics. The SDK can report timings of its own
|
||||
operations, and the client only writes them to its local log.
|
||||
|
||||
## What this client does not protect against
|
||||
@@ -75,7 +79,9 @@ operations, and the client only writes them to its local log.
|
||||
|
||||
## Packages
|
||||
|
||||
- The `.rpm` is signed with GPG.
|
||||
- The `.rpm` on the release page is signed with GPG.
|
||||
- The COPR packages are built by Fedora's COPR service from the tagged source and the vendored
|
||||
crates of the release, and are signed with the COPR project's own key.
|
||||
- The `.deb` and the `.tar.gz` are built by GitHub Actions from the tagged source, and are not
|
||||
signed.
|
||||
- The AUR packages build from the tagged source or from `main`, or install the release binaries
|
||||
|
||||
@@ -65,4 +65,7 @@ package() {
|
||||
cp -r --no-preserve=ownership "$srcdir/locale" "$pkgdir/usr/share/locale"
|
||||
|
||||
install -Dm644 "$srcdir/LICENSE-$pkgver" "$pkgdir/usr/share/licenses/$pkgname/LICENSE"
|
||||
|
||||
# pacman brings new releases, so the app never checks for them itself.
|
||||
install -Dm644 /dev/null "$pkgdir/usr/share/proton-drive-linux/managed-updates"
|
||||
}
|
||||
@@ -83,4 +83,7 @@ package() {
|
||||
|
||||
install -Dm644 LICENSE "$pkgdir/usr/share/licenses/$pkgname/LICENSE"
|
||||
install -Dm644 README.md "$pkgdir/usr/share/doc/$pkgname/README.md"
|
||||
|
||||
# pacman brings new releases, so the app never checks for them itself.
|
||||
install -Dm644 /dev/null "$pkgdir/usr/share/proton-drive-linux/managed-updates"
|
||||
}
|
||||
@@ -78,4 +78,7 @@ package() {
|
||||
|
||||
install -Dm644 LICENSE "$pkgdir/usr/share/licenses/$pkgname/LICENSE"
|
||||
install -Dm644 README.md "$pkgdir/usr/share/doc/$pkgname/README.md"
|
||||
|
||||
# pacman brings new releases, so the app never checks for them itself.
|
||||
install -Dm644 /dev/null "$pkgdir/usr/share/proton-drive-linux/managed-updates"
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
# COPR repository
|
||||
|
||||
Fedora users install from the COPR project
|
||||
[`narl-rs/proton-drive-linux`](https://copr.fedorainfracloud.org/coprs/narl-rs/proton-drive-linux/),
|
||||
so `dnf upgrade` brings every release. COPR builds `packaging/proton-drive-linux.spec` from two
|
||||
release assets:
|
||||
|
||||
| Source | What it is |
|
||||
| --- | --- |
|
||||
| `Source0` | GitHub's archive of the `v<version>` tag |
|
||||
| `Source1` | `proton-drive-linux-vendor-<version>.tar.xz`: every crate from `Cargo.lock`, made by `cargo vendor` in `release.yml` |
|
||||
|
||||
With the vendored crates the build needs no network, so the COPR project keeps network access
|
||||
off.
|
||||
|
||||
## Releasing a new version
|
||||
|
||||
`.github/workflows/copr.yml` does this automatically:
|
||||
|
||||
- **On a release tag**, `release.yml` calls it after the GitHub release is published. It writes
|
||||
the version into the spec, downloads both sources with `spectool`, builds a source package
|
||||
and submits it with `copr-cli build`. The job waits for COPR, so a failed build fails the
|
||||
release workflow.
|
||||
- **By hand**, run the workflow from the Actions tab with a version. The release must already
|
||||
have its vendor tarball.
|
||||
|
||||
It needs the `COPR_CONFIG` repository secret: the whole content of `~/.config/copr` from
|
||||
<https://copr.fedorainfracloud.org/api/>. **The token expires after 180 days.** Renew it on that
|
||||
page and replace the secret, or the next release's COPR job fails.
|
||||
|
||||
To do the same by hand:
|
||||
|
||||
```bash
|
||||
spectool -g -C ~/rpmbuild/SOURCES packaging/proton-drive-linux.spec
|
||||
rpmbuild -bs packaging/proton-drive-linux.spec
|
||||
copr-cli build narl-rs/proton-drive-linux ~/rpmbuild/SRPMS/proton-drive-linux-*.src.rpm
|
||||
```
|
||||
|
||||
## Project settings
|
||||
|
||||
- Chroots: `fedora-44-x86_64`, `fedora-45-x86_64` and `fedora-rawhide-x86_64`. The spec is
|
||||
`ExclusiveArch: x86_64` and needs `rust >= 1.96`.
|
||||
- Network access in builds: off.
|
||||
@@ -106,6 +106,9 @@ rustPlatform.buildRustPackage {
|
||||
|
||||
patchShebangs po/build.sh
|
||||
po/build.sh $out/share/locale
|
||||
|
||||
# Nix brings new releases, so the app never checks for them itself.
|
||||
install -Dm644 /dev/null $out/share/proton-drive-linux/managed-updates
|
||||
'';
|
||||
|
||||
# Thumbnails shell out to exiftool, and to ffmpeg for video. Suffixed, so a
|
||||
|
||||
@@ -1,13 +1,19 @@
|
||||
# Local in-tree RPM (mirrors packaging/PKGBUILD).
|
||||
# From the repository root:
|
||||
# Two ways to build this spec:
|
||||
#
|
||||
# - From a checkout (local builds and the release workflow). From the repository root:
|
||||
# rpmbuild -bb packaging/proton-drive-linux.spec --define "git_dir $PWD" ...
|
||||
# %build needs network so cargo can fetch crates.io (same trade-off as the Arch PKGBUILD).
|
||||
# - From the release tarballs (COPR, .github/workflows/copr.yml), when git_dir is not
|
||||
# defined. Source1 holds every crate, so the build runs offline.
|
||||
|
||||
%global debug_package %{nil}
|
||||
# Match Arch PKGBUILD `!lto` — LTO has broken some GTK/Rust links in practice.
|
||||
%global _lto_cflags %{nil}
|
||||
|
||||
%{!?version: %global version 0.4.0}
|
||||
%{!?version: %global version 3.5.0}
|
||||
%global forge https://github.com/narrrl/proton-drive-linux
|
||||
# Files come from the checkout, or from the unpacked tag archive.
|
||||
%global src %{?git_dir}%{!?git_dir:.}
|
||||
|
||||
Name: proton-drive-linux
|
||||
Version: %{version}
|
||||
@@ -16,9 +22,13 @@ Summary: Unofficial Proton Drive client for Linux (FUSE, CLI, GTK4 app +
|
||||
License: MIT
|
||||
URL: https://proton-drive.narl.io
|
||||
ExclusiveArch: x86_64
|
||||
%if 0%{!?git_dir:1}
|
||||
Source0: %{forge}/archive/refs/tags/v%{version}.tar.gz#/%{name}-%{version}.tar.gz
|
||||
Source1: %{forge}/releases/download/v%{version}/%{name}-vendor-%{version}.tar.xz
|
||||
%endif
|
||||
|
||||
BuildRequires: cargo
|
||||
BuildRequires: rust
|
||||
BuildRequires: rust >= 1.96
|
||||
BuildRequires: pkgconf-pkg-config
|
||||
BuildRequires: fuse3-devel
|
||||
BuildRequires: gtk4-devel
|
||||
@@ -57,18 +67,31 @@ block-level caching, a scriptable CLI, and a GTK4/Libadwaita desktop app with
|
||||
system tray and search launcher. Unofficial and not affiliated with Proton AG.
|
||||
|
||||
%prep
|
||||
# In-tree build: no Source tarball. Pass --define "git_dir /path/to/checkout".
|
||||
test -n "%{?git_dir}" || (echo 'Pass --define "git_dir $PWD" from the repo root' >&2; exit 1)
|
||||
%if 0%{?git_dir:1}
|
||||
# In-tree build: no Source tarball.
|
||||
test -f %{git_dir}/Cargo.toml
|
||||
cp -a %{git_dir}/LICENSE .
|
||||
%else
|
||||
# -a 1 unpacks the vendored crates into vendor/ inside the source tree.
|
||||
%autosetup -n %{name}-%{version} -a 1
|
||||
mkdir -p .cargo
|
||||
cat > .cargo/config.toml <<'EOF'
|
||||
[source.crates-io]
|
||||
replace-with = "vendored-sources"
|
||||
|
||||
[source.vendored-sources]
|
||||
directory = "vendor"
|
||||
EOF
|
||||
%endif
|
||||
|
||||
%build
|
||||
# The release workflow has already built target/release and passes --define "prebuilt 1".
|
||||
# rpmbuild exports its own compiler flags, which would make cargo rebuild everything.
|
||||
%if 0%{?prebuilt}
|
||||
test -x %{git_dir}/target/release/pdfs
|
||||
%else
|
||||
cd %{git_dir}
|
||||
cargo build --release --locked \
|
||||
cd %{src}
|
||||
cargo build --release %{?git_dir:--locked}%{!?git_dir:--frozen} \
|
||||
--bin pdfs \
|
||||
--bin pdfs-tray \
|
||||
--bin pdfs-app \
|
||||
@@ -76,21 +99,25 @@ cargo build --release --locked \
|
||||
%endif
|
||||
|
||||
%install
|
||||
rel=%{git_dir}/target/release
|
||||
rel=%{src}/target/release
|
||||
install -D -m0755 "$rel/pdfs" %{buildroot}%{_bindir}/pdfs
|
||||
install -D -m0755 "$rel/pdfs-tray" %{buildroot}%{_bindir}/pdfs-tray
|
||||
install -D -m0755 "$rel/pdfs-app" %{buildroot}%{_bindir}/pdfs-app
|
||||
install -D -m0755 "$rel/pdfs-prompt" %{buildroot}%{_bindir}/pdfs-prompt
|
||||
|
||||
install -D -m0644 %{git_dir}/packaging/io.narl.proton-drive-linux.desktop \
|
||||
install -D -m0644 %{src}/packaging/io.narl.proton-drive-linux.desktop \
|
||||
%{buildroot}%{_datadir}/applications/io.narl.proton-drive-linux.desktop
|
||||
install -D -m0644 %{git_dir}/packaging/io.narl.proton-drive-linux-tray.desktop \
|
||||
install -D -m0644 %{src}/packaging/io.narl.proton-drive-linux-tray.desktop \
|
||||
%{buildroot}%{_sysconfdir}/xdg/autostart/io.narl.proton-drive-linux-tray.desktop
|
||||
install -D -m0644 %{git_dir}/packaging/io.narl.proton-drive-linux.svg \
|
||||
install -D -m0644 %{src}/packaging/io.narl.proton-drive-linux.svg \
|
||||
%{buildroot}%{_datadir}/icons/hicolor/scalable/apps/io.narl.proton-drive-linux.svg
|
||||
install -D -m0644 %{git_dir}/packaging/proton-drive.service \
|
||||
install -D -m0644 %{src}/packaging/proton-drive.service \
|
||||
%{buildroot}/usr/lib/systemd/user/proton-drive.service
|
||||
%{git_dir}/po/build.sh %{buildroot}%{_datadir}/locale
|
||||
%{src}/po/build.sh %{buildroot}%{_datadir}/locale
|
||||
%if 0%{!?git_dir:1}
|
||||
# dnf brings new releases from COPR, so the app does not offer its own check.
|
||||
install -D -m0644 /dev/null %{buildroot}%{_datadir}/proton-drive-linux/managed-updates
|
||||
%endif
|
||||
|
||||
# Removing the package does not stop a running daemon: it keeps serving the
|
||||
# mount from the deleted binary, so files deleted under the mount afterwards,
|
||||
@@ -122,6 +149,9 @@ fi
|
||||
%{_datadir}/icons/hicolor/scalable/apps/io.narl.proton-drive-linux.svg
|
||||
/usr/lib/systemd/user/proton-drive.service
|
||||
%{_datadir}/locale/*/LC_MESSAGES/pdfs.mo
|
||||
%if 0%{!?git_dir:1}
|
||||
%{_datadir}/proton-drive-linux/managed-updates
|
||||
%endif
|
||||
|
||||
%changelog
|
||||
* Sun Jul 19 2026 Local Packager - 0.4.0-1
|
||||
|
||||
@@ -19,6 +19,7 @@ crates/pdfs-gui/src/app/pages/status.rs
|
||||
crates/pdfs-gui/src/app/pages/takeout.rs
|
||||
crates/pdfs-gui/src/app/pages/trash.rs
|
||||
crates/pdfs-gui/src/app/pages/verify.rs
|
||||
crates/pdfs-gui/src/app/update.rs
|
||||
crates/pdfs-gui/src/app/widgets/compat.rs
|
||||
crates/pdfs-gui/src/app/widgets/details.rs
|
||||
crates/pdfs-gui/src/app/widgets/file_list.rs
|
||||
|
||||
+199
-173
File diff suppressed because it is too large.
Load diff
+197
-173
File diff suppressed because it is too large.
Load diff
+201
-174
File diff suppressed because it is too large.
Load diff
Reference in new issue
Block a user