├── rustfmt.toml ├── .github ├── FUNDING.yml ├── ISSUE_TEMPLATE │ ├── config.yml │ ├── feature-request.yml │ └── bug-report.yml └── workflows │ ├── test.yml │ └── deploy.yml ├── rust-toolchain.toml ├── .gitignore ├── assets ├── main-window.png ├── com.hunterwittenborn.Celeste.desktop ├── com.hunterwittenborn.Celeste-symbolic.svg ├── context │ ├── com.hunterwittenborn.Celeste.CelesteTrayDone-symbolic.svg │ ├── com.hunterwittenborn.Celeste.CelesteTrayDisconnected-symbolic.svg │ ├── com.hunterwittenborn.Celeste.CelesteTrayWarning-symbolic.svg │ ├── com.hunterwittenborn.Celeste.CelesteTrayPaused-symbolic.svg │ ├── com.hunterwittenborn.Celeste.CelesteTrayLoading-symbolic.svg │ └── com.hunterwittenborn.Celeste.CelesteTraySyncing-symbolic.svg └── com.hunterwittenborn.Celeste-regular.svg ├── src ├── images │ ├── google-drive.png │ └── google-signin.png ├── migrations │ ├── mod.rs │ ├── m20230220_215840_remote_sync_items_fix.rs │ ├── m20230207_204909_sync_dirs_remove_slash_suffix.rs │ └── m20220101_000001_create_table.rs ├── login │ ├── owncloud.rs │ ├── nextcloud.rs │ ├── dropbox.rs │ ├── pcloud.rs │ ├── proton_drive.rs │ ├── webdav.rs │ ├── login_util.rs │ └── gdrive.rs ├── entities │ ├── mod.rs │ ├── remotes.rs │ ├── sync_items.rs │ └── sync_dirs.rs ├── style.scss ├── about.rs ├── traits.rs ├── html │ ├── google-drive.tera.html │ └── auth-template.go.html ├── mpsc.rs ├── util.rs ├── tray.rs ├── gtk_util.rs └── main.rs ├── PRIVACY.md ├── renovate.json ├── makedeb └── PKGBUILD ├── Cargo.toml ├── BUILDING.md ├── snap └── snapcraft.yaml ├── README.md ├── CHANGELOG.md ├── justfile └── po ├── gl.po ├── ro.po ├── com.hunterwittenborn.Celeste.pot ├── nb_NO.po ├── zh_Hant.po ├── tr.po ├── hu.po └── hi.po /rustfmt.toml: -------------------------------------------------------------------------------- 1 | wrap_comments = true -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: hwittenborn 2 | -------------------------------------------------------------------------------- /rust-toolchain.toml: -------------------------------------------------------------------------------- 1 | [toolchain] 2 | channel = "nightly" -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /celeste/src/style.css 2 | /celeste_*.snap 3 | /target 4 | /src/style.css 5 | -------------------------------------------------------------------------------- /assets/main-window.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hwittenborn/celeste/HEAD/assets/main-window.png -------------------------------------------------------------------------------- /src/images/google-drive.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hwittenborn/celeste/HEAD/src/images/google-drive.png -------------------------------------------------------------------------------- /src/images/google-signin.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hwittenborn/celeste/HEAD/src/images/google-signin.png -------------------------------------------------------------------------------- /PRIVACY.md: -------------------------------------------------------------------------------- 1 | # Privacy Policy 2 | This file is being provided in order to make the Google Drive server type function correctly. 3 | 4 | As would be expected, Celeste collects **no data** from the user. 5 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | contact_links: 2 | - name: Discord 3 | url: https://discord.gg/congress 4 | about: Celeste's Discord chat room. 5 | - name: Matrix 6 | url: https://matrix.to/#/#celeste:gnome.org 7 | about: Celeste's Matrix chat room. 8 | -------------------------------------------------------------------------------- /renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "config:base" 4 | ], 5 | "groupName": "all dependencies", 6 | "separateMajorMinor": false, 7 | "groupSlug": "all", 8 | "packageRules": [ 9 | { 10 | "matchPackagePatterns": [ 11 | "*" 12 | ], 13 | "groupName": "all dependencies", 14 | "groupSlug": "all" 15 | } 16 | ] 17 | } 18 | -------------------------------------------------------------------------------- /assets/com.hunterwittenborn.Celeste.desktop: -------------------------------------------------------------------------------- 1 | [Desktop Entry] 2 | Name=Celeste 3 | Exec=celeste 4 | GenericName=Folder Sync 5 | Comment=GUI file synchronization client that can connect to virtually any cloud provider 6 | Keywords=backup;cloud;file sync;sharing;syncing;dropbox;nextcloud;owncloud;google drive;webdav;rclone;proton;proton drive;pcloud 7 | Icon=com.hunterwittenborn.Celeste 8 | Type=Application 9 | Categories=FileTools;FileTransfer;GTK;Utility; 10 | StartupNotify=true 11 | Terminal=false 12 | -------------------------------------------------------------------------------- /src/migrations/mod.rs: -------------------------------------------------------------------------------- 1 | pub use sea_orm_migration::prelude::*; 2 | 3 | mod m20220101_000001_create_table; 4 | mod m20230207_204909_sync_dirs_remove_slash_suffix; 5 | mod m20230220_215840_remote_sync_items_fix; 6 | 7 | pub struct Migrator; 8 | 9 | #[async_trait::async_trait] 10 | impl MigratorTrait for Migrator { 11 | fn migrations() -> Vec> { 12 | vec![ 13 | Box::new(m20220101_000001_create_table::Migration), 14 | Box::new(m20230207_204909_sync_dirs_remove_slash_suffix::Migration), 15 | Box::new(m20230220_215840_remote_sync_items_fix::Migration), 16 | ] 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature-request.yml: -------------------------------------------------------------------------------- 1 | name: Feature Request 2 | description: File a feature request. 3 | title: "[Feature]: " 4 | labels: ["enhancement", "triage"] 5 | body: 6 | - type: textarea 7 | id: description 8 | attributes: 9 | label: Feature Description 10 | description: What kind of feature are you wanting? 11 | validations: 12 | required: true 13 | validations: 14 | required: true 15 | - type: input 16 | id: version 17 | attributes: 18 | label: What version of Celeste are you using? 19 | description: This can be found in Celeste's about page. 20 | validations: 21 | required: true 22 | -------------------------------------------------------------------------------- /src/login/owncloud.rs: -------------------------------------------------------------------------------- 1 | //! The data for an Owncloud Rclone config. 2 | use super::{ 3 | webdav::{WebDavConfig, WebDavType}, 4 | ServerType, 5 | }; 6 | use crate::mpsc::Sender; 7 | use adw::{gtk::Button, ApplicationWindow, EntryRow}; 8 | 9 | #[derive(Clone, Debug, Default)] 10 | pub struct OwncloudConfig { 11 | pub server_name: String, 12 | pub server_url: String, 13 | pub username: String, 14 | pub password: String, 15 | } 16 | 17 | impl super::LoginTrait for OwncloudConfig { 18 | fn get_sections( 19 | _window: &ApplicationWindow, 20 | sender: Sender>, 21 | ) -> (Vec, Button) { 22 | WebDavConfig::webdav_sections(sender, WebDavType::Owncloud) 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/login/nextcloud.rs: -------------------------------------------------------------------------------- 1 | //! The data for a Nextcloud Rclone config. 2 | use super::{ 3 | webdav::{WebDavConfig, WebDavType}, 4 | ServerType, 5 | }; 6 | use crate::mpsc::Sender; 7 | use adw::{gtk::Button, ApplicationWindow, EntryRow}; 8 | 9 | #[derive(Clone, Debug, Default)] 10 | pub struct NextcloudConfig { 11 | pub server_name: String, 12 | pub server_url: String, 13 | pub username: String, 14 | pub password: String, 15 | } 16 | 17 | impl super::LoginTrait for NextcloudConfig { 18 | fn get_sections( 19 | _window: &ApplicationWindow, 20 | sender: Sender>, 21 | ) -> (Vec, Button) { 22 | WebDavConfig::webdav_sections(sender, WebDavType::Nextcloud) 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/entities/mod.rs: -------------------------------------------------------------------------------- 1 | //! `SeaORM` Entity. Generated by sea-orm-codegen 0.10.3 2 | mod remotes; 3 | mod sync_dirs; 4 | mod sync_items; 5 | 6 | pub use remotes::ActiveModel as RemotesActiveModel; 7 | pub use remotes::Column as RemotesColumn; 8 | pub use remotes::Entity as RemotesEntity; 9 | pub use remotes::Model as RemotesModel; 10 | 11 | pub use sync_dirs::ActiveModel as SyncDirsActiveModel; 12 | pub use sync_dirs::Column as SyncDirsColumn; 13 | pub use sync_dirs::Entity as SyncDirsEntity; 14 | pub use sync_dirs::Model as SyncDirsModel; 15 | 16 | pub use sync_items::ActiveModel as SyncItemsActiveModel; 17 | pub use sync_items::Column as SyncItemsColumn; 18 | pub use sync_items::Entity as SyncItemsEntity; 19 | pub use sync_items::Model as SyncItemsModel; 20 | -------------------------------------------------------------------------------- /src/entities/remotes.rs: -------------------------------------------------------------------------------- 1 | //! `SeaORM` Entity. Generated by sea-orm-codegen 0.10.3 2 | use sea_orm::entity::prelude::*; 3 | use serde::{Deserialize, Serialize}; 4 | 5 | #[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Serialize, Deserialize)] 6 | #[sea_orm(table_name = "remotes")] 7 | pub struct Model { 8 | #[sea_orm(primary_key)] 9 | pub id: i32, 10 | pub name: String, 11 | } 12 | 13 | #[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] 14 | pub enum Relation { 15 | #[sea_orm(has_many = "super::sync_dirs::Entity")] 16 | SyncDirs, 17 | } 18 | 19 | impl Related for Entity { 20 | fn to() -> RelationDef { 21 | Relation::SyncDirs.def() 22 | } 23 | } 24 | 25 | impl ActiveModelBehavior for ActiveModel {} 26 | -------------------------------------------------------------------------------- /src/style.scss: -------------------------------------------------------------------------------- 1 | // Global padding on windows. 2 | .celeste-global-padding > *:not(contents):not(tooltip), .celeste-global-padding > contents { 3 | & > box, & > leaflet > box { 4 | & > *:not(headerbar):not(stacksidebar) { 5 | margin-left: 1em; 6 | margin-right: 1em; 7 | 8 | &:nth-child(2) { 9 | margin-top: 1em; 10 | } 11 | 12 | &:last-child { 13 | margin-bottom: 1em; 14 | } 15 | } 16 | } 17 | } 18 | 19 | 20 | // Formatting for code blocks. 21 | .celeste-scrollable-codeblock { 22 | border-style: solid; 23 | border-width: 1px; 24 | border-color: unquote("@borders"); 25 | } 26 | 27 | // Styling for `EntryRow`s with no titles. 28 | row.celeste-no-title box widget label { 29 | font-size: 0; 30 | } 31 | -------------------------------------------------------------------------------- /makedeb/PKGBUILD: -------------------------------------------------------------------------------- 1 | # Maintainer: Hunter Wittenborn 2 | pkgname=celeste 3 | pkgver=0.8.3 4 | pkgrel=1 5 | pkgdesc='Sync your cloud files' 6 | arch=('any') 7 | depends=( 8 | 'libadwaita-1-0' 9 | 'libdbus-1-3' 10 | 'rclone' 11 | ) 12 | makedepends=( 13 | 'just' 14 | 'libadwaita-1-dev' 15 | 'libclang-15-dev' 16 | 'libdbus-1-dev' 17 | 'libgtk-4-dev' 18 | 'golang-go>=2:1.17' 19 | 'pkg-config' 20 | 'rustup' 21 | ) 22 | license=('GPL-3.0') 23 | url='https://github.com/hwittenborn/celeste' 24 | 25 | source=("${url}/archive/refs/tags/v${pkgver}.tar.gz") 26 | sha256sums=('SKIP') 27 | 28 | build() { 29 | cd "${pkgname}-${pkgver}/" 30 | just build 31 | } 32 | 33 | package() { 34 | cd "${pkgname}-${pkgver}/" 35 | DESTDIR="${pkgdir}" just install 36 | } 37 | 38 | # vim: set sw=4 expandtab: 39 | -------------------------------------------------------------------------------- /src/about.rs: -------------------------------------------------------------------------------- 1 | use adw::{gtk::License, prelude::*, AboutWindow, Application}; 2 | 3 | static METADATA: &str = include_str!(concat!(env!("OUT_DIR"), "/release.xml")); 4 | 5 | pub fn about_window(app: &Application) { 6 | let window = AboutWindow::builder() 7 | .application(app) 8 | .application_icon("com.hunterwittenborn.Celeste") 9 | .application_name("Celeste") 10 | .copyright("© 2022-2023 Hunter Wittenborn") 11 | .developer_name("Hunter Wittenborn") 12 | .developers(vec![ 13 | "Hunter Wittenborn https://hunterwittenborn.com".to_string() 14 | ]) 15 | .issue_url("https://github.com/hwittenborn/celeste") 16 | .license_type(License::Gpl30) 17 | .release_notes(METADATA) 18 | .support_url("https://github.com/hwittenborn/celeste/issues") 19 | .build(); 20 | window.add_credit_section(Some(&tr::tr!("App icons by")), &["Adrien Facélina"]); 21 | 22 | window.show(); 23 | } 24 | -------------------------------------------------------------------------------- /src/login/dropbox.rs: -------------------------------------------------------------------------------- 1 | //! The data for a Dropbox Rclone config. 2 | use super::ServerType; 3 | use crate::{ 4 | login::gdrive::{AuthType, GDriveConfig}, 5 | mpsc::Sender, 6 | }; 7 | use adw::{gtk::Button, ApplicationWindow, EntryRow}; 8 | 9 | static DEFAULT_CLIENT_ID: &str = "hke0fgr43viaq03"; 10 | static DEFAULT_CLIENT_SECRET: &str = "o4cpx8trcnneq7a"; 11 | 12 | #[derive(Clone, Debug, Default)] 13 | pub struct DropboxConfig { 14 | pub server_name: String, 15 | pub client_id: String, 16 | pub client_secret: String, 17 | pub auth_json: String, 18 | } 19 | 20 | impl super::LoginTrait for DropboxConfig { 21 | fn get_sections( 22 | window: &ApplicationWindow, 23 | sender: Sender>, 24 | ) -> (Vec, Button) { 25 | GDriveConfig::auth_sections( 26 | window, 27 | sender, 28 | AuthType::Dropbox, 29 | DEFAULT_CLIENT_ID.to_owned(), 30 | DEFAULT_CLIENT_SECRET.to_owned(), 31 | ) 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/login/pcloud.rs: -------------------------------------------------------------------------------- 1 | //! The data for a pCloud Rclone config. 2 | use super::ServerType; 3 | use crate::{ 4 | login::gdrive::{AuthType, GDriveConfig}, 5 | mpsc::Sender, 6 | }; 7 | use adw::{gtk::Button, ApplicationWindow, EntryRow}; 8 | 9 | static DEFAULT_CLIENT_ID: &str = "KRzpo46NKb7"; 10 | static DEFAULT_CLIENT_SECRET: &str = "g10qvqgWR85lSvEQWlIqCmPYIhwX"; 11 | 12 | #[derive(Clone, Debug, Default)] 13 | pub struct PCloudConfig { 14 | pub server_name: String, 15 | pub client_id: String, 16 | pub client_secret: String, 17 | pub auth_json: String, 18 | } 19 | 20 | impl super::LoginTrait for PCloudConfig { 21 | fn get_sections( 22 | window: &ApplicationWindow, 23 | sender: Sender>, 24 | ) -> (Vec, Button) { 25 | GDriveConfig::auth_sections( 26 | window, 27 | sender, 28 | AuthType::PCloud, 29 | DEFAULT_CLIENT_ID.to_owned(), 30 | DEFAULT_CLIENT_SECRET.to_owned(), 31 | ) 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/traits.rs: -------------------------------------------------------------------------------- 1 | use std::cell::{Ref, RefCell, RefMut}; 2 | 3 | pub mod prelude { 4 | pub use super::{GetRcRef, GetRcRefMut}; 5 | } 6 | 7 | /// A trait to get the [`Ref`] out of a [`RefCell`], waiting until it can be 8 | /// obtained. 9 | pub trait GetRcRef { 10 | fn get_ref(&self) -> Ref<'_, T>; 11 | } 12 | 13 | impl GetRcRef for RefCell { 14 | fn get_ref(&self) -> Ref<'_, T> { 15 | loop { 16 | if let Ok(reference) = self.try_borrow() { 17 | return reference; 18 | } 19 | } 20 | } 21 | } 22 | 23 | /// A trait to get the [`RefMut`] out of a [`RefCell`], waiting until it can be 24 | /// obtained. 25 | pub trait GetRcRefMut { 26 | fn get_mut_ref(&self) -> RefMut<'_, T>; 27 | } 28 | 29 | impl GetRcRefMut for RefCell { 30 | fn get_mut_ref(&self) -> RefMut<'_, T> { 31 | loop { 32 | if let Ok(reference) = self.try_borrow_mut() { 33 | return reference; 34 | } 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: Run unit tests 2 | on: 3 | - pull_request 4 | jobs: 5 | run-tests: 6 | runs-on: ubuntu-latest 7 | container: 8 | image: proget.hunterwittenborn.com/docker/makedeb/makedeb:ubuntu-lunar 9 | options: --user root 10 | steps: 11 | - name: Checkout Git repository 12 | uses: actions/checkout@v3 13 | - name: Setup makedeb APT repositories 14 | uses: makedeb/setup-makedeb@main 15 | with: 16 | pbmpr-repo: true 17 | - name: Install needed APT packages 18 | run: | 19 | # Set the directory permissions to be correct so it can be used correctly by makedeb. 20 | chown 'makedeb:makedeb' makedeb/ -R 21 | 22 | # Get our needed dependencies by just sourcing them from the PKGBUILD. 23 | echo 'build() { true; }; package() { true; }; source=(); sha256sums=()' >> makedeb/PKGBUILD 24 | cd makedeb && sudo -u makedeb makedeb -s --no-confirm 25 | - name: Run formatting checks 26 | run: cargo fmt --check 27 | - name: Run Clippy checks 28 | run: just clippy 29 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "celeste" 3 | version = "0.8.3" 4 | edition = "2021" 5 | 6 | [dependencies] 7 | adw = { package = "libadwaita", version = "0.2.1", features = ["v1_2"] } 8 | base64 = "0.20.0" 9 | blocking = "1.6.1" 10 | clap = { version = "4.5.7", features = ["derive", "env"] } 11 | file-lock = "2.1.11" 12 | futures = "0.3.30" 13 | glib = "0.16.9" 14 | glob = "0.3.1" 15 | hw-msg = "0.3.1" 16 | indexmap = "1.9.3" 17 | ksni = "0.2.2" 18 | lazy_static = "1.4.0" 19 | librclone = { version = "0.6.3" } 20 | nix = "0.26.4" 21 | rand = "0.8.5" 22 | regex = "1.10.5" 23 | rocket = "0.5.1" 24 | sea-orm = { version = "0.10.7", features = ["sqlx-sqlite", "runtime-async-std-rustls", "macros"] } 25 | sea-orm-migration = "0.10.7" 26 | serde = { version = "1.0.203", features = ["derive"] } 27 | serde_json = "1.0.117" 28 | tempfile = "3.10.1" 29 | tera = "1.20.0" 30 | time = { version = "0.3.36", features = ["serde-well-known"] } 31 | tokio = { version = "1.38.0", features = ["macros", "rt-multi-thread", "sync"] } 32 | toml_edit = { version = "0.14.4", features = ["serde"] } 33 | tr = "0.1.7" 34 | url = "2.5.2" 35 | 36 | [build-dependencies] 37 | grass = "0.11.2" 38 | nipper = "0.1.9" 39 | -------------------------------------------------------------------------------- /BUILDING.md: -------------------------------------------------------------------------------- 1 | # Building Celeste 2 | This document contains the instructions needed to build Celeste from source. 3 | 4 | If you're just trying to install Celeste, look at the [installation instructions](/README.md#installation) instead. 5 | 6 | ## Needed packages 7 | Celeste needs some packages installed in order to build: 8 | 9 | - [rustup](https://rustup.rs/) 10 | - [Go](https://go.dev/) 11 | - [just](https://github.com/casey/just) 12 | - Headers for GTK4 and Libadwaita (The best way I've found to find the needed packages is to try building as described below, and then looking at the output to see what the missing header files are. You can then install the needed packages via your package manager. This process is kind of tedious, but I don't know any other methods at the moment). 13 | 14 | ## Building 15 | To build the project, run the following from the root of the repository: 16 | 17 | ```sh 18 | just build 19 | ``` 20 | 21 | To install the needed files into a packaging directory (such as how `${pkgdir}` functions in Arch/makedeb PKGBUILDs), run the following from the root of the repository (replacing `{pkgdir}` with the location of your packaging directory): 22 | 23 | ```sh 24 | DESTDIR='{pkgdir}' just install 25 | ``` 26 | -------------------------------------------------------------------------------- /src/migrations/m20230220_215840_remote_sync_items_fix.rs: -------------------------------------------------------------------------------- 1 | use sea_orm::{ConnectionTrait, FromQueryResult, JsonValue, Statement}; 2 | use sea_orm_migration::prelude::*; 3 | 4 | #[derive(DeriveMigrationName)] 5 | pub struct Migration; 6 | 7 | #[async_trait::async_trait] 8 | impl MigrationTrait for Migration { 9 | async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> { 10 | let db = manager.get_connection(); 11 | let backend = manager.get_database_backend(); 12 | 13 | let sync_items = JsonValue::find_by_statement(Statement::from_sql_and_values( 14 | backend, 15 | "SELECT remote_path FROM sync_items;", 16 | [], 17 | )) 18 | .all(db) 19 | .await?; 20 | 21 | for item in sync_items { 22 | if let Some(item_str) = item["remote_path"].as_str() 23 | && item_str.starts_with('/') 24 | { 25 | db.execute(Statement::from_string( 26 | backend, 27 | format!(r#"DELETE FROM sync_items where remote_path = "{item_str}""#), 28 | )) 29 | .await?; 30 | } 31 | } 32 | 33 | Ok(()) 34 | } 35 | 36 | async fn down(&self, _manager: &SchemaManager) -> Result<(), DbErr> { 37 | Ok(()) 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/html/google-drive.tera.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Celeste Cloud Authenticator 5 | 6 | 7 | 8 | 31 | 32 | 33 |

Celeste Cloud Authenticator

34 |

Click the button below to continue with authentication to Google Drive():

35 | 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug-report.yml: -------------------------------------------------------------------------------- 1 | name: Bug Report 2 | description: File a bug report. 3 | title: "[Bug]: " 4 | labels: ["bug", "triage"] 5 | body: 6 | - type: textarea 7 | id: description 8 | attributes: 9 | label: Bug Description 10 | description: How is this bug occurring? 11 | validations: 12 | required: true 13 | - type: dropdown 14 | id: installation-source 15 | attributes: 16 | label: Installation Source 17 | description: Where did you install Celeste from? 18 | options: 19 | - Flatpak 20 | - Snap 21 | - Prebuilt-MPR 22 | - Other (specify the source in the bug description above) 23 | validations: 24 | required: true 25 | - type: input 26 | id: version 27 | attributes: 28 | label: What version of Celeste are you using? 29 | description: This can be found in Celeste's about page. 30 | validations: 31 | required: true 32 | - type: dropdown 33 | id: remote-type 34 | attributes: 35 | label: Storage Provider 36 | description: If your issue is happening in relation to a specific cloud provider, specify it below. 37 | options: 38 | - Dropbox 39 | - Google Drive 40 | - Nextcloud 41 | - Owncloud 42 | - pCloud 43 | - Proton Drive 44 | - WebDAV 45 | validations: 46 | required: true 47 | -------------------------------------------------------------------------------- /src/migrations/m20230207_204909_sync_dirs_remove_slash_suffix.rs: -------------------------------------------------------------------------------- 1 | use sea_orm::{ConnectionTrait, FromQueryResult, JsonValue, Statement}; 2 | use sea_orm_migration::prelude::*; 3 | 4 | #[derive(DeriveMigrationName)] 5 | pub struct Migration; 6 | 7 | #[async_trait::async_trait] 8 | impl MigrationTrait for Migration { 9 | async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> { 10 | let local_dirs = JsonValue::find_by_statement(Statement::from_sql_and_values( 11 | manager.get_database_backend(), 12 | r#"select local_path from sync_dirs"#, 13 | [], 14 | )) 15 | .all(manager.get_connection()) 16 | .await?; 17 | 18 | for dir in local_dirs { 19 | let dir_string = match dir["local_path"].as_str().unwrap().strip_suffix('/') { 20 | Some(string) => string, 21 | None => continue, 22 | }; 23 | 24 | manager.get_connection() 25 | .execute(Statement::from_string( 26 | manager.get_database_backend(), 27 | format!(r#"update sync_dirs set local_path = "{dir_string}" where local_path = "{dir_string}/""#) 28 | )) 29 | .await?; 30 | } 31 | 32 | Ok(()) 33 | } 34 | 35 | async fn down(&self, _manager: &SchemaManager) -> Result<(), DbErr> { 36 | Ok(()) 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /assets/com.hunterwittenborn.Celeste-symbolic.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /src/entities/sync_items.rs: -------------------------------------------------------------------------------- 1 | //! `SeaORM` Entity. Generated by sea-orm-codegen 0.10.3 2 | use sea_orm::entity::prelude::*; 3 | use serde::{Deserialize, Serialize}; 4 | 5 | #[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Serialize, Deserialize)] 6 | #[sea_orm(table_name = "sync_items")] 7 | pub struct Model { 8 | #[sea_orm(primary_key)] 9 | pub id: i32, 10 | pub sync_dir_id: i32, 11 | /// The local item being synced, as an absolute path with no '/' at the end. 12 | pub local_path: String, 13 | /// The remote path being synced, relative to the directory of the matching 14 | /// `SyncDirs::sync_dir` specified by `Self::sync_dir_id`. 15 | pub remote_path: String, 16 | /// The local UNIX timestamp of the item when last synced. 17 | pub last_local_timestamp: i32, 18 | /// The remote UNIX timestamp of the item when last synced. 19 | pub last_remote_timestamp: i32, 20 | } 21 | 22 | #[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] 23 | pub enum Relation { 24 | #[sea_orm( 25 | belongs_to = "super::sync_dirs::Entity", 26 | from = "Column::SyncDirId", 27 | to = "super::sync_dirs::Column::Id", 28 | on_update = "NoAction", 29 | on_delete = "NoAction" 30 | )] 31 | SyncDirs, 32 | } 33 | 34 | impl Related for Entity { 35 | fn to() -> RelationDef { 36 | Relation::SyncDirs.def() 37 | } 38 | } 39 | 40 | impl ActiveModelBehavior for ActiveModel {} 41 | -------------------------------------------------------------------------------- /src/mpsc.rs: -------------------------------------------------------------------------------- 1 | use adw::gtk::glib::MainContext; 2 | use std::fmt::Debug; 3 | use tokio::sync::mpsc::{self as tokio_mpsc, Receiver as TokioReceiver, Sender as TokioSender}; 4 | 5 | /// Sends values to the associated [`Receiver`]. 6 | #[derive(Clone)] 7 | pub struct Sender { 8 | sender: TokioSender, 9 | } 10 | 11 | impl Sender { 12 | /// Send a value to the [`Receiver`]. This function does nothing if the 13 | /// [`Receiver`] has already closed up their connections. 14 | pub fn send(&self, item: T) { 15 | MainContext::default() 16 | .block_on(self.sender.send(item)) 17 | .unwrap_or(()); 18 | } 19 | } 20 | 21 | /// Receives values from the associated [`Sender`]. 22 | pub struct Receiver { 23 | receiver: TokioReceiver, 24 | } 25 | 26 | impl Receiver { 27 | /// Receive the value from the [`Sender`]. 28 | pub fn recv(&mut self) -> T { 29 | let item = MainContext::default() 30 | .block_on(self.receiver.recv()) 31 | .unwrap(); 32 | item 33 | } 34 | } 35 | 36 | /// Return a tuple containing a [`Sender`] and [`Receiver`] that can be used to 37 | /// send messages across a GTK application. 38 | pub fn channel() -> (Sender, Receiver) { 39 | let (tokio_sender, tokio_receiver) = tokio_mpsc::channel(1); 40 | ( 41 | Sender { 42 | sender: tokio_sender, 43 | }, 44 | Receiver { 45 | receiver: tokio_receiver, 46 | }, 47 | ) 48 | } 49 | -------------------------------------------------------------------------------- /src/html/auth-template.go.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Celeste Cloud Authenticator 5 | 6 | 7 | 8 | 26 | 27 | 28 |

Celeste Cloud Authenticator

29 | {{ if .OK }} 30 |

Authentication has completed successfully. Return back to Celeste to continue.

31 | {{ else }} 32 |

Celeste was unable to authenticate. The information below may help to explain why.

33 |
34 | Error: {{ .Name }}
35 | {{ if .Description }}Description: {{ .Description }}{{ end }}
36 | {{ if .Code }}Error Code: {{ .Code }}{{ end }}
37 | 
38 | {{ if .HelpURL }}

The following URL may also help if you still encounter issues: {{ .HelpURL }}

{{ end }} 39 | {{ end }} 40 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /src/util.rs: -------------------------------------------------------------------------------- 1 | use adw::glib::{self, MainContext}; 2 | use futures::future::Future; 3 | use std::path::PathBuf; 4 | 5 | /// The ID of the app. 6 | pub static APP_ID: &str = "com.hunterwittenborn.Celeste"; 7 | 8 | /// Get the value out of a future. 9 | pub fn await_future(future: F) -> F::Output { 10 | futures::executor::block_on(future) 11 | } 12 | 13 | /// Run a closure in the background so that the UI can keep running. 14 | pub fn run_in_background T + Send + 'static>(f: F) -> T { 15 | MainContext::default().block_on(blocking::unblock(f)) 16 | } 17 | 18 | /// Format a directory with the user's home directory replaced with '~'. 19 | pub fn fmt_home(dir: &str) -> String { 20 | let home_dir = glib::home_dir().into_os_string().into_string().unwrap(); 21 | 22 | match dir.strip_prefix(&home_dir) { 23 | Some(string) => "~".to_string() + string, 24 | None => dir.to_string(), 25 | } 26 | } 27 | 28 | /// Get the user's config directory. 29 | pub fn get_config_dir() -> PathBuf { 30 | let mut config_dir = glib::user_config_dir(); 31 | config_dir.push("celeste"); 32 | config_dir 33 | } 34 | 35 | /// Strip the slashes from the beginning and end of a string. 36 | pub fn strip_slashes(string: &str) -> String { 37 | let stripped_prefix = match string.strip_prefix('/') { 38 | Some(string) => string.to_string(), 39 | None => string.to_string(), 40 | }; 41 | 42 | match stripped_prefix.strip_suffix('/') { 43 | Some(string) => string.to_string(), 44 | None => stripped_prefix, 45 | } 46 | } 47 | 48 | /// Macro to get the title of a window. 49 | #[macro_export] 50 | macro_rules! get_title { 51 | ($($arg:tt)*) => { 52 | tr::tr!($($arg)*) + " - Celeste" 53 | } 54 | } 55 | 56 | pub use crate::get_title; 57 | -------------------------------------------------------------------------------- /src/migrations/m20220101_000001_create_table.rs: -------------------------------------------------------------------------------- 1 | use sea_orm::{ConnectionTrait, Statement}; 2 | use sea_orm_migration::prelude::*; 3 | 4 | #[derive(DeriveMigrationName)] 5 | pub struct Migration; 6 | 7 | #[async_trait::async_trait] 8 | impl MigrationTrait for Migration { 9 | async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> { 10 | let sql = r#" 11 | CREATE TABLE remotes ( 12 | id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, 13 | name TEXT NOT NULL 14 | ); 15 | 16 | 17 | CREATE TABLE sync_dirs ( 18 | id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, 19 | remote_id INTEGER NOT NULL, 20 | local_path TEXT NOT NULL, 21 | remote_path TEXT NOT NULL, 22 | FOREIGN KEY(remote_id) REFERENCES remotes(id) 23 | ); 24 | 25 | CREATE TABLE sync_items ( 26 | id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, 27 | sync_dir_id INTEGER NOT NULL, 28 | local_path TEXT NOT NULL, 29 | remote_path TEXT NOT NULL, 30 | last_local_timestamp INTEGER NOT NULL, 31 | last_remote_timestamp INTEGER NOT NULL, 32 | FOREIGN KEY(sync_dir_id) REFERENCES sync_dirs(id) 33 | ); 34 | "#; 35 | let stmt = Statement::from_string(manager.get_database_backend(), sql.to_owned()); 36 | manager.get_connection().execute(stmt).await.map(|_| ()) 37 | } 38 | 39 | async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> { 40 | let sql = " 41 | DROP TABLE `sync_items`; 42 | DROP TABLE `sync_dirs`; 43 | DROP TABLE `remotes`; 44 | "; 45 | let stmt = Statement::from_string(manager.get_database_backend(), sql.to_owned()); 46 | manager.get_connection().execute(stmt).await.map(|_| ()) 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/entities/sync_dirs.rs: -------------------------------------------------------------------------------- 1 | //! `SeaORM` Entity. Generated by sea-orm-codegen 0.10.3 2 | use crate::util; 3 | use sea_orm::entity::prelude::*; 4 | use serde::{Deserialize, Serialize}; 5 | 6 | #[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Serialize, Deserialize)] 7 | #[sea_orm(table_name = "sync_dirs")] 8 | pub struct Model { 9 | #[sea_orm(primary_key)] 10 | pub id: i32, 11 | pub remote_id: i32, 12 | /// The local directory being synced, as an absolute path with no '/' at the 13 | /// end. 14 | pub local_path: String, 15 | /// The remote path being synced, as an absolute path (though it won't start 16 | /// with `/`). 17 | pub remote_path: String, 18 | } 19 | 20 | impl Model { 21 | // See if this item still exists in the database (i.e. the struct was created 22 | // and the item was later deleted). 23 | pub fn exists(&self, db: &DatabaseConnection) -> bool { 24 | util::await_future( 25 | Entity::find() 26 | .filter(Column::LocalPath.eq(self.local_path.clone())) 27 | .filter(Column::RemotePath.eq(self.remote_path.clone())) 28 | .one(db), 29 | ) 30 | .unwrap() 31 | .is_some() 32 | } 33 | } 34 | 35 | #[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] 36 | pub enum Relation { 37 | #[sea_orm( 38 | belongs_to = "super::remotes::Entity", 39 | from = "Column::RemoteId", 40 | to = "super::remotes::Column::Id", 41 | on_update = "NoAction", 42 | on_delete = "NoAction" 43 | )] 44 | Remotes, 45 | #[sea_orm(has_many = "super::sync_items::Entity")] 46 | SyncItems, 47 | } 48 | 49 | impl Related for Entity { 50 | fn to() -> RelationDef { 51 | Relation::Remotes.def() 52 | } 53 | } 54 | 55 | impl Related for Entity { 56 | fn to() -> RelationDef { 57 | Relation::SyncItems.def() 58 | } 59 | } 60 | 61 | impl ActiveModelBehavior for ActiveModel {} 62 | -------------------------------------------------------------------------------- /snap/snapcraft.yaml: -------------------------------------------------------------------------------- 1 | name: celeste 2 | base: core22 3 | grade: stable 4 | confinement: strict 5 | adopt-info: celeste 6 | # TODO: Re-enable this once the Flatpak doesn't rely on the Snap for building. 7 | #compression: lzo 8 | architectures: 9 | - build-on: amd64 10 | - build-on: arm64 11 | 12 | 13 | parts: 14 | rustup: 15 | plugin: nil 16 | build-packages: [wget] 17 | build-environment: 18 | - RUSTUP_HOME: $CRAFT_PART_INSTALL/usr/share/rust 19 | - CARGO_HOME: $CRAFT_PART_INSTALL/usr/share/rust 20 | - CARGO_BUILD_JOBS: $CRAFT_PARALLEL_BUILD_COUNT 21 | override-pull: | 22 | wget https://sh.rustup.rs -O $CRAFT_PART_SRC/rustup-init.sh 23 | chmod +x $CRAFT_PART_SRC/rustup-init.sh 24 | override-build: | 25 | $CRAFT_PART_SRC/rustup-init.sh -y --no-modify-path 26 | mkdir -p $CRAFT_PART_INSTALL/usr/bin 27 | for i in `ls $RUSTUP_HOME/bin/`; do 28 | ln -s ../share/rust/bin/$i $CRAFT_PART_INSTALL/usr/bin/$i 29 | done 30 | override-prime: '' 31 | 32 | celeste: 33 | after: [ rustup ] 34 | plugin: dump 35 | source: . 36 | parse-info: [ "usr/share/metainfo/com.hunterwittenborn.Celeste.metainfo.xml" ] 37 | build-environment: 38 | - RUSTUP_HOME: $CRAFT_STAGE/usr/share/rust 39 | - CARGO_HOME: $CRAFT_STAGE/usr/share/rust 40 | - CARGO_BUILD_JOBS: $CRAFT_PARALLEL_BUILD_COUNT 41 | - PATH: $RUSTUP_HOME/bin:$PATH 42 | build-packages: 43 | - libclang-15-dev 44 | build-snaps: 45 | - go 46 | override-build: | 47 | cargo install just 48 | just build 49 | DESTDIR=$CRAFT_PART_INSTALL just install 50 | 51 | deps: 52 | after: 53 | - celeste 54 | plugin: nil 55 | stage-packages: 56 | - rclone 57 | prime: 58 | - usr/bin/rclone 59 | 60 | slots: 61 | celeste: 62 | interface: dbus 63 | bus: session 64 | name: com.hunterwittenborn.Celeste 65 | 66 | apps: 67 | celeste: 68 | command: usr/bin/celeste 69 | autostart: com.hunterwittenborn.Celeste.desktop 70 | desktop: usr/share/applications/com.hunterwittenborn.Celeste.desktop 71 | extensions: [gnome] 72 | plugs: 73 | - gsettings 74 | - home 75 | - network 76 | - network-bind 77 | - unity7 78 | slots: 79 | - celeste 80 | -------------------------------------------------------------------------------- /src/tray.rs: -------------------------------------------------------------------------------- 1 | use crate::{launch, util}; 2 | use ksni::{menu::StandardItem, MenuItem, Tray as KsniTray}; 3 | 4 | pub struct Tray { 5 | status: String, 6 | pub icon: String, 7 | } 8 | 9 | impl Tray { 10 | #[allow(clippy::new_without_default)] 11 | pub fn new() -> Self { 12 | Self { 13 | status: tr::tr!("Awaiting sync checks..."), 14 | icon: "com.hunterwittenborn.Celeste.CelesteTrayLoading-symbolic".to_owned(), 15 | } 16 | } 17 | 18 | pub fn set_msg(&mut self, msg: T) { 19 | self.status = msg.to_string(); 20 | } 21 | 22 | pub fn set_syncing(&mut self) { 23 | self.icon = "com.hunterwittenborn.Celeste.CelesteTraySyncing-symbolic".to_owned(); 24 | } 25 | 26 | pub fn set_warning(&mut self) { 27 | self.icon = "com.hunterwittenborn.Celeste.CelesteTrayWarning-symbolic".to_owned(); 28 | } 29 | 30 | pub fn set_done(&mut self) { 31 | self.icon = "com.hunterwittenborn.Celeste.CelesteTrayDone-symbolic".to_owned(); 32 | } 33 | 34 | pub fn set_disconnected(&mut self) { 35 | self.icon = "com.hunterwittenborn.Celeste.CelesteTrayDisconnected-symbolic".to_owned(); 36 | } 37 | } 38 | 39 | impl KsniTray for Tray { 40 | fn icon_name(&self) -> String { 41 | self.icon.clone() 42 | } 43 | 44 | fn title(&self) -> String { 45 | "Celeste".to_owned() 46 | } 47 | 48 | fn id(&self) -> String { 49 | util::APP_ID.to_owned() 50 | } 51 | 52 | fn menu(&self) -> Vec> { 53 | vec![ 54 | MenuItem::Standard(StandardItem { 55 | label: self.status.clone(), 56 | enabled: false, 57 | ..Default::default() 58 | }), 59 | MenuItem::Standard(StandardItem { 60 | label: tr::tr!("Open"), 61 | activate: Box::new(|_| { 62 | *(*launch::OPEN_REQUEST).lock().unwrap() = true; 63 | }), 64 | ..Default::default() 65 | }), 66 | MenuItem::Standard(StandardItem { 67 | label: tr::tr!("Close"), 68 | activate: Box::new(|_| { 69 | *(*launch::CLOSE_REQUEST).lock().unwrap() = true; 70 | }), 71 | ..Default::default() 72 | }), 73 | ] 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /src/gtk_util.rs: -------------------------------------------------------------------------------- 1 | use crate::{mpsc, util}; 2 | use adw::{ 3 | glib, 4 | gtk::{Orientation, ScrolledWindow, Separator, TextBuffer, TextView}, 5 | prelude::*, 6 | MessageDialog, 7 | }; 8 | 9 | /// Show an error screen. 10 | pub fn show_error(primary_text: &str, secondary_text: Option<&str>) { 11 | let (sender, mut receiver) = mpsc::channel::<()>(); 12 | let mut dialog = MessageDialog::builder() 13 | .heading(primary_text) 14 | .modal(true) 15 | .resizable(true); 16 | if let Some(text) = secondary_text { 17 | dialog = dialog.body(text); 18 | } 19 | let dialog = dialog.build(); 20 | dialog.add_response("ok", &tr::tr!("Ok")); 21 | dialog.connect_response( 22 | None, 23 | glib::clone!(@strong sender => move |dialog, resp| { 24 | if ["ok"].contains(&resp) { 25 | dialog.close(); 26 | sender.send(()); 27 | } 28 | }), 29 | ); 30 | dialog.show(); 31 | receiver.recv(); 32 | } 33 | 34 | // Show an error screen with a codeblock. 35 | pub fn show_codeblock_error(primary_text: &str, code: &str) { 36 | let (sender, mut receiver) = mpsc::channel::<()>(); 37 | let dialog = MessageDialog::builder() 38 | .title(&util::get_title!("{title}")) 39 | .heading(primary_text) 40 | .extra_child(&codeblock(code)) 41 | .resizable(true) 42 | .build(); 43 | dialog.add_response("ok", &tr::tr!("Ok")); 44 | dialog.connect_response( 45 | None, 46 | glib::clone!(@strong sender => move |dialog, resp| { 47 | if resp != "ok" { 48 | return; 49 | } 50 | dialog.close(); 51 | sender.send(()); 52 | }), 53 | ); 54 | dialog.show(); 55 | receiver.recv(); 56 | } 57 | 58 | /// Create a codeblock. 59 | pub fn codeblock(text: &str) -> ScrolledWindow { 60 | let buffer = TextBuffer::builder().text(text).build(); 61 | let block = TextView::builder() 62 | .buffer(&buffer) 63 | .editable(false) 64 | .focusable(false) 65 | .monospace(true) 66 | .build(); 67 | ScrolledWindow::builder() 68 | .child(&block) 69 | .hexpand(true) 70 | .vexpand(true) 71 | .min_content_width(100) 72 | .min_content_height(100) 73 | .margin_top(10) 74 | .css_classes(vec!["celeste-scrollable-codeblock".to_string()]) 75 | .build() 76 | } 77 | 78 | /// Get an invisible separator. 79 | pub fn separator() -> Separator { 80 | Separator::builder() 81 | .orientation(Orientation::Vertical) 82 | .css_classes(vec!["spacer".to_string()]) 83 | .build() 84 | } 85 | -------------------------------------------------------------------------------- /src/login/proton_drive.rs: -------------------------------------------------------------------------------- 1 | //! The data for a Proton Drive Rclone config. 2 | use super::ServerType; 3 | use crate::{login::login_util, mpsc::Sender}; 4 | use adw::{glib, gtk::Button, prelude::*, ApplicationWindow, EntryRow}; 5 | 6 | #[derive(Clone, Debug, Default)] 7 | pub struct ProtonDriveConfig { 8 | pub server_name: String, 9 | pub username: String, 10 | pub password: String, 11 | pub totp: String, 12 | } 13 | 14 | impl super::LoginTrait for ProtonDriveConfig { 15 | fn get_sections( 16 | _window: &ApplicationWindow, 17 | sender: Sender>, 18 | ) -> (Vec, Button) { 19 | let mut sections = vec![]; 20 | let server_name = login_util::server_name_input(); 21 | let username = login_util::username_input(); 22 | let password = login_util::password_input(); 23 | let totp = login_util::totp_input(); 24 | let submit_button = login_util::submit_button(); 25 | 26 | sections.push(server_name.clone()); 27 | sections.push(username.clone()); 28 | sections.push(password.clone().into()); 29 | sections.push(totp.clone()); 30 | 31 | submit_button.connect_clicked( 32 | glib::clone!(@weak server_name, @weak username, @weak password, @weak totp => move |_| { 33 | sender.send(Some(ServerType::ProtonDrive(ProtonDriveConfig { 34 | server_name: server_name.text().to_string(), 35 | username: username.text().to_string(), 36 | password: password.text().to_string(), 37 | totp: totp.text().to_string() 38 | }))); 39 | }), 40 | ); 41 | 42 | server_name.connect_changed(glib::clone!(@weak server_name, @weak username, @weak password, @weak totp, @weak submit_button => move |_| login_util::check_responses(&[&server_name, &username, &password.into(), &totp], &submit_button))); 43 | username.connect_changed(glib::clone!(@weak server_name, @weak username, @weak password, @weak totp, @weak submit_button => move |_| login_util::check_responses(&[&server_name, &username, &password.into(), &totp], &submit_button))); 44 | password.connect_changed(glib::clone!(@weak server_name, @weak username, @weak password, @weak totp, @weak submit_button => move |_| login_util::check_responses(&[&server_name, &username, &password.into(), &totp], &submit_button))); 45 | totp.connect_changed(glib::clone!(@weak server_name, @weak username, @weak password, @weak totp, @weak submit_button => move |_| login_util::check_responses(&[&server_name, &username, &password.into(), &totp], &submit_button))); 46 | 47 | (sections, submit_button) 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | > [!WARNING] 2 | > makedeb is currently unmaintained: https://hunterwittenborn.com/blog/stepping-back-from-open-source/ 3 | 4 | # Celeste 5 | 6 | 7 | 8 | Celeste is a GUI file synchronization client that can connect to virtually any cloud provider. 9 | 10 | - Backed by [rclone](https://rclone.org/), giving you a reliable and battle-tested way to sync your files anywhere 11 | - Written with GTK4 and Libadwaita, giving Celeste a native look and feel on your desktop 12 | - Written in Rust, making Celeste ***blazingly fast*** to use 13 | 14 | ![](/assets/main-window.png) 15 | 16 | ## Features 17 | - Two-way sync 18 | - Asking what to do when a local and remote file have both been updated since last sync 19 | - Ability to exclude files/folders from sync 20 | - Connecting to multiple cloud providers at the same time 21 | 22 | ## Supported cloud providers 23 | Celeste can currently connect to the following cloud providers: 24 | - Dropbox 25 | - Google Drive 26 | - Nextcloud 27 | - Owncloud 28 | - pCloud 29 | - Proton Drive 30 | - WebDAV 31 | 32 | ## Installation 33 | Celeste can be installed via the methods listed below: 34 | 35 | ### Flatpak 36 | Celeste is available on [Flathub](https://flathub.org/apps/details/com.hunterwittenborn.Celeste). First make sure you have [set up Flatpak](https://flatpak.org/setup/) on your system, and then run the following: 37 | 38 | ```sh 39 | flatpak install flathub com.hunterwittenborn.Celeste 40 | ``` 41 | 42 | ### Snap 43 | Celeste is available on the [Snap Store](https://snapcraft.io/celeste), which can be installed on any system that has Snap installed. 44 | 45 | ```sh 46 | snap install celeste 47 | ``` 48 | 49 | ### Prebuilt-MPR (Debian/Ubuntu) 50 | If you're on Ubuntu 22.10 or later, you can install Celeste from the Prebuilt-MPR. First make sure [the Prebuilt-MPR is set up](https://docs.makedeb.org/prebuilt-mpr/getting-started/) on your system, and then run the following: 51 | 52 | ```sh 53 | sudo apt install celeste 54 | ``` 55 | 56 | ## Support 57 | Celeste has multiple communication rooms available if you need assistance, want to talk about the project, or to just hang around with some fellow users: 58 | - Discord: https://discord.gg/FtNhPepvj7 59 | - Matrix: https://matrix.to/#/#celeste:gnome.org 60 | 61 | **Bugs and features can be discussed in the rooms if you feel like there's information that should be talked about, but such should ultimately fall into the [issue tracker](https://github.com/hwittenborn/celeste/issues).** 62 | 63 | ## Contributing 64 | Instructions still largely need to be written up - if you'd like to help with that, feel free to submit a PR! 65 | 66 | ### Translating 67 | Celeste uses [Weblate](https://weblate.org) to manage translations. See if you'd like to assist in translating. 68 | -------------------------------------------------------------------------------- /src/login/webdav.rs: -------------------------------------------------------------------------------- 1 | //! The data for a WebDAV Rclone config. 2 | use super::{login_util, nextcloud::NextcloudConfig, owncloud::OwncloudConfig, ServerType}; 3 | use crate::mpsc::Sender; 4 | use adw::{ 5 | gtk::{glib, Button}, 6 | prelude::*, 7 | ApplicationWindow, EntryRow, 8 | }; 9 | 10 | pub enum WebDavType { 11 | Nextcloud, 12 | Owncloud, 13 | WebDav, 14 | } 15 | 16 | #[derive(Clone, Debug, Default)] 17 | pub struct WebDavConfig { 18 | pub server_name: String, 19 | pub server_url: String, 20 | pub username: String, 21 | pub password: String, 22 | } 23 | 24 | impl super::LoginTrait for WebDavConfig { 25 | fn get_sections( 26 | _window: &ApplicationWindow, 27 | sender: Sender>, 28 | ) -> (Vec, Button) { 29 | Self::webdav_sections(sender, WebDavType::WebDav) 30 | } 31 | } 32 | 33 | impl WebDavConfig { 34 | pub fn webdav_sections( 35 | sender: Sender>, 36 | webdav_type: WebDavType, 37 | ) -> (Vec, Button) { 38 | let mut sections: Vec = vec![]; 39 | 40 | let server_name = login_util::server_name_input(); 41 | let server_url = login_util::server_url_input(match webdav_type { 42 | WebDavType::Nextcloud | WebDavType::Owncloud => true, 43 | WebDavType::WebDav => false, 44 | }); 45 | let username = login_util::username_input(); 46 | let password = login_util::password_input(); 47 | let submit_button = login_util::submit_button(); 48 | 49 | sections.push(server_name.clone()); 50 | sections.push(server_url.clone()); 51 | sections.push(username.clone()); 52 | sections.push(password.clone().into()); 53 | 54 | submit_button.connect_clicked( 55 | glib::clone!(@weak server_name, @weak server_url, @weak username, @weak password => move |_| { 56 | // Nextcloud/Owncloud server types have everything after 'remote.php' stripped, so 57 | // add it back here. 58 | let formatted_nextcloud_url = format!("{}/remote.php/dav/files/{}", server_url.text(), username.text()); 59 | 60 | let server_type = match webdav_type { 61 | WebDavType::Nextcloud => ServerType::Nextcloud(NextcloudConfig { 62 | server_name: server_name.text().to_string(), 63 | server_url: formatted_nextcloud_url, 64 | username: username.text().to_string(), 65 | password: password.text().to_string(), 66 | }), 67 | WebDavType::Owncloud => ServerType::Owncloud(OwncloudConfig { 68 | server_name: server_name.text().to_string(), 69 | server_url: formatted_nextcloud_url, 70 | username: username.text().to_string(), 71 | password: password.text().to_string(), 72 | }), 73 | WebDavType::WebDav => ServerType::WebDav(WebDavConfig{ 74 | server_name: server_name.text().to_string(), 75 | server_url: server_url.text().to_string(), 76 | username: username.text().to_string(), 77 | password: password.text().to_string(), 78 | }) 79 | }; 80 | sender.send(Some(server_type)); 81 | }), 82 | ); 83 | 84 | server_name.connect_changed(glib::clone!(@weak server_name, @weak server_url, @weak username, @weak password, @weak submit_button => move |_| login_util::check_responses(&[&server_name, &server_url, &username, &password.into()], &submit_button))); 85 | server_url.connect_changed(glib::clone!(@weak server_name, @weak server_url, @weak username, @weak password, @weak submit_button => move |_| login_util::check_responses(&[&server_name, &server_url, &username, &password.into()], &submit_button))); 86 | username.connect_changed(glib::clone!(@weak server_name, @weak server_url, @weak username, @weak password, @weak submit_button => move |_| login_util::check_responses(&[&server_name, &server_url, &username, &password.into()], &submit_button))); 87 | password.connect_changed(glib::clone!(@weak server_name, @weak server_url, @weak username, @weak password, @weak submit_button => move |_| login_util::check_responses(&[&server_name, &server_url, &username, &password.into()], &submit_button))); 88 | 89 | (sections, submit_button) 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /assets/context/com.hunterwittenborn.Celeste.CelesteTrayDone-symbolic.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | -------------------------------------------------------------------------------- /assets/context/com.hunterwittenborn.Celeste.CelesteTrayDisconnected-symbolic.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | -------------------------------------------------------------------------------- /assets/context/com.hunterwittenborn.Celeste.CelesteTrayWarning-symbolic.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | -------------------------------------------------------------------------------- /assets/context/com.hunterwittenborn.Celeste.CelesteTrayPaused-symbolic.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | -------------------------------------------------------------------------------- /assets/context/com.hunterwittenborn.Celeste.CelesteTrayLoading-symbolic.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | -------------------------------------------------------------------------------- /assets/context/com.hunterwittenborn.Celeste.CelesteTraySyncing-symbolic.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | -------------------------------------------------------------------------------- /assets/com.hunterwittenborn.Celeste-regular.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | All notable changes to this project will be documented in this file. 3 | 4 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), 5 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). 6 | 7 | ## [Unreleased] 8 | 9 | ## [0.8.3] - 2024-06-22 10 | ### Fixed 11 | - Fix parsing error in MetaInfo file. 12 | 13 | ## [0.8.2] - 2024-06-21 14 | ### Fixed 15 | - Update dependencies to fix build error. 16 | 17 | ## [0.8.1] - 2023-11-18 18 | ### Changed 19 | - Added more keywords to Celeste's desktop entry. 20 | 21 | ### Fixed 22 | - Fixed Google Drive authentication window not showing when using older rclone versions. 23 | 24 | ## [0.8.0] - 2023-10-23 25 | ### Changed 26 | - Added functionality to only run sync checks when files are changed. 27 | 28 | ## [0.7.0] - 2023-10-07 29 | ### Changed 30 | - Remove reliance on GTK3 and libappindicator. 31 | - Make tray functionality part of main application, instead of being a separate app. 32 | - Combine all application functionality into a singular crate. 33 | 34 | ### Fixed 35 | - Fixed incorrect number of errors being reported in tray. 36 | - Fixed bug where tray icons would never change. 37 | 38 | ## [0.6.0] - 2023-10-05 39 | ### Added 40 | - Added Proton Drive support. 41 | 42 | ### Fixed 43 | - Fixed missing `description` tags in metainfo's `releases` section. 44 | 45 | ## [0.5.8] - 2023-09-16 46 | ### Added 47 | - Added release notes to about page. 48 | 49 | ## [0.5.7] - 2023-09-15 50 | ### Fixed 51 | - Removed duplicate releases in AppStream metadata file. 52 | 53 | ## [0.5.6] - 2023-09-14 54 | ### Changed 55 | - Changed to new application icons. 56 | - Added credits to about page. 57 | 58 | ## [0.5.5] - 2023-08-09 59 | ### Fixed 60 | - Update `Cargo.toml` and `Cargo.lock` to fix `arm64` compile error. 61 | 62 | ## [0.5.4] - 2023-07-24 63 | ### Fixed 64 | - Updated `Cargo.lock` to fix compile error. 65 | 66 | ## [0.5.3] - 2023-06-07 67 | ### Fixed 68 | - Updated `Cargo.lock` to fix compile error. 69 | 70 | ## [0.5.2] - 2023-03-27 71 | ### Fixed 72 | - Fixed more issues in automated Flathub packaging. 73 | 74 | ## [0.5.1] - 2023-03-27 75 | ### Fixed 76 | - Fixed automated Flathub packaging. 77 | 78 | ## [0.5.0] - 2023-03-27 79 | ### Added 80 | - Added support for translations. 81 | 82 | ### Changed 83 | - Fixed loading times when adding new sync directories. 84 | - Made autocompletions in sync directory additions more dynamic. 85 | 86 | ### Fixed 87 | - Fixed loading times when adding remotes with high storage usage. 88 | - Fixed freeze when ports needed by `rclone authorize` are already being used. 89 | 90 | ## [0.4.6] - 2023-02-24 91 | ### Fixed 92 | - Fixed HTML elements in metainfo release descriptions. 93 | 94 | ## [0.4.5] - 2023-02-24 95 | ### Fixed 96 | - Fixed location of metainfo file. 97 | 98 | ## [0.4.4] - 2023-02-23 99 | ### Fixed 100 | - Add `com.hunterwittenborn.Celeste.metainfo.xml` to packaging. 101 | - Fixed Google Drive authentication for Google's branding requirements. 102 | 103 | ## [0.4.3] - 2023-02-23 104 | ### Fixed 105 | - Removed commented line in `justfile`. 106 | 107 | ## [0.4.2] - 2023-02-22 108 | ### Changed 109 | - Fixed panic when previously synced local dir no longer exists. 110 | - Fixed extra padding in certain windows. 111 | - Fixed recorded remote items incorrectly starting with `/` in Celeste's database. 112 | - Fixed long windows when multiple errors are present. 113 | 114 | ## [0.4.1] - 2023-02-15 115 | ### Changed 116 | - Removed no `:` requirement in username and password login fields. 117 | 118 | ## [0.4.0] - 2023-02-08 119 | ### Added 120 | - Added support for pCloud. 121 | 122 | ## [0.3.6] - 2023-02-08 123 | ### Changed 124 | - Fixed slash suffixes in local directory causing a crash in sync directory dialog. 125 | - Fixed remote directories not being placed correctly on the local system. 126 | 127 | ## [0.3.5] - 2023-02-04 128 | ### Changed 129 | - Fixed behavior of tray icon closing when main application crashes inside of Flatpak. 130 | - Fixed wordage on a couple CLI warning messages. 131 | 132 | ## [0.3.4] - 2023-02-02 133 | ### Changed 134 | - Closed tray icon when main application crashes. 135 | - Fixed new servers not showing up after all have been removed. 136 | - Fixed crash caused by incorrect server name being registered for Nextcloud and Owncloud servers. 137 | 138 | ## [0.3.3] - 2023-02-02 139 | ### Changed 140 | - Improved titlebars in main application window. 141 | 142 | ## [0.3.2] - 2023-02-02 143 | ### Changed 144 | - Added better error handing when sending DBus messages. 145 | 146 | ## [0.3.0] - 2023-02-02 147 | ### Added 148 | - Added Owncloud storage type. 149 | - Added ability to start up in the background. 150 | 151 | ## [0.2.0] - 2023-02-02 152 | ### Added 153 | - Added Google Drive storage type. 154 | 155 | ### Changed 156 | - Fixed DBus connection names in Snapcraft config. 157 | 158 | ## [0.1.12] - 2023-02-01 159 | ### Changed 160 | - Fixed file names in `justfile`. 161 | 162 | ## [0.1.11] - 2023-02-01 163 | ### Changed 164 | - Fixed namespace used for symbolic icons and DBus connections. 165 | 166 | ## [0.1.10] - 2023-02-01 167 | ### Changed 168 | - Fixed location of symbolic icons during installation. 169 | 170 | ## [0.1.9] - 2023-02-01 171 | ### Added 172 | - Added Snap packaging 173 | 174 | ## [0.1.8] - 2023-01-07 175 | ### Added 176 | - Added tray icon. 177 | 178 | ## [0.1.7] - 2022-12-30 179 | ### Changed 180 | - Fixed vertical alignment of text in file/folder exclusion section. 181 | - Improved method for finding running Celeste instances. 182 | 183 | ## [0.1.6] - 2022-12-30 184 | ### Changed 185 | - Fixed missing icon on about page. 186 | - Updated progress text to show individual file checks. 187 | - Fixed missing popover button on main screen. 188 | - Fixed main screen window not closing after being reopened. 189 | 190 | ## [0.1.5] - 2022-12-30 191 | ### Changed 192 | - Fixed panic on launch from missing directory. 193 | 194 | ## [0.1.4] - 2022-12-29 195 | ### Changed 196 | - Added very hacky workaround to finally fix linker usage in AppImage. 197 | 198 | ## [0.1.3] - 2022-12-29 199 | ### Changed 200 | - Fixed more linker usage in AppImage. 201 | 202 | ## [0.1.2] - 2022-12-29 203 | ### Changed 204 | - Fixed linker usage and missing dependencies in AppImage. 205 | 206 | ## [0.1.1] - 2022-12-29 207 | ### Changed 208 | - Fixed MPR packaging. 209 | 210 | ## [0.1.0] - 2022-12-29 211 | First release! 🥳 212 | -------------------------------------------------------------------------------- /justfile: -------------------------------------------------------------------------------- 1 | set positional-arguments 2 | 3 | build: 4 | cargo build --release 5 | 6 | install: 7 | install -Dm 755 target/release/celeste "{{ env_var('DESTDIR') }}/usr/bin/celeste" 8 | install -Dm 644 assets/com.hunterwittenborn.Celeste.desktop "{{ env_var('DESTDIR') }}/usr/share/applications/com.hunterwittenborn.Celeste.desktop" 9 | install -Dm 644 assets/com.hunterwittenborn.Celeste-regular.svg "{{ env_var('DESTDIR') }}/usr/share/icons/hicolor/scalable/apps/com.hunterwittenborn.Celeste.svg" 10 | install -Dm 644 assets/context/com.hunterwittenborn.Celeste.CelesteTrayLoading-symbolic.svg "{{ env_var('DESTDIR') }}/usr/share/icons/hicolor/symbolic/apps/com.hunterwittenborn.Celeste.CelesteTrayLoading-symbolic.svg" 11 | install -Dm 644 assets/context/com.hunterwittenborn.Celeste.CelesteTraySyncing-symbolic.svg "{{ env_var('DESTDIR') }}/usr/share/icons/hicolor/symbolic/apps/com.hunterwittenborn.Celeste.CelesteTraySyncing-symbolic.svg" 12 | install -Dm 644 assets/context/com.hunterwittenborn.Celeste.CelesteTrayWarning-symbolic.svg "{{ env_var('DESTDIR') }}/usr/share/icons/hicolor/symbolic/apps/com.hunterwittenborn.Celeste.CelesteTrayWarning-symbolic.svg" 13 | install -Dm 644 assets/context/com.hunterwittenborn.Celeste.CelesteTrayDone-symbolic.svg "{{ env_var('DESTDIR') }}/usr/share/icons/hicolor/symbolic/apps/com.hunterwittenborn.Celeste.CelesteTrayDone-symbolic.svg" 14 | install -Dm 644 assets/com.hunterwittenborn.Celeste.metainfo.xml "{{ env_var('DESTDIR') }}/usr/share/metainfo/com.hunterwittenborn.Celeste.metainfo.xml" 15 | 16 | clippy: 17 | cargo clippy -- -D warnings 18 | 19 | get-version: 20 | #!/usr/bin/env bash 21 | source makedeb/PKGBUILD 22 | echo "${pkgver}" 23 | 24 | update-versions: 25 | #!/usr/bin/env bash 26 | set -euo pipefail 27 | version="$(just get-version)" 28 | sed -i "s|^version = .*|version = \"${version}\"|" Cargo.toml 29 | 30 | date="$(cat CHANGELOG.md | grep "^## \[${version}\]" | grep -o '[^ ]*$')" 31 | notes="$(parse-changelog CHANGELOG.md "${version}")" 32 | just update-metainfo 33 | 34 | # Temporary fix for XML data not being inlined (such as with `metainfo_license` and `project_license`). 35 | dasel -f assets/com.hunterwittenborn.Celeste.metainfo.xml > tmp.xml 36 | mv tmp.xml assets/com.hunterwittenborn.Celeste.metainfo.xml 37 | 38 | update-metainfo: 39 | #!/usr/bin/env python3 40 | import sys 41 | import markdown 42 | import bs4 43 | import re 44 | 45 | from bs4 import BeautifulSoup, NavigableString 46 | from bs4.formatter import HTMLFormatter 47 | 48 | # Parse the metainfo and Changelog into BeautifulSoup objects. 49 | metainfo_path = "assets/com.hunterwittenborn.Celeste.metainfo.xml" 50 | changelog_path = "CHANGELOG.md" 51 | 52 | text = open(metainfo_path).read() 53 | changelog_md = open(changelog_path).read() 54 | changelog_html = markdown.markdown(changelog_md) 55 | 56 | soup = BeautifulSoup(text, features="xml") 57 | changelog_soup = BeautifulSoup(changelog_html, "html.parser") 58 | releases = [] 59 | 60 | # The changelog is in a flat list of HTML tags. Group them into dicts of 61 | # `version: [html-elements]` for easier usage. 62 | versions = {} 63 | current_version = None 64 | 65 | for tag in changelog_soup: 66 | # We don't need empty lines, so ignore them. 67 | if tag.text == "\n": 68 | continue 69 | # Version tags are '##' in markdown (i.e. an '

'). 70 | elif tag.name == "h2" and tag.text != "[Unreleased]": 71 | version = re.search("[^[][0-9.]*", tag.text).group(0) 72 | date = re.search("[^ ]*$", tag.text).group(0) 73 | 74 | release_soup = BeautifulSoup("", "html.parser").release 75 | release_soup["date"] = date 76 | release_soup["version"] = version 77 | 78 | versions[version] = release_soup 79 | current_version = version 80 | # If we aren't on a version tag and haven't gotten any version yet, 81 | # we're dealing with content before the first version tag and we 82 | # should ignore it. 83 | elif len(versions) == 0: 84 | continue 85 | # Appstream doesn't support headers in descriptions, so format them as `

` tags. 86 | elif tag.name == "h3": 87 | match tag.text: 88 | case "Added": 89 | header = "New features in this release:" 90 | case "Changed": 91 | header = "Changes in this release:" 92 | case "Fixed": 93 | header = "Fixes in this release:" 94 | case _: 95 | raise Exception(f"Unknown change type: `{tag.text}`") 96 | 97 | versions[version].description.append(BeautifulSoup(f"

{header}

", "html.parser")) 98 | # Otherwise we're adding to the existing version. 99 | else: 100 | versions[version].description.append(tag) 101 | 102 | # Clear out the existing versions and write the new ones. 103 | soup.component.releases.clear() 104 | 105 | for release in versions.values(): 106 | soup.component.releases.append(release) 107 | 108 | output = soup.prettify(formatter=HTMLFormatter(indent=4)) 109 | open(metainfo_path, "w").write(output) 110 | 111 | update-translations: 112 | xtr src/main.rs --copyright-holder 'Hunter Wittenborn ' -o /dev/stdout --package-name 'Celeste' --package-version "$(just get-version)" > po/com.hunterwittenborn.Celeste.pot 113 | 114 | # Create the Snap using an already build copy of Celeste. This currently requires you to be running on Ubuntu 22.10 or newer. 115 | create-host-snap: 116 | #!/usr/bin/env bash 117 | set -euo pipefail 118 | 119 | cd "$(git rev-parse --show-toplevel)" 120 | host_snapcraft_yml="$(cat snap/snapcraft.yaml | grep -Ev 'source-type: |override-build: |just build')" 121 | tmpdir="$(mktemp -d)" 122 | 123 | find ./ -mindepth 1 -maxdepth 1 -not -path './target' -exec cp '{}' "${tmpdir}/{}" -R \; 124 | mkdir -p "${tmpdir}/target/release" 125 | cp target/debug/celeste "${tmpdir}/target/release" 126 | 127 | cd "${tmpdir}" 128 | echo "${host_snapcraft_yml}" > snap/snapcraft.yaml 129 | snapcraft -v 130 | 131 | cd - 132 | cp "${tmpdir}/"*.snap ./ 133 | rm "${tmpdir}" -rf 134 | -------------------------------------------------------------------------------- /src/login/login_util.rs: -------------------------------------------------------------------------------- 1 | //! A collection of helper functions for generating login UIs. 2 | use crate::rclone::{self}; 3 | use adw::{ 4 | glib, 5 | gtk::{Align, Button, CheckButton, Label}, 6 | prelude::*, 7 | EntryRow, PasswordEntryRow, 8 | }; 9 | 10 | use regex::Regex; 11 | use url::Url; 12 | 13 | /// Get the input for the server name. 14 | pub fn server_name_input() -> EntryRow { 15 | let input = EntryRow::builder().title(&tr::tr!("Name")).build(); 16 | input.connect_changed(|input| { 17 | let text = input.text(); 18 | 19 | // Get a list of already existing config names. 20 | let existing_remotes: Vec = rclone::get_remotes() 21 | .iter() 22 | .map(|config| config.remote_name()) 23 | .collect(); 24 | 25 | if existing_remotes.contains(&text.to_string()) { 26 | input.add_css_class("error"); 27 | input.set_tooltip_text(Some(&tr::tr!("Name already exists."))); 28 | } else if !Regex::new(r"^[0-9a-zA-Z_.][0-9a-zA-Z_. -]*[0-9a-zA-Z_.-]$").unwrap().is_match(&text) { 29 | let err_msg = tr::tr!("Invalid name. Names must:\n- Only contain numbers, letters, '_', '-', '.', and spaces\n- Not start with '-' or a space\n- Not end with a space"); 30 | input.add_css_class("error"); 31 | input.set_tooltip_text(Some(&err_msg)); 32 | } else { 33 | input.remove_css_class("error"); 34 | input.set_tooltip_text(None); 35 | } 36 | }); 37 | 38 | input 39 | } 40 | 41 | /// Get the input for the server URL. 42 | pub fn server_url_input(disallow_nextcloud_suffix: bool) -> EntryRow { 43 | let input = EntryRow::builder().title(&tr::tr!("Server URL")).build(); 44 | input.connect_changed(move |input| { 45 | let text = input.text(); 46 | let url = Url::parse(&text); 47 | 48 | if let Err(err) = url { 49 | let err_string = tr::tr!("Invalid server URL ({}).", err); 50 | input.add_css_class("error"); 51 | input.set_tooltip_text(Some(&err_string)); 52 | return; 53 | } 54 | 55 | let url = url.unwrap(); 56 | if !url.has_host() { 57 | input.add_css_class("error"); 58 | input.set_tooltip_text(Some(&tr::tr!("Invalid server URL (no domain specified)."))); 59 | } else if url.password().is_some() { 60 | input.add_css_class("error"); 61 | input.set_tooltip_text(Some(&tr::tr!( 62 | "Invalid server URL (password was specified)." 63 | ))); 64 | } else if !["http", "https"].contains(&url.scheme()) { 65 | let err_string = tr::tr!( 66 | "Invalid server URL(unknown server scheme {}).", 67 | url.scheme() 68 | ); 69 | input.add_css_class("error"); 70 | input.set_tooltip_text(Some(&err_string)); 71 | } else if disallow_nextcloud_suffix && url.path().contains("/remote.php/") { 72 | let text_to_remove = Regex::new(r"/remote\.php/.*") 73 | .unwrap() 74 | .find(url.path()) 75 | .unwrap() 76 | .as_str() 77 | .to_string(); 78 | let err_string = tr::tr!("Don't specify '{}' as part of the URL.", text_to_remove); 79 | input.add_css_class("error"); 80 | input.set_tooltip_text(Some(&err_string)); 81 | } else { 82 | input.remove_css_class("error"); 83 | input.set_tooltip_text(None); 84 | } 85 | }); 86 | input 87 | } 88 | 89 | /// Get the input for usernames. 90 | pub fn username_input() -> EntryRow { 91 | EntryRow::builder().title(&tr::tr!("Username")).build() 92 | } 93 | 94 | /// Get the input for passwords. 95 | pub fn password_input() -> PasswordEntryRow { 96 | PasswordEntryRow::builder() 97 | .title(&tr::tr!("Password")) 98 | .build() 99 | } 100 | 101 | /// Get the input for TOTP/2FA codes. 102 | pub fn totp_input() -> EntryRow { 103 | let input = EntryRow::builder() 104 | .title(&tr::tr!("2FA Code")) 105 | .editable(false) 106 | .build(); 107 | input.connect_changed(move |input| { 108 | let text = input.text(); 109 | 110 | if text.chars().any(|c| !c.is_numeric()) { 111 | input.add_css_class("error"); 112 | input.set_tooltip_text(Some(&tr::tr!( 113 | "The provided 2FA code is invalid (should only contain digits)." 114 | ))); 115 | } else if text.len() != 6 { 116 | input.add_css_class("error"); 117 | input.set_tooltip_text(Some(&tr::tr!( 118 | "The provided 2FA code is invalid (should be 6 digits long)." 119 | ))); 120 | } else { 121 | input.remove_css_class("error"); 122 | input.set_tooltip_text(None); 123 | } 124 | }); 125 | let check = CheckButton::new(); 126 | check.connect_toggled(glib::clone!(@weak input => move |check| { 127 | let active = check.is_active(); 128 | input.set_editable(active); 129 | 130 | if !active { 131 | input.set_text(""); 132 | input.remove_css_class("error"); 133 | input.set_tooltip_text(None); 134 | } 135 | })); 136 | input.add_prefix(&check); 137 | input 138 | } 139 | 140 | /// Get the login button. 141 | pub fn submit_button() -> Button { 142 | let label = Label::builder().label(&tr::tr!("Log in")).build(); 143 | let button = Button::builder() 144 | .child(&label) 145 | .halign(Align::End) 146 | .margin_top(10) 147 | .css_classes(vec!["login-window-submit-button".to_string()]) 148 | .build(); 149 | // Grey out the button initially so it can't be until items are validated. 150 | button.set_sensitive(false); 151 | button 152 | } 153 | 154 | /// Grey out the password button if any of the specified fields have errors or 155 | /// are empty. This ignores any entries that aren't sensitive. 156 | pub fn check_responses(responses: &[&EntryRow], submit_button: &Button) { 157 | let mut no_errors = true; 158 | 159 | for resp in responses { 160 | if resp.is_editable() && (resp.has_css_class("error") || resp.text().is_empty()) { 161 | no_errors = false; 162 | } 163 | } 164 | 165 | submit_button.set_sensitive(no_errors); 166 | } 167 | -------------------------------------------------------------------------------- /.github/workflows/deploy.yml: -------------------------------------------------------------------------------- 1 | name: Deploy Celeste 2 | on: 3 | push: 4 | branches: 5 | - main 6 | tags-ignore: 7 | - '**' 8 | permissions: 9 | id-token: write 10 | contents: read 11 | 12 | jobs: 13 | create-release: 14 | runs-on: ubuntu-latest 15 | if: "!contains(github.event.head_commit.message, 'skip-ci: create-release')" 16 | steps: 17 | - name: Checkout Git repository 18 | uses: actions/checkout@v3 19 | - name: Setup makedeb APT repositories 20 | uses: makedeb/setup-makedeb@main 21 | with: 22 | makedeb-repo: false 23 | pbmpr-repo: true 24 | - name: Install needed APT packages 25 | run: sudo apt-get install just parse-changelog -y 26 | - name: Create release 27 | run: | 28 | version="$(just get-version)" 29 | release_notes="$(parse-changelog CHANGELOG.md "${version}")" 30 | gh release create "v${version}" --title "v${version}" --target "${GITHUB_SHA}" -n "${release_notes}" 31 | env: 32 | GITHUB_TOKEN: ${{ secrets.GH_TOKEN_CUSTOM }} 33 | deploy-mpr: 34 | runs-on: ubuntu-latest 35 | needs: [create-release] 36 | if: "!failure() && !contains(github.event.head_commit.message, 'skip-ci: deploy-mpr')" 37 | steps: 38 | - name: Checkout Git repository 39 | uses: actions/checkout@v3 40 | - name: Setup makedeb APT repositories 41 | uses: makedeb/setup-makedeb@main 42 | - name: Publish MPR package 43 | run: | 44 | # Install our CI-utils package. 45 | curl -Ls "https://shlink.hunterwittenborn.com/ci-utils" | sudo bash - 46 | 47 | # Set up our SSH config. 48 | mkdir "${HOME}/.ssh" 49 | echo -e "Host mpr.makedeb.org\n Hostname mpr.makedeb.org\n IdentityFile ${HOME}/.ssh/ssh_key" > "${HOME}/.ssh/config" 50 | echo "${SSH_KEY}" > "${HOME}/.ssh/ssh_key" 51 | 52 | # Set up the MPR's SSH fingerprint in our local config. 53 | MPR_SSH_KEY="$(curl 'https://mpr.makedeb.org/api/meta' | jq -r '.ssh_key_fingerprints.ECDSA')" 54 | ( 55 | export SSH_HOST='mpr.makedeb.org' 56 | export SSH_EXPECTED_FINGERPRINT="${MPR_SSH_KEY}" 57 | export SET_PERMS=true 58 | get-ssh-key 59 | ) 60 | 61 | # Set up our Git user. 62 | git config --global user.name 'Kavplex Bot' 63 | git config --global user.email 'kavplex@hunterwittenborn.com' 64 | 65 | # Clone the MPR repository for Celeste, and update it. 66 | cd makedeb/ 67 | git clone 'ssh://mpr@mpr.makedeb.org/celeste' 68 | 69 | cp PKGBUILD celeste/ 70 | cd celeste/ 71 | makedeb --print-srcinfo | tee .SRCINFO 72 | 73 | source PKGBUILD 74 | git add . 75 | git commit -m "Bump version to '${pkgver}-${pkgrel}'" 76 | git push 77 | env: 78 | SSH_KEY: ${{ secrets.SSH_KEY }} 79 | deploy-snap: 80 | runs-on: ubuntu-latest 81 | needs: [create-release] 82 | if: "!failure() && !contains(github.event.head_commit.message, 'skip-ci: deploy-snap')" 83 | steps: 84 | - name: Checkout Git repository 85 | uses: actions/checkout@v3 86 | - name: Setup makedeb APT repositories 87 | uses: makedeb/setup-makedeb@main 88 | with: 89 | pbmpr-repo: true 90 | - name: Build Celeste Snap 91 | uses: snapcore/action-build@v1 92 | id: snapcraft-build 93 | with: 94 | snapcraft-args: "-v" 95 | - name: Upload and release Celeste Snap 96 | uses: snapcore/action-publish@v1 97 | with: 98 | snap: ${{ steps.snapcraft-build.outputs.snap }} 99 | release: stable 100 | env: 101 | SNAPCRAFT_STORE_CREDENTIALS: ${{ secrets.SNAPCRAFT_STORE_CREDENTIALS }} 102 | deploy-flathub: 103 | runs-on: ubuntu-latest 104 | needs: [deploy-snap] 105 | if: "!failure() && !contains(github.event.head_commit.message, 'skip-ci: deply-flathub')" 106 | steps: 107 | - name: Checkout Flathub Celeste Git repository 108 | uses: actions/checkout@v3 109 | with: 110 | repository: flathub/com.hunterwittenborn.Celeste 111 | path: com.hunterwittenborn.Celeste 112 | token: ${{ secrets.GH_TOKEN_CUSTOM }} 113 | - name: Setup makedeb APT repositories 114 | uses: makedeb/setup-makedeb@main 115 | - name: Update Flathub package 116 | run: | 117 | # Set up our Git user. 118 | git config --global user.name 'Kavplex Bot' 119 | git config --global user.email 'kavplex@hunterwittenborn.com' 120 | 121 | # Get the current snap revision. 122 | snap download celeste 123 | snap_revision="$(echo celeste_*.snap | sed -e 's|^celeste_||' -e 's|\.snap$||')" 124 | snap_sha256sum="$(sha256sum celeste_*.snap | awk '{print $1}')" 125 | 126 | # Update the version info for the Flatpak. 127 | cd com.hunterwittenborn.Celeste 128 | git checkout -b "version/${snap_revision}" 129 | sed -i -e "s|a9zAmHVl4doDwIGkptVyA7VI7fMlPPpE_[0-9]*\.snap|a9zAmHVl4doDwIGkptVyA7VI7fMlPPpE_${snap_revision}.snap|" -e "s|sha256: .*|sha256: '${snap_sha256sum}'|" com.hunterwittenborn.Celeste.yml 130 | git add . && git commit -m "Update package version" 131 | git push --set-upstream origin "version/${snap_revision}" 132 | 133 | # Create the PR and wait until we can merge it. 134 | gh pr create --title "Update package version" --body '' 135 | pr_id="$(gh pr list --json headRefName,headRepositoryOwner,number -q ".[] | select((.headRefName==\"version/${snap_revision}\") and .headRepositoryOwner.login==\"flathub\").number")" 136 | 137 | while true; do 138 | comments="$(gh pr view -c)" 139 | 140 | if echo "${comments}" | grep -q failed; then 141 | echo "The build failed! Please investigate manually." 142 | exit 1 143 | elif echo "${comments}" | grep -q successful; then 144 | # Even after a successful build, we have to wait 145 | # a bit longer for Buildbot to allow us to merge. 146 | status='BLOCKED' 147 | 148 | while [[ "${status}" == 'BLOCKED' ]]; do 149 | echo 'Waiting for @flathubbot to allow merging...' 150 | sleep 1 151 | status="$(gh pr list --json number,mergeStateStatus -q ".[] | select(.number==${pr_id}).mergeStateStatus")" 152 | done 153 | 154 | echo "The build succeeded! Merging the PR..." 155 | gh pr merge "${pr_id}" --merge 156 | exit 157 | fi 158 | 159 | echo "Waiting for @flathubbot to report status checks..." 160 | sleep 1 161 | done 162 | env: 163 | GITHUB_TOKEN: ${{ secrets.GH_TOKEN_CUSTOM }} 164 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | #![feature(let_chains)] 2 | #![feature(arc_unwrap_or_clone)] 3 | #![feature(panic_info_message)] 4 | #![feature(async_closure)] 5 | #![feature(trait_alias)] 6 | #![feature(exit_status_error)] 7 | 8 | pub mod about; 9 | pub mod entities; 10 | pub mod gtk_util; 11 | pub mod launch; 12 | pub mod login; 13 | pub mod migrations; 14 | pub mod mpsc; 15 | pub mod rclone; 16 | pub mod traits; 17 | pub mod tray; 18 | pub mod util; 19 | 20 | use adw::{ 21 | gtk::{self, gdk::Display, Align, Box, CssProvider, Label, Orientation, StyleContext}, 22 | prelude::*, 23 | Application, ApplicationWindow, HeaderBar, 24 | }; 25 | use clap::{Parser, Subcommand}; 26 | use serde_json::json; 27 | use std::{ 28 | env, 29 | io::{BufRead, BufReader}, 30 | process::{Command, Stdio}, 31 | thread, 32 | }; 33 | 34 | #[derive(Parser)] 35 | #[command(author, version, about, long_about = None)] 36 | struct Cli { 37 | #[command(subcommand)] 38 | command: Option, 39 | 40 | /// Whether to start in the background. 41 | #[arg(long)] 42 | background: bool, 43 | } 44 | 45 | #[derive(Subcommand)] 46 | enum Commands { 47 | RunGui { 48 | /// Whether to start in the background. 49 | #[arg(long)] 50 | background: bool, 51 | }, 52 | } 53 | 54 | fn main() { 55 | // Initialize GTK. 56 | gtk::init().unwrap(); 57 | 58 | // Configure Rclone. 59 | let mut config = util::get_config_dir(); 60 | config.push("rclone.conf"); 61 | librclone::initialize(); 62 | librclone::rpc("config/setpath", json!({ "path": config }).to_string()).unwrap(); 63 | 64 | // Load our CSS. 65 | let provider = CssProvider::new(); 66 | provider.load_from_data(include_bytes!(concat!(env!("OUT_DIR"), "/style.css"))); 67 | 68 | StyleContext::add_provider_for_display( 69 | &Display::default().unwrap(), 70 | &provider, 71 | gtk::STYLE_PROVIDER_PRIORITY_APPLICATION, 72 | ); 73 | 74 | // Get the application. 75 | let app = Application::builder().application_id(util::APP_ID).build(); 76 | 77 | // Due to GTK working in Rust via Rust's FFI, panics don't appear to be able to 78 | // be captured (this hasn't been confirmed behavior, it's just what I've 79 | // observed). Panics would like to be captured when they're encountered though, 80 | // so we relaunch this program in a subprocess and capture any errors from 81 | // there. 82 | let cli = Cli::parse(); 83 | if let Some(cmd) = cli.command { 84 | match cmd { 85 | Commands::RunGui { background } => { 86 | // Start up the application. 87 | app.connect_activate(move |app| { 88 | if app.is_remote() { 89 | app.activate(); 90 | return; 91 | } 92 | 93 | let windows = app.windows(); 94 | if windows.is_empty() { 95 | launch::launch(app, background); 96 | } else { 97 | windows.iter().for_each(|window| window.show()); 98 | } 99 | }); 100 | 101 | app.run_with_args::<&str>(&[]); 102 | } 103 | } 104 | } else { 105 | // Set `RUST_BACKTRACE` so we get a better backtrace for reporting. 106 | env::set_var("RUST_BACKTRACE", "1"); 107 | 108 | // Run the command and get the stderr, checking for a backtrace. 109 | let mut args = vec!["run-gui"]; 110 | if cli.background { 111 | args.push("--background"); 112 | } 113 | 114 | let mut command = Command::new(env::args().next().unwrap()) 115 | .args(args) 116 | .stdout(Stdio::piped()) 117 | .stderr(Stdio::piped()) 118 | .spawn() 119 | .unwrap(); 120 | let stdout_thread = thread::spawn(move || { 121 | let mut stdout = String::new(); 122 | let mut stdout_handle = command.stdout.as_mut().unwrap(); 123 | let stdout_reader = BufReader::new(&mut stdout_handle); 124 | 125 | for line in stdout_reader.lines() { 126 | let unwrapped_line = line.unwrap(); 127 | println!("{unwrapped_line}"); 128 | stdout.push_str(&unwrapped_line); 129 | stdout.push('\n'); 130 | } 131 | 132 | stdout 133 | }); 134 | let stderr_thread = thread::spawn(move || { 135 | let mut stderr = String::new(); 136 | let mut stderr_handle = command.stderr.as_mut().unwrap(); 137 | let stderr_reader = BufReader::new(&mut stderr_handle); 138 | 139 | for line in stderr_reader.lines() { 140 | let unwrapped_line = line.unwrap(); 141 | eprintln!("{unwrapped_line}"); 142 | stderr.push_str(&unwrapped_line); 143 | stderr.push('\n'); 144 | } 145 | 146 | stderr 147 | }); 148 | let _stdout = stdout_thread.join().unwrap(); 149 | let stderr = stderr_thread.join().unwrap(); 150 | 151 | let maybe_backtrace = { 152 | let mut backtrace = String::new(); 153 | let mut backtrace_found = false; 154 | 155 | for line in stderr.lines() { 156 | if backtrace_found && !line.contains("note: Some details are omitted") { 157 | backtrace.push_str(line); 158 | backtrace.push('\n'); 159 | } else if line.starts_with("thread 'main' panicked at") { 160 | backtrace.push_str(line); 161 | backtrace.push('\n'); 162 | backtrace_found = true; 163 | } 164 | } 165 | 166 | backtrace.pop(); // The extra newline at the end. 167 | 168 | if backtrace_found { 169 | Some(backtrace) 170 | } else { 171 | None 172 | } 173 | }; 174 | 175 | // Show the backtrace in the GUI if one was found. 176 | if let Some(backtrace) = maybe_backtrace { 177 | app.connect_activate(move |app| { 178 | let window = ApplicationWindow::builder() 179 | .application(app) 180 | .title(&util::get_title!("Unknown Error")) 181 | .build(); 182 | window.add_css_class("celeste-global-padding"); 183 | let sections = Box::builder() 184 | .orientation(Orientation::Vertical) 185 | .build(); 186 | sections.append(&HeaderBar::new()); 187 | let error_label = Label::builder() 188 | .label(&tr::tr!("Unknown Error")) 189 | .halign(Align::Start) 190 | .css_classes(vec!["heading".to_owned()]) 191 | .build(); 192 | sections.append(&error_label); 193 | 194 | let error_text = Label::builder() 195 | .label(&tr::tr!("An unknown error has occurred while running. This is an internal issue with Celeste and should be reported.\n\nThe following backtrace may help with debugging the issue - note that it may contain information such as login tokens/keys, so avoid posting the information publicly:")) 196 | .halign(Align::Start) 197 | .wrap(true) 198 | .xalign(0.0) 199 | .yalign(0.0) 200 | .build(); 201 | sections.append(&error_text); 202 | sections.append(>k_util::codeblock(&backtrace)); 203 | 204 | window.set_content(Some(§ions)); 205 | window.show(); 206 | }); 207 | 208 | app.run_with_args::<&str>(&[]); 209 | } 210 | } 211 | } 212 | -------------------------------------------------------------------------------- /po/gl.po: -------------------------------------------------------------------------------- 1 | # SOME DESCRIPTIVE TITLE. 2 | # Copyright (C) YEAR Hunter Wittenborn 3 | # This file is distributed under the same license as the Celeste package. 4 | # FIRST AUTHOR , YEAR. 5 | # 6 | msgid "" 7 | msgstr "" 8 | "Project-Id-Version: Celeste 0.8.1\n" 9 | "Report-Msgid-Bugs-To: \n" 10 | "POT-Creation-Date: 2023-11-19 01:22+0000\n" 11 | "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" 12 | "Last-Translator: Automatically generated\n" 13 | "Language-Team: none\n" 14 | "Language: gl\n" 15 | "MIME-Version: 1.0\n" 16 | "Content-Type: text/plain; charset=UTF-8\n" 17 | "Content-Transfer-Encoding: 8bit\n" 18 | 19 | #: src/about.rs:20 20 | msgid "App icons by" 21 | msgstr "" 22 | 23 | #: src/gtk_util.rs:20 src/gtk_util.rs:43 src/launch.rs:614 src/launch.rs:776 24 | msgid "Ok" 25 | msgstr "" 26 | 27 | #: src/launch.rs:99 28 | msgid "Both '{}' and '{}' are more recent than at last sync." 29 | msgstr "" 30 | 31 | #: src/launch.rs:158 32 | msgid "Unable to create Celeste's config directory [{}]." 33 | msgstr "" 34 | 35 | #: src/launch.rs:170 36 | msgid "Unable to create Celeste's database file [{}]." 37 | msgstr "" 38 | 39 | #: src/launch.rs:180 40 | msgid "Unable to connect to database [{}]." 41 | msgstr "" 42 | 43 | #: src/launch.rs:188 44 | msgid "Unable to run database migrations [{}]" 45 | msgstr "" 46 | 47 | #: src/launch.rs:288 48 | msgid "Awaiting sync check..." 49 | msgstr "" 50 | 51 | #: src/launch.rs:324 52 | msgid "Sync Errors" 53 | msgstr "" 54 | 55 | #: src/launch.rs:339 56 | msgid "File/Folder Exclusions" 57 | msgstr "" 58 | 59 | #: src/launch.rs:458 60 | msgid "Stop syncing this directory" 61 | msgstr "" 62 | 63 | #: src/launch.rs:475 64 | msgid "Are you sure you want to stop syncing '{}' to '{}'?" 65 | msgstr "" 66 | 67 | #: src/launch.rs:484 68 | msgid "This directory is currently being processed to no longer be synced." 69 | msgstr "" 70 | 71 | #: src/launch.rs:574 72 | msgid "Directories" 73 | msgstr "" 74 | 75 | #. Get the local folder to sync with. 76 | #: src/launch.rs:597 77 | msgid "Local folder:" 78 | msgstr "" 79 | 80 | #: src/launch.rs:613 src/launch.rs:775 src/login/gdrive.rs:214 81 | msgid "Cancel" 82 | msgstr "" 83 | 84 | #. Get the remote folder to sync with, and add it. 85 | #. The entry completion code is largely inspired by https://github.com/gtk-rs/gtk4-rs/blob/master/examples/entry_completion/main.rs. I honestly have no clue what half the code for that is doing, I just know the current code is working well enough, and it can be fixed later if it breaks. 86 | #: src/launch.rs:633 87 | msgid "Remote folder:" 88 | msgstr "" 89 | 90 | #: src/launch.rs:820 91 | msgid "The specified remote directory doesn't exist" 92 | msgstr "" 93 | 94 | #: src/launch.rs:828 95 | msgid "Failed to check if the specified remote directory exists" 96 | msgstr "" 97 | 98 | #: src/launch.rs:839 99 | msgid "The specified directory pair is already being synced" 100 | msgstr "" 101 | 102 | #: src/launch.rs:842 103 | msgid "The specified local directory doesn't exist" 104 | msgstr "" 105 | 106 | #: src/launch.rs:845 107 | msgid "The specified local path isn't a directory" 108 | msgstr "" 109 | 110 | #: src/launch.rs:848 111 | msgid "The specified local directory needs to be an absolute path" 112 | msgstr "" 113 | 114 | #: src/launch.rs:876 115 | msgid "Are you sure you want to delete this remote?" 116 | msgstr "" 117 | 118 | #: src/launch.rs:877 119 | msgid "All the directories associated with this remote will also stop syncing." 120 | msgstr "" 121 | 122 | #: src/launch.rs:1342 src/launch.rs:2509 123 | msgid "Files are synced." 124 | msgstr "" 125 | 126 | #: src/launch.rs:1352 127 | msgid "Syncing '{}'..." 128 | msgstr "" 129 | 130 | #: src/launch.rs:1373 131 | msgid "Checking for changes..." 132 | msgstr "" 133 | 134 | #. Add an error for reporting in the UI. 135 | #: src/launch.rs:1380 136 | msgid "Please resolve the reported syncing issues." 137 | msgstr "" 138 | 139 | #: src/launch.rs:1407 140 | msgid "{} errors found. " 141 | msgstr "" 142 | 143 | #: src/launch.rs:1421 144 | msgid "Would you like to dismiss this error?" 145 | msgstr "" 146 | 147 | #: src/launch.rs:1448 148 | msgid "Failed to sync '{}' to '{}' on remote." 149 | msgstr "" 150 | 151 | #: src/launch.rs:1456 152 | msgid "Failed to sync '{}' on remote to '{}'." 153 | msgstr "" 154 | 155 | #: src/launch.rs:1481 156 | msgid "Unable to fetch data for '{}' from the remote." 157 | msgstr "" 158 | 159 | #: src/launch.rs:1490 src/launch.rs:1495 src/launch.rs:1503 160 | msgid "File Update" 161 | msgstr "" 162 | 163 | #: src/launch.rs:1490 164 | msgid "Neither the local item or remote item exists anymore. This error will now be removed." 165 | msgstr "" 166 | 167 | #: src/launch.rs:1495 168 | msgid "Only the local item exists now, so it will be synced to the remote." 169 | msgstr "" 170 | 171 | #: src/launch.rs:1503 172 | msgid "Only the remote item exists now, so it will be synced to the local machine." 173 | msgstr "" 174 | 175 | #: src/launch.rs:1513 176 | msgid "Both the local item '{}' and remote item '{}' have been updated since the last sync." 177 | msgstr "" 178 | 179 | #: src/launch.rs:1515 180 | msgid "Which item would you like to keep?" 181 | msgstr "" 182 | 183 | #: src/launch.rs:1517 184 | msgid "Local" 185 | msgstr "" 186 | 187 | #: src/launch.rs:1518 188 | msgid "Remote" 189 | msgstr "" 190 | 191 | #: src/launch.rs:1567 192 | msgid "1 error found." 193 | msgstr "" 194 | 195 | #: src/launch.rs:1569 196 | msgid "{} errors found." 197 | msgstr "" 198 | 199 | #: src/launch.rs:1701 200 | msgid "Checking '{}' for changes..." 201 | msgstr "" 202 | 203 | #: src/launch.rs:2117 204 | msgid "Checking '{}' on remote for changes..." 205 | msgstr "" 206 | 207 | #: src/launch.rs:2530 208 | msgid "Finished sync checks with {} errors." 209 | msgstr "" 210 | 211 | #: src/launch.rs:2535 212 | msgid "Finished sync checks." 213 | msgstr "" 214 | 215 | #: src/login/gdrive.rs:211 216 | msgid "Authenticating to {}..." 217 | msgstr "" 218 | 219 | #: src/login/gdrive.rs:212 220 | msgid "Follow the link that opened in your browser, and come back once you've finished." 221 | msgstr "" 222 | 223 | #: src/login/gdrive.rs:239 224 | msgid "There was an issue while running the webserver for authentication" 225 | msgstr "" 226 | 227 | #: src/login/gdrive.rs:248 228 | msgid "There was an issue authenticating to {}" 229 | msgstr "" 230 | 231 | #: src/login/login_util.rs:15 232 | msgid "Name" 233 | msgstr "" 234 | 235 | #: src/login/login_util.rs:27 236 | msgid "Name already exists." 237 | msgstr "" 238 | 239 | #: src/login/login_util.rs:29 240 | msgid "Invalid name. Names must:\n" 241 | "- Only contain numbers, letters, '_', '-', '.', and spaces\n" 242 | "- Not start with '-' or a space\n" 243 | "- Not end with a space" 244 | msgstr "" 245 | 246 | #: src/login/login_util.rs:43 247 | msgid "Server URL" 248 | msgstr "" 249 | 250 | #: src/login/login_util.rs:49 251 | msgid "Invalid server URL ({})." 252 | msgstr "" 253 | 254 | #: src/login/login_util.rs:58 255 | msgid "Invalid server URL (no domain specified)." 256 | msgstr "" 257 | 258 | #: src/login/login_util.rs:62 259 | msgid "Invalid server URL (password was specified)." 260 | msgstr "" 261 | 262 | #: src/login/login_util.rs:66 263 | msgid "Invalid server URL(unknown server scheme {})." 264 | msgstr "" 265 | 266 | #: src/login/login_util.rs:78 267 | msgid "Don't specify '{}' as part of the URL." 268 | msgstr "" 269 | 270 | #: src/login/login_util.rs:91 271 | msgid "Username" 272 | msgstr "" 273 | 274 | #: src/login/login_util.rs:97 275 | msgid "Password" 276 | msgstr "" 277 | 278 | #: src/login/login_util.rs:104 279 | msgid "2FA Code" 280 | msgstr "" 281 | 282 | #: src/login/login_util.rs:113 283 | msgid "The provided 2FA code is invalid (should only contain digits)." 284 | msgstr "" 285 | 286 | #: src/login/login_util.rs:118 287 | msgid "The provided 2FA code is invalid (should be 6 digits long)." 288 | msgstr "" 289 | 290 | #: src/login/login_util.rs:142 291 | msgid "Log in" 292 | msgstr "" 293 | 294 | #: src/login/mod.rs:77 295 | msgid "Unable to connect to the server. Check your internet connection and try again." 296 | msgstr "" 297 | 298 | #: src/login/mod.rs:80 299 | msgid "A 2FA code is required to log in to this account. Provide one and try again." 300 | msgstr "" 301 | 302 | #: src/login/mod.rs:83 303 | msgid "Unable to authenticate to the server. Check your login credentials and try again." 304 | msgstr "" 305 | 306 | #: src/login/mod.rs:87 307 | msgid "Unable to log in" 308 | msgstr "" 309 | 310 | #. The dropdown for selecting the server type. 311 | #: src/login/mod.rs:124 312 | msgid "Server Type" 313 | msgstr "" 314 | 315 | #: src/tray.rs:13 316 | msgid "Awaiting sync checks..." 317 | msgstr "" 318 | 319 | #: src/tray.rs:60 320 | msgid "Open" 321 | msgstr "" 322 | 323 | #: src/tray.rs:67 324 | msgid "Close" 325 | msgstr "" 326 | 327 | #: src/main.rs:188 328 | msgid "Unknown Error" 329 | msgstr "" 330 | 331 | #: src/main.rs:195 332 | msgid "An unknown error has occurred while running. This is an internal issue with Celeste and should be reported.\n" 333 | "\n" 334 | "The following backtrace may help with debugging the issue - note that it may contain information such as login tokens/keys, so avoid posting the information publicly:" 335 | msgstr "" 336 | -------------------------------------------------------------------------------- /po/ro.po: -------------------------------------------------------------------------------- 1 | # SOME DESCRIPTIVE TITLE. 2 | # Copyright (C) YEAR Hunter Wittenborn 3 | # This file is distributed under the same license as the Celeste package. 4 | # FIRST AUTHOR , YEAR. 5 | # 6 | msgid "" 7 | msgstr "" 8 | "Project-Id-Version: Celeste 0.8.1\n" 9 | "Report-Msgid-Bugs-To: \n" 10 | "POT-Creation-Date: 2023-11-19 01:22+0000\n" 11 | "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" 12 | "Last-Translator: Automatically generated\n" 13 | "Language-Team: none\n" 14 | "Language: ro\n" 15 | "MIME-Version: 1.0\n" 16 | "Content-Type: text/plain; charset=UTF-8\n" 17 | "Content-Transfer-Encoding: 8bit\n" 18 | 19 | #: src/about.rs:20 20 | msgid "App icons by" 21 | msgstr "" 22 | 23 | #: src/gtk_util.rs:20 src/gtk_util.rs:43 src/launch.rs:614 src/launch.rs:776 24 | msgid "Ok" 25 | msgstr "" 26 | 27 | #: src/launch.rs:99 28 | msgid "Both '{}' and '{}' are more recent than at last sync." 29 | msgstr "" 30 | 31 | #: src/launch.rs:158 32 | msgid "Unable to create Celeste's config directory [{}]." 33 | msgstr "" 34 | 35 | #: src/launch.rs:170 36 | msgid "Unable to create Celeste's database file [{}]." 37 | msgstr "" 38 | 39 | #: src/launch.rs:180 40 | msgid "Unable to connect to database [{}]." 41 | msgstr "" 42 | 43 | #: src/launch.rs:188 44 | msgid "Unable to run database migrations [{}]" 45 | msgstr "" 46 | 47 | #: src/launch.rs:288 48 | msgid "Awaiting sync check..." 49 | msgstr "" 50 | 51 | #: src/launch.rs:324 52 | msgid "Sync Errors" 53 | msgstr "" 54 | 55 | #: src/launch.rs:339 56 | msgid "File/Folder Exclusions" 57 | msgstr "" 58 | 59 | #: src/launch.rs:458 60 | msgid "Stop syncing this directory" 61 | msgstr "" 62 | 63 | #: src/launch.rs:475 64 | msgid "Are you sure you want to stop syncing '{}' to '{}'?" 65 | msgstr "" 66 | 67 | #: src/launch.rs:484 68 | msgid "This directory is currently being processed to no longer be synced." 69 | msgstr "" 70 | 71 | #: src/launch.rs:574 72 | msgid "Directories" 73 | msgstr "" 74 | 75 | #. Get the local folder to sync with. 76 | #: src/launch.rs:597 77 | msgid "Local folder:" 78 | msgstr "" 79 | 80 | #: src/launch.rs:613 src/launch.rs:775 src/login/gdrive.rs:214 81 | msgid "Cancel" 82 | msgstr "" 83 | 84 | #. Get the remote folder to sync with, and add it. 85 | #. The entry completion code is largely inspired by https://github.com/gtk-rs/gtk4-rs/blob/master/examples/entry_completion/main.rs. I honestly have no clue what half the code for that is doing, I just know the current code is working well enough, and it can be fixed later if it breaks. 86 | #: src/launch.rs:633 87 | msgid "Remote folder:" 88 | msgstr "" 89 | 90 | #: src/launch.rs:820 91 | msgid "The specified remote directory doesn't exist" 92 | msgstr "" 93 | 94 | #: src/launch.rs:828 95 | msgid "Failed to check if the specified remote directory exists" 96 | msgstr "" 97 | 98 | #: src/launch.rs:839 99 | msgid "The specified directory pair is already being synced" 100 | msgstr "" 101 | 102 | #: src/launch.rs:842 103 | msgid "The specified local directory doesn't exist" 104 | msgstr "" 105 | 106 | #: src/launch.rs:845 107 | msgid "The specified local path isn't a directory" 108 | msgstr "" 109 | 110 | #: src/launch.rs:848 111 | msgid "The specified local directory needs to be an absolute path" 112 | msgstr "" 113 | 114 | #: src/launch.rs:876 115 | msgid "Are you sure you want to delete this remote?" 116 | msgstr "" 117 | 118 | #: src/launch.rs:877 119 | msgid "All the directories associated with this remote will also stop syncing." 120 | msgstr "" 121 | 122 | #: src/launch.rs:1342 src/launch.rs:2509 123 | msgid "Files are synced." 124 | msgstr "" 125 | 126 | #: src/launch.rs:1352 127 | msgid "Syncing '{}'..." 128 | msgstr "" 129 | 130 | #: src/launch.rs:1373 131 | msgid "Checking for changes..." 132 | msgstr "" 133 | 134 | #. Add an error for reporting in the UI. 135 | #: src/launch.rs:1380 136 | msgid "Please resolve the reported syncing issues." 137 | msgstr "" 138 | 139 | #: src/launch.rs:1407 140 | msgid "{} errors found. " 141 | msgstr "" 142 | 143 | #: src/launch.rs:1421 144 | msgid "Would you like to dismiss this error?" 145 | msgstr "" 146 | 147 | #: src/launch.rs:1448 148 | msgid "Failed to sync '{}' to '{}' on remote." 149 | msgstr "" 150 | 151 | #: src/launch.rs:1456 152 | msgid "Failed to sync '{}' on remote to '{}'." 153 | msgstr "" 154 | 155 | #: src/launch.rs:1481 156 | msgid "Unable to fetch data for '{}' from the remote." 157 | msgstr "" 158 | 159 | #: src/launch.rs:1490 src/launch.rs:1495 src/launch.rs:1503 160 | msgid "File Update" 161 | msgstr "" 162 | 163 | #: src/launch.rs:1490 164 | msgid "Neither the local item or remote item exists anymore. This error will now be removed." 165 | msgstr "" 166 | 167 | #: src/launch.rs:1495 168 | msgid "Only the local item exists now, so it will be synced to the remote." 169 | msgstr "" 170 | 171 | #: src/launch.rs:1503 172 | msgid "Only the remote item exists now, so it will be synced to the local machine." 173 | msgstr "" 174 | 175 | #: src/launch.rs:1513 176 | msgid "Both the local item '{}' and remote item '{}' have been updated since the last sync." 177 | msgstr "" 178 | 179 | #: src/launch.rs:1515 180 | msgid "Which item would you like to keep?" 181 | msgstr "" 182 | 183 | #: src/launch.rs:1517 184 | msgid "Local" 185 | msgstr "" 186 | 187 | #: src/launch.rs:1518 188 | msgid "Remote" 189 | msgstr "" 190 | 191 | #: src/launch.rs:1567 192 | msgid "1 error found." 193 | msgstr "" 194 | 195 | #: src/launch.rs:1569 196 | msgid "{} errors found." 197 | msgstr "" 198 | 199 | #: src/launch.rs:1701 200 | msgid "Checking '{}' for changes..." 201 | msgstr "" 202 | 203 | #: src/launch.rs:2117 204 | msgid "Checking '{}' on remote for changes..." 205 | msgstr "" 206 | 207 | #: src/launch.rs:2530 208 | msgid "Finished sync checks with {} errors." 209 | msgstr "" 210 | 211 | #: src/launch.rs:2535 212 | msgid "Finished sync checks." 213 | msgstr "" 214 | 215 | #: src/login/gdrive.rs:211 216 | msgid "Authenticating to {}..." 217 | msgstr "" 218 | 219 | #: src/login/gdrive.rs:212 220 | msgid "Follow the link that opened in your browser, and come back once you've finished." 221 | msgstr "" 222 | 223 | #: src/login/gdrive.rs:239 224 | msgid "There was an issue while running the webserver for authentication" 225 | msgstr "" 226 | 227 | #: src/login/gdrive.rs:248 228 | msgid "There was an issue authenticating to {}" 229 | msgstr "" 230 | 231 | #: src/login/login_util.rs:15 232 | msgid "Name" 233 | msgstr "" 234 | 235 | #: src/login/login_util.rs:27 236 | msgid "Name already exists." 237 | msgstr "" 238 | 239 | #: src/login/login_util.rs:29 240 | msgid "Invalid name. Names must:\n" 241 | "- Only contain numbers, letters, '_', '-', '.', and spaces\n" 242 | "- Not start with '-' or a space\n" 243 | "- Not end with a space" 244 | msgstr "" 245 | 246 | #: src/login/login_util.rs:43 247 | msgid "Server URL" 248 | msgstr "" 249 | 250 | #: src/login/login_util.rs:49 251 | msgid "Invalid server URL ({})." 252 | msgstr "" 253 | 254 | #: src/login/login_util.rs:58 255 | msgid "Invalid server URL (no domain specified)." 256 | msgstr "" 257 | 258 | #: src/login/login_util.rs:62 259 | msgid "Invalid server URL (password was specified)." 260 | msgstr "" 261 | 262 | #: src/login/login_util.rs:66 263 | msgid "Invalid server URL(unknown server scheme {})." 264 | msgstr "" 265 | 266 | #: src/login/login_util.rs:78 267 | msgid "Don't specify '{}' as part of the URL." 268 | msgstr "" 269 | 270 | #: src/login/login_util.rs:91 271 | msgid "Username" 272 | msgstr "" 273 | 274 | #: src/login/login_util.rs:97 275 | msgid "Password" 276 | msgstr "" 277 | 278 | #: src/login/login_util.rs:104 279 | msgid "2FA Code" 280 | msgstr "" 281 | 282 | #: src/login/login_util.rs:113 283 | msgid "The provided 2FA code is invalid (should only contain digits)." 284 | msgstr "" 285 | 286 | #: src/login/login_util.rs:118 287 | msgid "The provided 2FA code is invalid (should be 6 digits long)." 288 | msgstr "" 289 | 290 | #: src/login/login_util.rs:142 291 | msgid "Log in" 292 | msgstr "" 293 | 294 | #: src/login/mod.rs:77 295 | msgid "Unable to connect to the server. Check your internet connection and try again." 296 | msgstr "" 297 | 298 | #: src/login/mod.rs:80 299 | msgid "A 2FA code is required to log in to this account. Provide one and try again." 300 | msgstr "" 301 | 302 | #: src/login/mod.rs:83 303 | msgid "Unable to authenticate to the server. Check your login credentials and try again." 304 | msgstr "" 305 | 306 | #: src/login/mod.rs:87 307 | msgid "Unable to log in" 308 | msgstr "" 309 | 310 | #. The dropdown for selecting the server type. 311 | #: src/login/mod.rs:124 312 | msgid "Server Type" 313 | msgstr "" 314 | 315 | #: src/tray.rs:13 316 | msgid "Awaiting sync checks..." 317 | msgstr "" 318 | 319 | #: src/tray.rs:60 320 | msgid "Open" 321 | msgstr "" 322 | 323 | #: src/tray.rs:67 324 | msgid "Close" 325 | msgstr "" 326 | 327 | #: src/main.rs:188 328 | msgid "Unknown Error" 329 | msgstr "" 330 | 331 | #: src/main.rs:195 332 | msgid "An unknown error has occurred while running. This is an internal issue with Celeste and should be reported.\n" 333 | "\n" 334 | "The following backtrace may help with debugging the issue - note that it may contain information such as login tokens/keys, so avoid posting the information publicly:" 335 | msgstr "" 336 | -------------------------------------------------------------------------------- /po/com.hunterwittenborn.Celeste.pot: -------------------------------------------------------------------------------- 1 | # SOME DESCRIPTIVE TITLE. 2 | # Copyright (C) YEAR Hunter Wittenborn 3 | # This file is distributed under the same license as the Celeste package. 4 | # FIRST AUTHOR , YEAR. 5 | # 6 | #, fuzzy 7 | msgid "" 8 | msgstr "" 9 | "Project-Id-Version: Celeste 0.8.1\n" 10 | "Report-Msgid-Bugs-To: \n" 11 | "POT-Creation-Date: 2023-11-19 01:22+0000\n" 12 | "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" 13 | "Last-Translator: FULL NAME \n" 14 | "Language-Team: LANGUAGE \n" 15 | "Language: \n" 16 | "MIME-Version: 1.0\n" 17 | "Content-Type: text/plain; charset=UTF-8\n" 18 | "Content-Transfer-Encoding: 8bit\n" 19 | 20 | #: src/about.rs:20 21 | msgid "App icons by" 22 | msgstr "" 23 | 24 | #: src/gtk_util.rs:20 src/gtk_util.rs:43 src/launch.rs:614 src/launch.rs:776 25 | msgid "Ok" 26 | msgstr "" 27 | 28 | #: src/launch.rs:99 29 | msgid "Both '{}' and '{}' are more recent than at last sync." 30 | msgstr "" 31 | 32 | #: src/launch.rs:158 33 | msgid "Unable to create Celeste's config directory [{}]." 34 | msgstr "" 35 | 36 | #: src/launch.rs:170 37 | msgid "Unable to create Celeste's database file [{}]." 38 | msgstr "" 39 | 40 | #: src/launch.rs:180 41 | msgid "Unable to connect to database [{}]." 42 | msgstr "" 43 | 44 | #: src/launch.rs:188 45 | msgid "Unable to run database migrations [{}]" 46 | msgstr "" 47 | 48 | #: src/launch.rs:288 49 | msgid "Awaiting sync check..." 50 | msgstr "" 51 | 52 | #: src/launch.rs:324 53 | msgid "Sync Errors" 54 | msgstr "" 55 | 56 | #: src/launch.rs:339 57 | msgid "File/Folder Exclusions" 58 | msgstr "" 59 | 60 | #: src/launch.rs:458 61 | msgid "Stop syncing this directory" 62 | msgstr "" 63 | 64 | #: src/launch.rs:475 65 | msgid "Are you sure you want to stop syncing '{}' to '{}'?" 66 | msgstr "" 67 | 68 | #: src/launch.rs:484 69 | msgid "This directory is currently being processed to no longer be synced." 70 | msgstr "" 71 | 72 | #: src/launch.rs:574 73 | msgid "Directories" 74 | msgstr "" 75 | 76 | #. Get the local folder to sync with. 77 | #: src/launch.rs:597 78 | msgid "Local folder:" 79 | msgstr "" 80 | 81 | #: src/launch.rs:613 src/launch.rs:775 src/login/gdrive.rs:214 82 | msgid "Cancel" 83 | msgstr "" 84 | 85 | #. Get the remote folder to sync with, and add it. 86 | #. The entry completion code is largely inspired by https://github.com/gtk-rs/gtk4-rs/blob/master/examples/entry_completion/main.rs. I honestly have no clue what half the code for that is doing, I just know the current code is working well enough, and it can be fixed later if it breaks. 87 | #: src/launch.rs:633 88 | msgid "Remote folder:" 89 | msgstr "" 90 | 91 | #: src/launch.rs:820 92 | msgid "The specified remote directory doesn't exist" 93 | msgstr "" 94 | 95 | #: src/launch.rs:828 96 | msgid "Failed to check if the specified remote directory exists" 97 | msgstr "" 98 | 99 | #: src/launch.rs:839 100 | msgid "The specified directory pair is already being synced" 101 | msgstr "" 102 | 103 | #: src/launch.rs:842 104 | msgid "The specified local directory doesn't exist" 105 | msgstr "" 106 | 107 | #: src/launch.rs:845 108 | msgid "The specified local path isn't a directory" 109 | msgstr "" 110 | 111 | #: src/launch.rs:848 112 | msgid "The specified local directory needs to be an absolute path" 113 | msgstr "" 114 | 115 | #: src/launch.rs:876 116 | msgid "Are you sure you want to delete this remote?" 117 | msgstr "" 118 | 119 | #: src/launch.rs:877 120 | msgid "All the directories associated with this remote will also stop syncing." 121 | msgstr "" 122 | 123 | #: src/launch.rs:1342 src/launch.rs:2509 124 | msgid "Files are synced." 125 | msgstr "" 126 | 127 | #: src/launch.rs:1352 128 | msgid "Syncing '{}'..." 129 | msgstr "" 130 | 131 | #: src/launch.rs:1373 132 | msgid "Checking for changes..." 133 | msgstr "" 134 | 135 | #. Add an error for reporting in the UI. 136 | #: src/launch.rs:1380 137 | msgid "Please resolve the reported syncing issues." 138 | msgstr "" 139 | 140 | #: src/launch.rs:1407 141 | msgid "{} errors found. " 142 | msgstr "" 143 | 144 | #: src/launch.rs:1421 145 | msgid "Would you like to dismiss this error?" 146 | msgstr "" 147 | 148 | #: src/launch.rs:1448 149 | msgid "Failed to sync '{}' to '{}' on remote." 150 | msgstr "" 151 | 152 | #: src/launch.rs:1456 153 | msgid "Failed to sync '{}' on remote to '{}'." 154 | msgstr "" 155 | 156 | #: src/launch.rs:1481 157 | msgid "Unable to fetch data for '{}' from the remote." 158 | msgstr "" 159 | 160 | #: src/launch.rs:1490 src/launch.rs:1495 src/launch.rs:1503 161 | msgid "File Update" 162 | msgstr "" 163 | 164 | #: src/launch.rs:1490 165 | msgid "Neither the local item or remote item exists anymore. This error will now be removed." 166 | msgstr "" 167 | 168 | #: src/launch.rs:1495 169 | msgid "Only the local item exists now, so it will be synced to the remote." 170 | msgstr "" 171 | 172 | #: src/launch.rs:1503 173 | msgid "Only the remote item exists now, so it will be synced to the local machine." 174 | msgstr "" 175 | 176 | #: src/launch.rs:1513 177 | msgid "Both the local item '{}' and remote item '{}' have been updated since the last sync." 178 | msgstr "" 179 | 180 | #: src/launch.rs:1515 181 | msgid "Which item would you like to keep?" 182 | msgstr "" 183 | 184 | #: src/launch.rs:1517 185 | msgid "Local" 186 | msgstr "" 187 | 188 | #: src/launch.rs:1518 189 | msgid "Remote" 190 | msgstr "" 191 | 192 | #: src/launch.rs:1567 193 | msgid "1 error found." 194 | msgstr "" 195 | 196 | #: src/launch.rs:1569 197 | msgid "{} errors found." 198 | msgstr "" 199 | 200 | #: src/launch.rs:1701 201 | msgid "Checking '{}' for changes..." 202 | msgstr "" 203 | 204 | #: src/launch.rs:2117 205 | msgid "Checking '{}' on remote for changes..." 206 | msgstr "" 207 | 208 | #: src/launch.rs:2530 209 | msgid "Finished sync checks with {} errors." 210 | msgstr "" 211 | 212 | #: src/launch.rs:2535 213 | msgid "Finished sync checks." 214 | msgstr "" 215 | 216 | #: src/login/gdrive.rs:211 217 | msgid "Authenticating to {}..." 218 | msgstr "" 219 | 220 | #: src/login/gdrive.rs:212 221 | msgid "Follow the link that opened in your browser, and come back once you've finished." 222 | msgstr "" 223 | 224 | #: src/login/gdrive.rs:239 225 | msgid "There was an issue while running the webserver for authentication" 226 | msgstr "" 227 | 228 | #: src/login/gdrive.rs:248 229 | msgid "There was an issue authenticating to {}" 230 | msgstr "" 231 | 232 | #: src/login/login_util.rs:15 233 | msgid "Name" 234 | msgstr "" 235 | 236 | #: src/login/login_util.rs:27 237 | msgid "Name already exists." 238 | msgstr "" 239 | 240 | #: src/login/login_util.rs:29 241 | msgid "Invalid name. Names must:\n" 242 | "- Only contain numbers, letters, '_', '-', '.', and spaces\n" 243 | "- Not start with '-' or a space\n" 244 | "- Not end with a space" 245 | msgstr "" 246 | 247 | #: src/login/login_util.rs:43 248 | msgid "Server URL" 249 | msgstr "" 250 | 251 | #: src/login/login_util.rs:49 252 | msgid "Invalid server URL ({})." 253 | msgstr "" 254 | 255 | #: src/login/login_util.rs:58 256 | msgid "Invalid server URL (no domain specified)." 257 | msgstr "" 258 | 259 | #: src/login/login_util.rs:62 260 | msgid "Invalid server URL (password was specified)." 261 | msgstr "" 262 | 263 | #: src/login/login_util.rs:66 264 | msgid "Invalid server URL(unknown server scheme {})." 265 | msgstr "" 266 | 267 | #: src/login/login_util.rs:78 268 | msgid "Don't specify '{}' as part of the URL." 269 | msgstr "" 270 | 271 | #: src/login/login_util.rs:91 272 | msgid "Username" 273 | msgstr "" 274 | 275 | #: src/login/login_util.rs:97 276 | msgid "Password" 277 | msgstr "" 278 | 279 | #: src/login/login_util.rs:104 280 | msgid "2FA Code" 281 | msgstr "" 282 | 283 | #: src/login/login_util.rs:113 284 | msgid "The provided 2FA code is invalid (should only contain digits)." 285 | msgstr "" 286 | 287 | #: src/login/login_util.rs:118 288 | msgid "The provided 2FA code is invalid (should be 6 digits long)." 289 | msgstr "" 290 | 291 | #: src/login/login_util.rs:142 292 | msgid "Log in" 293 | msgstr "" 294 | 295 | #: src/login/mod.rs:77 296 | msgid "Unable to connect to the server. Check your internet connection and try again." 297 | msgstr "" 298 | 299 | #: src/login/mod.rs:80 300 | msgid "A 2FA code is required to log in to this account. Provide one and try again." 301 | msgstr "" 302 | 303 | #: src/login/mod.rs:83 304 | msgid "Unable to authenticate to the server. Check your login credentials and try again." 305 | msgstr "" 306 | 307 | #: src/login/mod.rs:87 308 | msgid "Unable to log in" 309 | msgstr "" 310 | 311 | #. The dropdown for selecting the server type. 312 | #: src/login/mod.rs:124 313 | msgid "Server Type" 314 | msgstr "" 315 | 316 | #: src/tray.rs:13 317 | msgid "Awaiting sync checks..." 318 | msgstr "" 319 | 320 | #: src/tray.rs:60 321 | msgid "Open" 322 | msgstr "" 323 | 324 | #: src/tray.rs:67 325 | msgid "Close" 326 | msgstr "" 327 | 328 | #: src/main.rs:188 329 | msgid "Unknown Error" 330 | msgstr "" 331 | 332 | #: src/main.rs:195 333 | msgid "An unknown error has occurred while running. This is an internal issue with Celeste and should be reported.\n" 334 | "\n" 335 | "The following backtrace may help with debugging the issue - note that it may contain information such as login tokens/keys, so avoid posting the information publicly:" 336 | msgstr "" 337 | -------------------------------------------------------------------------------- /po/nb_NO.po: -------------------------------------------------------------------------------- 1 | # SOME DESCRIPTIVE TITLE. 2 | # Copyright (C) YEAR Hunter Wittenborn 3 | # This file is distributed under the same license as the Celeste package. 4 | # FIRST AUTHOR , YEAR. 5 | # 6 | msgid "" 7 | msgstr "" 8 | "Project-Id-Version: Celeste 0.5.2\n" 9 | "Report-Msgid-Bugs-To: \n" 10 | "POT-Creation-Date: 2023-05-01 18:51+0000\n" 11 | "PO-Revision-Date: 2023-05-05 11:52+0000\n" 12 | "Last-Translator: Allan Nordhøy \n" 13 | "Language-Team: Norwegian Bokmål \n" 15 | "Language: nb_NO\n" 16 | "MIME-Version: 1.0\n" 17 | "Content-Type: text/plain; charset=UTF-8\n" 18 | "Content-Transfer-Encoding: 8bit\n" 19 | "Plural-Forms: nplurals=2; plural=n != 1;\n" 20 | "X-Generator: Weblate 4.18-dev\n" 21 | 22 | #: celeste/src/gtk_util.rs:20 celeste/src/gtk_util.rs:43 celeste/src/launch.rs:678 celeste/src/launch.rs:840 23 | msgid "Ok" 24 | msgstr "OK" 25 | 26 | #: celeste/src/launch.rs:102 27 | msgid "Both '{}' and '{}' are more recent than at last sync." 28 | msgstr "" 29 | 30 | #: celeste/src/launch.rs:214 31 | msgid "Unable to create Celeste's config directory [{}]." 32 | msgstr "" 33 | 34 | #: celeste/src/launch.rs:226 35 | msgid "Unable to create Celeste's database file [{}]." 36 | msgstr "" 37 | 38 | #: celeste/src/launch.rs:236 39 | msgid "Unable to connect to database [{}]." 40 | msgstr "Kunne ikke koble til database [{}]." 41 | 42 | #: celeste/src/launch.rs:244 43 | msgid "Unable to run database migrations [{}]" 44 | msgstr "" 45 | 46 | #: celeste/src/launch.rs:351 47 | msgid "Awaiting sync check..." 48 | msgstr "Venter på synkroniseringssjekk …" 49 | 50 | #: celeste/src/launch.rs:387 51 | msgid "Sync Errors" 52 | msgstr "Synkroniseringsfeil" 53 | 54 | #: celeste/src/launch.rs:402 55 | msgid "File/Folder Exclusions" 56 | msgstr "" 57 | 58 | #: celeste/src/launch.rs:521 59 | msgid "Stop syncing this directory" 60 | msgstr "" 61 | 62 | #: celeste/src/launch.rs:538 63 | msgid "Are you sure you want to stop syncing '{}' to '{}'?" 64 | msgstr "Stopp synkronisering av «{}» til «{}»?" 65 | 66 | #: celeste/src/launch.rs:547 67 | msgid "This directory is currently being processed to no longer be synced." 68 | msgstr "" 69 | 70 | #: celeste/src/launch.rs:638 71 | msgid "Directories" 72 | msgstr "Mapper" 73 | 74 | #. Get the local folder to sync with. 75 | #: celeste/src/launch.rs:661 76 | msgid "Local folder:" 77 | msgstr "Lokal mappe:" 78 | 79 | #: celeste/src/launch.rs:677 celeste/src/launch.rs:839 celeste/src/login/gdrive.rs:207 80 | msgid "Cancel" 81 | msgstr "Avbryt" 82 | 83 | #. Get the remote folder to sync with, and add it. 84 | #. The entry completion code is largely inspired by https://github.com/gtk-rs/gtk4-rs/blob/master/examples/entry_completion/main.rs. I honestly have no clue what half the code for that is doing, I just know the current code is working well enough, and it can be fixed later if it breaks. 85 | #: celeste/src/launch.rs:697 86 | msgid "Remote folder:" 87 | msgstr "Mappe annensteds hen:" 88 | 89 | #: celeste/src/launch.rs:884 90 | msgid "The specified remote directory doesn't exist" 91 | msgstr "Angitt mappe annensteds hen finnes ikke der" 92 | 93 | #: celeste/src/launch.rs:892 94 | msgid "Failed to check if the specified remote directory exists" 95 | msgstr "Klarte ikke å finne ut om angitt mappe annensteds hen finnes" 96 | 97 | #: celeste/src/launch.rs:903 98 | msgid "The specified directory pair is already being synced" 99 | msgstr "" 100 | 101 | #: celeste/src/launch.rs:906 102 | msgid "The specified local directory doesn't exist" 103 | msgstr "" 104 | 105 | #: celeste/src/launch.rs:909 106 | msgid "The specified local path isn't a directory" 107 | msgstr "Angitt lokal sti er ikke en mappe" 108 | 109 | #: celeste/src/launch.rs:912 110 | msgid "The specified local directory needs to be an absolute path" 111 | msgstr "" 112 | 113 | #: celeste/src/launch.rs:940 114 | msgid "Are you sure you want to delete this remote?" 115 | msgstr "" 116 | 117 | #: celeste/src/launch.rs:941 118 | msgid "All the directories associated with this remote will also stop syncing." 119 | msgstr "" 120 | 121 | #: celeste/src/launch.rs:1167 celeste-tray/src/main.rs:51 122 | msgid "Awaiting sync checks..." 123 | msgstr "Venter på synkroniseringssjekking …" 124 | 125 | #. Notify the tray app that we're syncing this remote now. 126 | #: celeste/src/launch.rs:1263 127 | msgid "Syncing '{}'..." 128 | msgstr "Synkroniserer «{}» …" 129 | 130 | #: celeste/src/launch.rs:1298 131 | msgid "Checking for changes..." 132 | msgstr "Ser etter endringer …" 133 | 134 | #. Add an error for reporting in the UI. 135 | #: celeste/src/launch.rs:1305 136 | msgid "Please resolve the reported syncing issues." 137 | msgstr "Løs de rapporterte synkroniseringsproblemene." 138 | 139 | #: celeste/src/launch.rs:1332 140 | #, fuzzy 141 | msgid "{} errors found. " 142 | msgstr "Fant {} feil. " 143 | 144 | #: celeste/src/launch.rs:1346 145 | msgid "Would you like to dismiss this error?" 146 | msgstr "Forkast denne feilen?" 147 | 148 | #: celeste/src/launch.rs:1373 149 | msgid "Failed to sync '{}' to '{}' on remote." 150 | msgstr "" 151 | 152 | #: celeste/src/launch.rs:1381 153 | msgid "Failed to sync '{}' on remote to '{}'." 154 | msgstr "" 155 | 156 | #: celeste/src/launch.rs:1406 157 | msgid "Unable to fetch data for '{}' from the remote." 158 | msgstr "" 159 | 160 | #: celeste/src/launch.rs:1415 celeste/src/launch.rs:1420 celeste/src/launch.rs:1428 161 | msgid "File Update" 162 | msgstr "Filoppdatering" 163 | 164 | #: celeste/src/launch.rs:1415 165 | msgid "Neither the local item or remote item exists anymore. This error will now be removed." 166 | msgstr "" 167 | 168 | #: celeste/src/launch.rs:1420 169 | msgid "Only the local item exists now, so it will be synced to the remote." 170 | msgstr "" 171 | 172 | #: celeste/src/launch.rs:1428 173 | msgid "Only the remote item exists now, so it will be synced to the local machine." 174 | msgstr "" 175 | 176 | #: celeste/src/launch.rs:1438 177 | msgid "Both the local item '{}' and remote item '{}' have been updated since the last sync." 178 | msgstr "" 179 | 180 | #: celeste/src/launch.rs:1440 181 | msgid "Which item would you like to keep?" 182 | msgstr "Hvilket element vil du beholde?" 183 | 184 | #: celeste/src/launch.rs:1442 185 | msgid "Local" 186 | msgstr "Lokalt" 187 | 188 | #: celeste/src/launch.rs:1443 189 | msgid "Remote" 190 | msgstr "Annensteds hen" 191 | 192 | #: celeste/src/launch.rs:1492 193 | msgid "1 error found." 194 | msgstr "Fant én feil." 195 | 196 | #: celeste/src/launch.rs:1494 197 | msgid "{} errors found." 198 | msgstr "Fant {} feil." 199 | 200 | #: celeste/src/launch.rs:1626 201 | msgid "Checking '{}' for changes..." 202 | msgstr "Sjekker «{}» for endringer …" 203 | 204 | #: celeste/src/launch.rs:2042 205 | msgid "Checking '{}' on remote for changes..." 206 | msgstr "" 207 | 208 | #: celeste/src/launch.rs:2433 209 | msgid "Directory has finished sync checks." 210 | msgstr "" 211 | 212 | #: celeste/src/launch.rs:2454 213 | msgid "Finished sync checks with {} errors." 214 | msgstr "" 215 | 216 | #: celeste/src/login/gdrive.rs:204 217 | msgid "Authenticating to {}..." 218 | msgstr "" 219 | 220 | #: celeste/src/login/gdrive.rs:205 221 | msgid "Follow the link that opened in your browser, and come back once you've finished." 222 | msgstr "" 223 | 224 | #: celeste/src/login/gdrive.rs:232 225 | msgid "There was an issue while running the webserver for authentication" 226 | msgstr "" 227 | 228 | #: celeste/src/login/gdrive.rs:241 229 | msgid "There was an issue authenticating to {}" 230 | msgstr "" 231 | 232 | #: celeste/src/login/login_util.rs:14 233 | msgid "Server Name" 234 | msgstr "Tjenernavn" 235 | 236 | #: celeste/src/login/login_util.rs:26 237 | msgid "Server name already exists." 238 | msgstr "Tjenernavnet finnes allerede." 239 | 240 | #: celeste/src/login/login_util.rs:28 241 | msgid "Invalid server name. Server names must:\n" 242 | "- Only contain numbers, letters, '_', '-', '.', and spaces\n" 243 | "- Not start with '-' or a space\n" 244 | "- Not end with a space" 245 | msgstr "" 246 | 247 | #: celeste/src/login/login_util.rs:42 248 | msgid "Server URL" 249 | msgstr "Tjenernettadresse" 250 | 251 | #: celeste/src/login/login_util.rs:48 252 | msgid "Invalid server URL ({})." 253 | msgstr "Ugyldig tjenernettadresse ({})." 254 | 255 | #: celeste/src/login/login_util.rs:57 256 | msgid "Invalid server URL (no domain specified)." 257 | msgstr "Ugyldig tjenernettadresse. (Ingen domenenavn angitt)." 258 | 259 | #: celeste/src/login/login_util.rs:61 260 | msgid "Invalid server URL (password was specified)." 261 | msgstr "Ugyldig tjenernettadresse. (Passord ble angitt)." 262 | 263 | #: celeste/src/login/login_util.rs:65 264 | msgid "Invalid server URL(unknown server scheme {})." 265 | msgstr "" 266 | 267 | #: celeste/src/login/login_util.rs:77 268 | msgid "Don't specify '{}' as part of the URL." 269 | msgstr "" 270 | 271 | #: celeste/src/login/login_util.rs:90 272 | msgid "Username" 273 | msgstr "Brukernavn" 274 | 275 | #: celeste/src/login/login_util.rs:96 276 | msgid "Password" 277 | msgstr "Passord" 278 | 279 | #: celeste/src/login/login_util.rs:102 280 | msgid "Log in" 281 | msgstr "Logg inn" 282 | 283 | #: celeste/src/login/mod.rs:72 284 | msgid "Unable to connect to the server. Check your internet connection and try again." 285 | msgstr "" 286 | 287 | #: celeste/src/login/mod.rs:76 288 | msgid "Unable to authenticate to the server. Check your login credentials and try again." 289 | msgstr "" 290 | 291 | #: celeste/src/login/mod.rs:80 292 | msgid "Unable to log in" 293 | msgstr "Kan ikke logge inn" 294 | 295 | #. The dropdown for selecting the server type. 296 | #: celeste/src/login/mod.rs:116 297 | msgid "Server Type" 298 | msgstr "Tjenertype" 299 | 300 | #: celeste/src/main.rs:187 301 | msgid "Unknown Error" 302 | msgstr "Ukjent feil" 303 | 304 | #: celeste/src/main.rs:194 305 | msgid "An unknown error has occurred while running. This is an internal issue with Celeste and should be reported.\n" 306 | "\n" 307 | "The following backtrace may help with debugging the issue - note that it may contain information such as login tokens/keys, so avoid posting the information publicly:" 308 | msgstr "" 309 | 310 | #: celeste-tray/src/main.rs:54 311 | msgid "Open" 312 | msgstr "Åpne" 313 | 314 | #: celeste-tray/src/main.rs:55 315 | msgid "Quit" 316 | msgstr "Avslutt" 317 | 318 | #: celeste-tray/src/main.rs:116 319 | msgid "Quitting..." 320 | msgstr "Avslutter …" 321 | -------------------------------------------------------------------------------- /po/zh_Hant.po: -------------------------------------------------------------------------------- 1 | # SOME DESCRIPTIVE TITLE. 2 | # Copyright (C) YEAR Hunter Wittenborn 3 | # This file is distributed under the same license as the Celeste package. 4 | # FIRST AUTHOR , YEAR. 5 | # 6 | msgid "" 7 | msgstr "" 8 | "Project-Id-Version: Celeste 0.8.1\n" 9 | "Report-Msgid-Bugs-To: \n" 10 | "POT-Creation-Date: 2023-11-19 01:22+0000\n" 11 | "PO-Revision-Date: 2024-03-03 16:01+0000\n" 12 | "Last-Translator: Kisaragi Hiu \n" 13 | "Language-Team: Chinese (Traditional) \n" 15 | "Language: zh_Hant\n" 16 | "MIME-Version: 1.0\n" 17 | "Content-Type: text/plain; charset=UTF-8\n" 18 | "Content-Transfer-Encoding: 8bit\n" 19 | "Plural-Forms: nplurals=1; plural=0;\n" 20 | "X-Generator: Weblate 5.5-dev\n" 21 | 22 | #: src/about.rs:20 23 | msgid "App icons by" 24 | msgstr "應用程式圖示製作" 25 | 26 | #: src/gtk_util.rs:20 src/gtk_util.rs:43 src/launch.rs:614 src/launch.rs:776 27 | msgid "Ok" 28 | msgstr "確定" 29 | 30 | #: src/launch.rs:99 31 | msgid "Both '{}' and '{}' are more recent than at last sync." 32 | msgstr "'{}' 和 '{}' 都比上次同步還要更新。" 33 | 34 | #: src/launch.rs:158 35 | msgid "Unable to create Celeste's config directory [{}]." 36 | msgstr "無法建立 Celeste 的設定目錄 [{}]。" 37 | 38 | #: src/launch.rs:170 39 | msgid "Unable to create Celeste's database file [{}]." 40 | msgstr "無法建立 Celeste 的資料庫檔案 [{}]。" 41 | 42 | #: src/launch.rs:180 43 | msgid "Unable to connect to database [{}]." 44 | msgstr "無法連線到資料庫 [{}]。" 45 | 46 | #: src/launch.rs:188 47 | msgid "Unable to run database migrations [{}]" 48 | msgstr "無法執行資料庫遷移 [{}]" 49 | 50 | #: src/launch.rs:288 51 | msgid "Awaiting sync check..." 52 | msgstr "正在等待同步檢查..." 53 | 54 | #: src/launch.rs:324 55 | msgid "Sync Errors" 56 | msgstr "同步錯誤" 57 | 58 | #: src/launch.rs:339 59 | msgid "File/Folder Exclusions" 60 | msgstr "檔案/資料夾排除" 61 | 62 | #: src/launch.rs:458 63 | msgid "Stop syncing this directory" 64 | msgstr "停止同步這個目錄" 65 | 66 | #: src/launch.rs:475 67 | msgid "Are you sure you want to stop syncing '{}' to '{}'?" 68 | msgstr "您確定要停止將 '{}' 同步到 '{}' 嗎?" 69 | 70 | #: src/launch.rs:484 71 | msgid "This directory is currently being processed to no longer be synced." 72 | msgstr "這個目錄目前正在進行不再繼續同步的處理。" 73 | 74 | #: src/launch.rs:574 75 | msgid "Directories" 76 | msgstr "目錄" 77 | 78 | #. Get the local folder to sync with. 79 | #: src/launch.rs:597 80 | msgid "Local folder:" 81 | msgstr "本地資料夾:" 82 | 83 | #: src/launch.rs:613 src/launch.rs:775 src/login/gdrive.rs:214 84 | msgid "Cancel" 85 | msgstr "取消" 86 | 87 | #. Get the remote folder to sync with, and add it. 88 | #. The entry completion code is largely inspired by https://github.com/gtk-rs/gtk4-rs/blob/master/examples/entry_completion/main.rs. I honestly have no clue what half the code for that is doing, I just know the current code is working well enough, and it can be fixed later if it breaks. 89 | #: src/launch.rs:633 90 | msgid "Remote folder:" 91 | msgstr "遠端資料夾:" 92 | 93 | #: src/launch.rs:820 94 | msgid "The specified remote directory doesn't exist" 95 | msgstr "指定的遠端目錄不存在" 96 | 97 | #: src/launch.rs:828 98 | msgid "Failed to check if the specified remote directory exists" 99 | msgstr "檢查指定遠端目錄是否存在時失敗" 100 | 101 | #: src/launch.rs:839 102 | msgid "The specified directory pair is already being synced" 103 | msgstr "指定的目錄對已進行同步" 104 | 105 | #: src/launch.rs:842 106 | msgid "The specified local directory doesn't exist" 107 | msgstr "指定的本地目錄不存在" 108 | 109 | #: src/launch.rs:845 110 | msgid "The specified local path isn't a directory" 111 | msgstr "指定的本地路徑不是目錄" 112 | 113 | #: src/launch.rs:848 114 | msgid "The specified local directory needs to be an absolute path" 115 | msgstr "指定的本地路徑必須是絕對路徑" 116 | 117 | #: src/launch.rs:876 118 | msgid "Are you sure you want to delete this remote?" 119 | msgstr "您確定要刪除這個遠端嗎?" 120 | 121 | #: src/launch.rs:877 122 | msgid "All the directories associated with this remote will also stop syncing." 123 | msgstr "所有與此遠端配對的目錄也會停止同步。" 124 | 125 | #: src/launch.rs:1342 src/launch.rs:2509 126 | msgid "Files are synced." 127 | msgstr "檔案已同步。" 128 | 129 | #: src/launch.rs:1352 130 | msgid "Syncing '{}'..." 131 | msgstr "正在同步 '{}'..." 132 | 133 | #: src/launch.rs:1373 134 | msgid "Checking for changes..." 135 | msgstr "正在檢查變更..." 136 | 137 | #. Add an error for reporting in the UI. 138 | #: src/launch.rs:1380 139 | msgid "Please resolve the reported syncing issues." 140 | msgstr "請解決回報的同步問題。" 141 | 142 | #: src/launch.rs:1407 143 | msgid "{} errors found. " 144 | msgstr "找到 {} 個錯誤。 " 145 | 146 | #: src/launch.rs:1421 147 | msgid "Would you like to dismiss this error?" 148 | msgstr "您要忽略這個錯誤嗎?" 149 | 150 | #: src/launch.rs:1448 151 | msgid "Failed to sync '{}' to '{}' on remote." 152 | msgstr "將 '{}' 同步到遠端上的 '{}' 失敗。" 153 | 154 | #: src/launch.rs:1456 155 | msgid "Failed to sync '{}' on remote to '{}'." 156 | msgstr "將遠端上的 '{}' 同步到 '{}' 失敗。" 157 | 158 | #: src/launch.rs:1481 159 | msgid "Unable to fetch data for '{}' from the remote." 160 | msgstr "無法從遠端取得 '{}' 的資料。" 161 | 162 | #: src/launch.rs:1490 src/launch.rs:1495 src/launch.rs:1503 163 | msgid "File Update" 164 | msgstr "檔案更新" 165 | 166 | #: src/launch.rs:1490 167 | msgid "Neither the local item or remote item exists anymore. This error will now be removed." 168 | msgstr "本地或遠端項目皆已不存在。此錯誤將被移除。" 169 | 170 | #: src/launch.rs:1495 171 | msgid "Only the local item exists now, so it will be synced to the remote." 172 | msgstr "只有本地項目存在,將會將其同步到遠端。" 173 | 174 | #: src/launch.rs:1503 175 | msgid "Only the remote item exists now, so it will be synced to the local machine." 176 | msgstr "只有遠端項目存在,將會將其同步到本地。" 177 | 178 | #: src/launch.rs:1513 179 | msgid "Both the local item '{}' and remote item '{}' have been updated since the last sync." 180 | msgstr "上次同步以來本地項目 '{}' 與遠端項目 '{}' 皆有被更新。" 181 | 182 | #: src/launch.rs:1515 183 | msgid "Which item would you like to keep?" 184 | msgstr "您要保留哪一個版本?" 185 | 186 | #: src/launch.rs:1517 187 | msgid "Local" 188 | msgstr "本地" 189 | 190 | #: src/launch.rs:1518 191 | msgid "Remote" 192 | msgstr "遠端" 193 | 194 | #: src/launch.rs:1567 195 | msgid "1 error found." 196 | msgstr "找到 1 個錯誤。" 197 | 198 | #: src/launch.rs:1569 199 | msgid "{} errors found." 200 | msgstr "找到 {} 個錯誤。" 201 | 202 | #: src/launch.rs:1701 203 | msgid "Checking '{}' for changes..." 204 | msgstr "正在檢查 '{}' 的變更..." 205 | 206 | #: src/launch.rs:2117 207 | msgid "Checking '{}' on remote for changes..." 208 | msgstr "正在檢查遠端的 '{}' 的變更..." 209 | 210 | #: src/launch.rs:2530 211 | msgid "Finished sync checks with {} errors." 212 | msgstr "已完成同步檢查,有 {} 個錯誤。" 213 | 214 | #: src/launch.rs:2535 215 | msgid "Finished sync checks." 216 | msgstr "已完成同步檢查。" 217 | 218 | #: src/login/gdrive.rs:211 219 | msgid "Authenticating to {}..." 220 | msgstr "正在與 {} 進行驗證..." 221 | 222 | #: src/login/gdrive.rs:212 223 | msgid "Follow the link that opened in your browser, and come back once you've finished." 224 | msgstr "跟隨已在您的瀏覽器開啟的連結,完成後再回到此處。" 225 | 226 | #: src/login/gdrive.rs:239 227 | msgid "There was an issue while running the webserver for authentication" 228 | msgstr "執行驗證用的伺服器時發生問題" 229 | 230 | #: src/login/gdrive.rs:248 231 | msgid "There was an issue authenticating to {}" 232 | msgstr "與 {} 進行驗證時發生問題" 233 | 234 | #: src/login/login_util.rs:15 235 | msgid "Name" 236 | msgstr "名稱" 237 | 238 | #: src/login/login_util.rs:27 239 | msgid "Name already exists." 240 | msgstr "名稱已存在。" 241 | 242 | #: src/login/login_util.rs:29 243 | msgid "Invalid name. Names must:\n" 244 | "- Only contain numbers, letters, '_', '-', '.', and spaces\n" 245 | "- Not start with '-' or a space\n" 246 | "- Not end with a space" 247 | msgstr "" 248 | "無效的名稱。名稱必須符合以下條件:\n" 249 | "- 只能包含數字、字母、底線 ('_')、dash ('-')、半形句點 ('.') 或空格\n" 250 | "- 不能以 dash ('-') 或空格開頭\n" 251 | "- 不能以空格結尾" 252 | 253 | #: src/login/login_util.rs:43 254 | msgid "Server URL" 255 | msgstr "伺服器網址" 256 | 257 | #: src/login/login_util.rs:49 258 | msgid "Invalid server URL ({})." 259 | msgstr "無效的伺服器網址({})。" 260 | 261 | #: src/login/login_util.rs:58 262 | msgid "Invalid server URL (no domain specified)." 263 | msgstr "無效的伺服器網址(未指定網域)。" 264 | 265 | #: src/login/login_util.rs:62 266 | msgid "Invalid server URL (password was specified)." 267 | msgstr "無效的伺服器網址(指定了密碼)。" 268 | 269 | #: src/login/login_util.rs:66 270 | msgid "Invalid server URL(unknown server scheme {})." 271 | msgstr "無效的伺服器網址(未知的伺服器機制 {})。" 272 | 273 | #: src/login/login_util.rs:78 274 | msgid "Don't specify '{}' as part of the URL." 275 | msgstr "請不要將 '{}' 包含在網址中。" 276 | 277 | #: src/login/login_util.rs:91 278 | msgid "Username" 279 | msgstr "使用者名稱" 280 | 281 | #: src/login/login_util.rs:97 282 | msgid "Password" 283 | msgstr "密碼" 284 | 285 | #: src/login/login_util.rs:104 286 | msgid "2FA Code" 287 | msgstr "兩步驟驗證碼" 288 | 289 | #: src/login/login_util.rs:113 290 | msgid "The provided 2FA code is invalid (should only contain digits)." 291 | msgstr "提供的兩步驟驗證碼是無效的(應該只有數字)。" 292 | 293 | #: src/login/login_util.rs:118 294 | msgid "The provided 2FA code is invalid (should be 6 digits long)." 295 | msgstr "提供的兩步驟驗證碼是無效的(應該是六碼長)。" 296 | 297 | #: src/login/login_util.rs:142 298 | msgid "Log in" 299 | msgstr "登入" 300 | 301 | #: src/login/mod.rs:77 302 | msgid "Unable to connect to the server. Check your internet connection and try again." 303 | msgstr "無法連線到伺服器。請檢查您的網路連線然後再試一次。" 304 | 305 | #: src/login/mod.rs:80 306 | msgid "A 2FA code is required to log in to this account. Provide one and try again." 307 | msgstr "需要提供兩步驟驗證碼才能登入此帳戶。請提供驗證碼再試一次。" 308 | 309 | #: src/login/mod.rs:83 310 | msgid "Unable to authenticate to the server. Check your login credentials and try again." 311 | msgstr "無法與伺服器進行驗證。請檢查您的登入資訊然後再試一次。" 312 | 313 | #: src/login/mod.rs:87 314 | msgid "Unable to log in" 315 | msgstr "無法登入" 316 | 317 | #. The dropdown for selecting the server type. 318 | #: src/login/mod.rs:124 319 | msgid "Server Type" 320 | msgstr "伺服器類型" 321 | 322 | #: src/tray.rs:13 323 | msgid "Awaiting sync checks..." 324 | msgstr "正在等待同步檢查..." 325 | 326 | #: src/tray.rs:60 327 | msgid "Open" 328 | msgstr "開啟" 329 | 330 | #: src/tray.rs:67 331 | msgid "Close" 332 | msgstr "關閉" 333 | 334 | #: src/main.rs:188 335 | msgid "Unknown Error" 336 | msgstr "未知錯誤" 337 | 338 | #: src/main.rs:195 339 | msgid "An unknown error has occurred while running. This is an internal issue with Celeste and should be reported.\n" 340 | "\n" 341 | "The following backtrace may help with debugging the issue - note that it may contain information such as login tokens/keys, so avoid posting the information publicly:" 342 | msgstr "" 343 | "執行時發生了未知錯誤。這是 Celeste 的內部錯誤而應被回報。\n" 344 | "\n" 345 | "以下回溯追蹤可能可以幫忙為此問題偵錯 — " 346 | "請注意,它可能包含登入權杖或金鑰等秘密資訊,所以請不要將它公開:" 347 | -------------------------------------------------------------------------------- /po/tr.po: -------------------------------------------------------------------------------- 1 | # SOME DESCRIPTIVE TITLE. 2 | # Copyright (C) YEAR Hunter Wittenborn 3 | # This file is distributed under the same license as the Celeste package. 4 | # FIRST AUTHOR , YEAR. 5 | # 6 | msgid "" 7 | msgstr "" 8 | "Project-Id-Version: Celeste 0.5.2\n" 9 | "Report-Msgid-Bugs-To: \n" 10 | "POT-Creation-Date: 2023-05-01 18:51+0000\n" 11 | "PO-Revision-Date: 2023-08-20 16:52+0000\n" 12 | "Last-Translator: Sabri Ünal \n" 13 | "Language-Team: Turkish \n" 15 | "Language: tr\n" 16 | "MIME-Version: 1.0\n" 17 | "Content-Type: text/plain; charset=UTF-8\n" 18 | "Content-Transfer-Encoding: 8bit\n" 19 | "Plural-Forms: nplurals=2; plural=n != 1;\n" 20 | "X-Generator: Weblate 5.0-dev\n" 21 | 22 | #: celeste/src/gtk_util.rs:20 celeste/src/gtk_util.rs:43 celeste/src/launch.rs:678 celeste/src/launch.rs:840 23 | msgid "Ok" 24 | msgstr "Tamam." 25 | 26 | #: celeste/src/launch.rs:102 27 | msgid "Both '{}' and '{}' are more recent than at last sync." 28 | msgstr "Hem '{}' hem de '{}' son eşzamanlamadan daha yeni." 29 | 30 | #: celeste/src/launch.rs:214 31 | msgid "Unable to create Celeste's config directory [{}]." 32 | msgstr "Celeste'nin yapılandırma dizini oluşturulamıyor [{}]." 33 | 34 | #: celeste/src/launch.rs:226 35 | msgid "Unable to create Celeste's database file [{}]." 36 | msgstr "Celeste'nin veri tabanı dosyası oluşturulamıyor [{}]." 37 | 38 | #: celeste/src/launch.rs:236 39 | msgid "Unable to connect to database [{}]." 40 | msgstr "Veri tabanına bağlanılamıyor [{}]." 41 | 42 | #: celeste/src/launch.rs:244 43 | msgid "Unable to run database migrations [{}]" 44 | msgstr "Veri tabanı geçişleri çalıştırılamıyor [{}]" 45 | 46 | #: celeste/src/launch.rs:351 47 | msgid "Awaiting sync check..." 48 | msgstr "Eşzamanlama denetimi bekleniyor..." 49 | 50 | #: celeste/src/launch.rs:387 51 | msgid "Sync Errors" 52 | msgstr "Eşzamanlama Hataları" 53 | 54 | #: celeste/src/launch.rs:402 55 | msgid "File/Folder Exclusions" 56 | msgstr "Dosya/Klasör Dışlamaları" 57 | 58 | #: celeste/src/launch.rs:521 59 | msgid "Stop syncing this directory" 60 | msgstr "Bu dizini eşzamanlamayı durdur" 61 | 62 | #: celeste/src/launch.rs:538 63 | msgid "Are you sure you want to stop syncing '{}' to '{}'?" 64 | msgstr "" 65 | "'{}' ile '{}' arasında eşzamanlamayı durdurmak istediğinizden emin misiniz?" 66 | 67 | #: celeste/src/launch.rs:547 68 | msgid "This directory is currently being processed to no longer be synced." 69 | msgstr "Bu dizin şu anda artık eşzamanlanmamak üzere işlenmektedir." 70 | 71 | #: celeste/src/launch.rs:638 72 | msgid "Directories" 73 | msgstr "Dizinler" 74 | 75 | #. Get the local folder to sync with. 76 | #: celeste/src/launch.rs:661 77 | msgid "Local folder:" 78 | msgstr "Yerel klasör:" 79 | 80 | #: celeste/src/launch.rs:677 celeste/src/launch.rs:839 celeste/src/login/gdrive.rs:207 81 | msgid "Cancel" 82 | msgstr "İptal" 83 | 84 | #. Get the remote folder to sync with, and add it. 85 | #. The entry completion code is largely inspired by https://github.com/gtk-rs/gtk4-rs/blob/master/examples/entry_completion/main.rs. I honestly have no clue what half the code for that is doing, I just know the current code is working well enough, and it can be fixed later if it breaks. 86 | #: celeste/src/launch.rs:697 87 | msgid "Remote folder:" 88 | msgstr "Uzak klasör:" 89 | 90 | #: celeste/src/launch.rs:884 91 | msgid "The specified remote directory doesn't exist" 92 | msgstr "Belirtilen uzak dizin yok" 93 | 94 | #: celeste/src/launch.rs:892 95 | msgid "Failed to check if the specified remote directory exists" 96 | msgstr "Belirtilen uzak dizinin varlığı denetlenemedi" 97 | 98 | #: celeste/src/launch.rs:903 99 | msgid "The specified directory pair is already being synced" 100 | msgstr "Belirtilen dizin çifti zaten eşzamanlanıyor" 101 | 102 | #: celeste/src/launch.rs:906 103 | msgid "The specified local directory doesn't exist" 104 | msgstr "Belirtilen yerel dizin yok" 105 | 106 | #: celeste/src/launch.rs:909 107 | msgid "The specified local path isn't a directory" 108 | msgstr "Belirtilen yerel yol dizin değil" 109 | 110 | #: celeste/src/launch.rs:912 111 | msgid "The specified local directory needs to be an absolute path" 112 | msgstr "Belirtilen yerel dizinin mutlak bir yol olması gerekir" 113 | 114 | #: celeste/src/launch.rs:940 115 | msgid "Are you sure you want to delete this remote?" 116 | msgstr "Bu uzağı silmek istediğinizden emin misiniz?" 117 | 118 | #: celeste/src/launch.rs:941 119 | msgid "All the directories associated with this remote will also stop syncing." 120 | msgstr "Bu uzakla ilişkili tüm dizinlerin de eşitlenmesi durduracak." 121 | 122 | #: celeste/src/launch.rs:1167 celeste-tray/src/main.rs:51 123 | msgid "Awaiting sync checks..." 124 | msgstr "Eşzamanlama denetimleri bekleniyor..." 125 | 126 | #. Notify the tray app that we're syncing this remote now. 127 | #: celeste/src/launch.rs:1263 128 | msgid "Syncing '{}'..." 129 | msgstr "'{}' eşitleniyor..." 130 | 131 | #: celeste/src/launch.rs:1298 132 | msgid "Checking for changes..." 133 | msgstr "Değişiklikler denetleniyor..." 134 | 135 | #. Add an error for reporting in the UI. 136 | #: celeste/src/launch.rs:1305 137 | msgid "Please resolve the reported syncing issues." 138 | msgstr "Lütfen bildirilen eşzamanlama sorunlarını çözün." 139 | 140 | #: celeste/src/launch.rs:1332 141 | msgid "{} errors found. " 142 | msgstr "{} hata bulundu. " 143 | 144 | #: celeste/src/launch.rs:1346 145 | msgid "Would you like to dismiss this error?" 146 | msgstr "Bu hatayı gözden çıkarmak ister misiniz?" 147 | 148 | #: celeste/src/launch.rs:1373 149 | msgid "Failed to sync '{}' to '{}' on remote." 150 | msgstr "'{}', '{}' uzağı ile eşitlenemedi." 151 | 152 | #: celeste/src/launch.rs:1381 153 | msgid "Failed to sync '{}' on remote to '{}'." 154 | msgstr "'{}', '{}' uzağına eşitlenemedi." 155 | 156 | #: celeste/src/launch.rs:1406 157 | msgid "Unable to fetch data for '{}' from the remote." 158 | msgstr "'{}' uzağı için veri alınamadı." 159 | 160 | #: celeste/src/launch.rs:1415 celeste/src/launch.rs:1420 celeste/src/launch.rs:1428 161 | msgid "File Update" 162 | msgstr "Dosya Güncelle" 163 | 164 | #: celeste/src/launch.rs:1415 165 | msgid "Neither the local item or remote item exists anymore. This error will now be removed." 166 | msgstr "Yerel öge de, uzak öge artık yok. Bu hata şimdi kaldırılacak." 167 | 168 | #: celeste/src/launch.rs:1420 169 | msgid "Only the local item exists now, so it will be synced to the remote." 170 | msgstr "Şu anda yalnızca yerel öge var, bu nedenle uzakla eşitlenecek." 171 | 172 | #: celeste/src/launch.rs:1428 173 | msgid "Only the remote item exists now, so it will be synced to the local machine." 174 | msgstr "Şu anda yalnızca uzak öge var, dolayısıyla yerel makineyle eşitlenecek." 175 | 176 | #: celeste/src/launch.rs:1438 177 | msgid "Both the local item '{}' and remote item '{}' have been updated since the last sync." 178 | msgstr "" 179 | "Son eşzamanlamadan sonra hem yerel '{}' ögesi hem de uzak '{}' ögesi " 180 | "güncellendi." 181 | 182 | #: celeste/src/launch.rs:1440 183 | msgid "Which item would you like to keep?" 184 | msgstr "Hangi ögeyi korumak istersiniz?" 185 | 186 | #: celeste/src/launch.rs:1442 187 | msgid "Local" 188 | msgstr "Yerel" 189 | 190 | #: celeste/src/launch.rs:1443 191 | msgid "Remote" 192 | msgstr "Uzak" 193 | 194 | #: celeste/src/launch.rs:1492 195 | msgid "1 error found." 196 | msgstr "1 hata bulundu." 197 | 198 | #: celeste/src/launch.rs:1494 199 | msgid "{} errors found." 200 | msgstr "{} hata bulundu." 201 | 202 | #: celeste/src/launch.rs:1626 203 | msgid "Checking '{}' for changes..." 204 | msgstr "'{}' değişiklikleri denetleniyor..." 205 | 206 | #: celeste/src/launch.rs:2042 207 | msgid "Checking '{}' on remote for changes..." 208 | msgstr "Değişiklikler için '{}' uzağı denetleniyor..." 209 | 210 | #: celeste/src/launch.rs:2433 211 | msgid "Directory has finished sync checks." 212 | msgstr "Dizinler eşzamanlama denetimleri tamamlandı." 213 | 214 | #: celeste/src/launch.rs:2454 215 | msgid "Finished sync checks with {} errors." 216 | msgstr "Eşzamanlama denetimleri {} hatayla tamamlandı." 217 | 218 | #: celeste/src/login/gdrive.rs:204 219 | msgid "Authenticating to {}..." 220 | msgstr "{} kimlik doğrulanıyor..." 221 | 222 | #: celeste/src/login/gdrive.rs:205 223 | msgid "Follow the link that opened in your browser, and come back once you've finished." 224 | msgstr "" 225 | "Tarayıcınızda açılan bağlantıyı takip edin ve işlem tamamlandığında geri " 226 | "gelin." 227 | 228 | #: celeste/src/login/gdrive.rs:232 229 | msgid "There was an issue while running the webserver for authentication" 230 | msgstr "Kimlik doğrulama için web sunucusu çalıştırılırken hata oluştu" 231 | 232 | #: celeste/src/login/gdrive.rs:241 233 | msgid "There was an issue authenticating to {}" 234 | msgstr "{} için kimlik doğrulanırken hata oluştu" 235 | 236 | #: celeste/src/login/login_util.rs:14 237 | msgid "Server Name" 238 | msgstr "Sunucu Adı" 239 | 240 | #: celeste/src/login/login_util.rs:26 241 | msgid "Server name already exists." 242 | msgstr "Sunucu adı zaten var." 243 | 244 | #: celeste/src/login/login_util.rs:28 245 | msgid "Invalid server name. Server names must:\n" 246 | "- Only contain numbers, letters, '_', '-', '.', and spaces\n" 247 | "- Not start with '-' or a space\n" 248 | "- Not end with a space" 249 | msgstr "" 250 | "Geçersiz sunucu adı. Sunucu adları:\n" 251 | "- Yalnızca sayı, harf, '_', '-', '.', ve boşluk içerebilir\n" 252 | "- '-' veya boşluk ile başlayamaz\n" 253 | "- Boşluk ile bitemez\n" 254 | "- Türkçe karakter gibi yabancı karakter içeremez" 255 | 256 | #: celeste/src/login/login_util.rs:42 257 | msgid "Server URL" 258 | msgstr "Sunucu URL" 259 | 260 | #: celeste/src/login/login_util.rs:48 261 | msgid "Invalid server URL ({})." 262 | msgstr "Geçersiz sunucu URL ({})." 263 | 264 | #: celeste/src/login/login_util.rs:57 265 | msgid "Invalid server URL (no domain specified)." 266 | msgstr "Geçersiz sunucu URL (alan adı belirtilmedi)." 267 | 268 | #: celeste/src/login/login_util.rs:61 269 | msgid "Invalid server URL (password was specified)." 270 | msgstr "Geçersiz sunucu URL (parola belirtildi)." 271 | 272 | #: celeste/src/login/login_util.rs:65 273 | msgid "Invalid server URL(unknown server scheme {})." 274 | msgstr "Geçersiz sunucu URL (bilinmeyen sunucu şeması)." 275 | 276 | #: celeste/src/login/login_util.rs:77 277 | msgid "Don't specify '{}' as part of the URL." 278 | msgstr "URL parçası olarak '{}' belirtmeyin." 279 | 280 | #: celeste/src/login/login_util.rs:90 281 | msgid "Username" 282 | msgstr "Kullanıcı Adı" 283 | 284 | #: celeste/src/login/login_util.rs:96 285 | msgid "Password" 286 | msgstr "Parola" 287 | 288 | #: celeste/src/login/login_util.rs:102 289 | msgid "Log in" 290 | msgstr "Giriş" 291 | 292 | #: celeste/src/login/mod.rs:72 293 | msgid "Unable to connect to the server. Check your internet connection and try again." 294 | msgstr "Sunucuya bağlanamıyor. İnternet bağlantınızı denetleyip tekrar deneyin." 295 | 296 | #: celeste/src/login/mod.rs:76 297 | msgid "Unable to authenticate to the server. Check your login credentials and try again." 298 | msgstr "" 299 | "Sunucuda kimlik doğrulanamıyor. Giriş kimlik bilgilerinizi denetleyip tekrar " 300 | "deneyin." 301 | 302 | #: celeste/src/login/mod.rs:80 303 | msgid "Unable to log in" 304 | msgstr "Giriş yapılamıyor" 305 | 306 | #. The dropdown for selecting the server type. 307 | #: celeste/src/login/mod.rs:116 308 | msgid "Server Type" 309 | msgstr "Sunucu Türü" 310 | 311 | #: celeste/src/main.rs:187 312 | msgid "Unknown Error" 313 | msgstr "Bilinmeyen Hata" 314 | 315 | #: celeste/src/main.rs:194 316 | msgid "An unknown error has occurred while running. This is an internal issue with Celeste and should be reported.\n" 317 | "\n" 318 | "The following backtrace may help with debugging the issue - note that it may contain information such as login tokens/keys, so avoid posting the information publicly:" 319 | msgstr "" 320 | "Çalışırken bilinmeyen bir hata oluştu. Bu, Celesteʼnin içsel bir sorunudur " 321 | "ve bildirilmesi gerekir.\n" 322 | "\n" 323 | "Aşağıdaki hata çıktısı, hata ayıklamaya yardımcı olabilir - oturum açma " 324 | "jetonları/anahtarları gibi bilgiler içerebileceğini unutmayın, bu nedenle " 325 | "bilgileri herkese açık olarak yayınlamaktan kaçının:" 326 | 327 | #: celeste-tray/src/main.rs:54 328 | msgid "Open" 329 | msgstr "Aç" 330 | 331 | #: celeste-tray/src/main.rs:55 332 | msgid "Quit" 333 | msgstr "Çık" 334 | 335 | #: celeste-tray/src/main.rs:116 336 | msgid "Quitting..." 337 | msgstr "Çıkış yapılıyor..." 338 | -------------------------------------------------------------------------------- /po/hu.po: -------------------------------------------------------------------------------- 1 | # SOME DESCRIPTIVE TITLE. 2 | # Copyright (C) YEAR Hunter Wittenborn 3 | # This file is distributed under the same license as the Celeste package. 4 | # FIRST AUTHOR , YEAR. 5 | # 6 | msgid "" 7 | msgstr "" 8 | "Project-Id-Version: Celeste 0.8.1\n" 9 | "Report-Msgid-Bugs-To: \n" 10 | "POT-Creation-Date: 2023-11-19 01:22+0000\n" 11 | "PO-Revision-Date: 2024-03-01 09:00+0000\n" 12 | "Last-Translator: H Tamás \n" 13 | "Language-Team: Hungarian \n" 15 | "Language: hu\n" 16 | "MIME-Version: 1.0\n" 17 | "Content-Type: text/plain; charset=UTF-8\n" 18 | "Content-Transfer-Encoding: 8bit\n" 19 | "Plural-Forms: nplurals=2; plural=n != 1;\n" 20 | "X-Generator: Weblate 5.5-dev\n" 21 | 22 | #: src/about.rs:20 23 | msgid "App icons by" 24 | msgstr "App ikonokat készítette" 25 | 26 | #: src/gtk_util.rs:20 src/gtk_util.rs:43 src/launch.rs:614 src/launch.rs:776 27 | msgid "Ok" 28 | msgstr "Ok" 29 | 30 | #: src/launch.rs:99 31 | msgid "Both '{}' and '{}' are more recent than at last sync." 32 | msgstr "'{}' és '{}' frissebbek mint a legutolsó szinkelt." 33 | 34 | #: src/launch.rs:158 35 | msgid "Unable to create Celeste's config directory [{}]." 36 | msgstr "A Celeste konfig mappájának létrehozása sikertelen [{}]." 37 | 38 | #: src/launch.rs:170 39 | msgid "Unable to create Celeste's database file [{}]." 40 | msgstr "Sikertelen a Celesete adatbázis fájl létrehozása [{}]." 41 | 42 | #: src/launch.rs:180 43 | msgid "Unable to connect to database [{}]." 44 | msgstr "Sikertelen adatbázishoz való csatlakozás [{}]." 45 | 46 | #: src/launch.rs:188 47 | msgid "Unable to run database migrations [{}]" 48 | msgstr "Sikertelen adatbázis migráció futtatás [{}]" 49 | 50 | #: src/launch.rs:288 51 | msgid "Awaiting sync check..." 52 | msgstr "Szinkelési ellenőrzésre várás..." 53 | 54 | #: src/launch.rs:324 55 | msgid "Sync Errors" 56 | msgstr "Szinkronizációs Hibák" 57 | 58 | #: src/launch.rs:339 59 | msgid "File/Folder Exclusions" 60 | msgstr "Fájl/Mappa Kivételek" 61 | 62 | #: src/launch.rs:458 63 | msgid "Stop syncing this directory" 64 | msgstr "Ne szinkelje tovább ezt a mappát" 65 | 66 | #: src/launch.rs:475 67 | msgid "Are you sure you want to stop syncing '{}' to '{}'?" 68 | msgstr "Biztos abban, hogy a '{}'-t nem szinkeli tovább '{}'-ba?" 69 | 70 | #: src/launch.rs:484 71 | msgid "This directory is currently being processed to no longer be synced." 72 | msgstr "Ez a mappa jelenleg szinkelés megszüntetési folyamat alatt áll." 73 | 74 | #: src/launch.rs:574 75 | msgid "Directories" 76 | msgstr "Mappák" 77 | 78 | #. Get the local folder to sync with. 79 | #: src/launch.rs:597 80 | msgid "Local folder:" 81 | msgstr "Helyi mappa:" 82 | 83 | #: src/launch.rs:613 src/launch.rs:775 src/login/gdrive.rs:214 84 | msgid "Cancel" 85 | msgstr "Mégse" 86 | 87 | #. Get the remote folder to sync with, and add it. 88 | #. The entry completion code is largely inspired by https://github.com/gtk-rs/gtk4-rs/blob/master/examples/entry_completion/main.rs. I honestly have no clue what half the code for that is doing, I just know the current code is working well enough, and it can be fixed later if it breaks. 89 | #: src/launch.rs:633 90 | msgid "Remote folder:" 91 | msgstr "Távoli mappa:" 92 | 93 | #: src/launch.rs:820 94 | msgid "The specified remote directory doesn't exist" 95 | msgstr "A meghatározott távoli mappa nem létezik" 96 | 97 | #: src/launch.rs:828 98 | msgid "Failed to check if the specified remote directory exists" 99 | msgstr "Nem sikerült ellenőrizni a távoli mappa létezését" 100 | 101 | #: src/launch.rs:839 102 | msgid "The specified directory pair is already being synced" 103 | msgstr "A megadott mappa páros már alapból szinkelődik" 104 | 105 | #: src/launch.rs:842 106 | msgid "The specified local directory doesn't exist" 107 | msgstr "A megadott helyi mappa nem létezik" 108 | 109 | #: src/launch.rs:845 110 | msgid "The specified local path isn't a directory" 111 | msgstr "A megadott helyi elérési útvonal nem mappára mutat" 112 | 113 | #: src/launch.rs:848 114 | msgid "The specified local directory needs to be an absolute path" 115 | msgstr "A megadott helyi mappa abszolút elérési úttal kell rendelkezzen" 116 | 117 | #: src/launch.rs:876 118 | msgid "Are you sure you want to delete this remote?" 119 | msgstr "Biztosan ki akarja törölni ezt a távoli elérést?" 120 | 121 | #: src/launch.rs:877 122 | msgid "All the directories associated with this remote will also stop syncing." 123 | msgstr "Minden ezzel a távoli eléréssel kapcsolatos mappa szinkelése leáll." 124 | 125 | #: src/launch.rs:1342 src/launch.rs:2509 126 | msgid "Files are synced." 127 | msgstr "A fájlok szinkeltek." 128 | 129 | #: src/launch.rs:1352 130 | msgid "Syncing '{}'..." 131 | msgstr "'{}' szinkelése..." 132 | 133 | #: src/launch.rs:1373 134 | msgid "Checking for changes..." 135 | msgstr "Változások keresése..." 136 | 137 | #. Add an error for reporting in the UI. 138 | #: src/launch.rs:1380 139 | msgid "Please resolve the reported syncing issues." 140 | msgstr "Kérem oldja meg a jelentett szinkelési hibákat." 141 | 142 | #: src/launch.rs:1407 143 | msgid "{} errors found. " 144 | msgstr "{} hibát találtunk. " 145 | 146 | #: src/launch.rs:1421 147 | msgid "Would you like to dismiss this error?" 148 | msgstr "Szeretne eltekinteni ettől a hibától?" 149 | 150 | #: src/launch.rs:1448 151 | msgid "Failed to sync '{}' to '{}' on remote." 152 | msgstr "'{}' szinkelése sikertelen ovlt '{}' o felé a távoli kapcsolatban." 153 | 154 | #: src/launch.rs:1456 155 | msgid "Failed to sync '{}' on remote to '{}'." 156 | msgstr "'{}' sikertelenül szinkelt '{}' távolin." 157 | 158 | #: src/launch.rs:1481 159 | msgid "Unable to fetch data for '{}' from the remote." 160 | msgstr "'{}' adatinak lekérdezése nem lehetséges a távoli kapcsolatról." 161 | 162 | #: src/launch.rs:1490 src/launch.rs:1495 src/launch.rs:1503 163 | msgid "File Update" 164 | msgstr "Fájl Frissítés" 165 | 166 | #: src/launch.rs:1490 167 | msgid "Neither the local item or remote item exists anymore. This error will now be removed." 168 | msgstr "" 169 | "Sem a helyi sem a távoli elem nem áll rendelkezésre. Ez a hiba törlésre " 170 | "kerül." 171 | 172 | #: src/launch.rs:1495 173 | msgid "Only the local item exists now, so it will be synced to the remote." 174 | msgstr "Csak a helyi elem létezik, szinkelődni fog a távolira is." 175 | 176 | #: src/launch.rs:1503 177 | msgid "Only the remote item exists now, so it will be synced to the local machine." 178 | msgstr "Csak a távoli elem létezik, ezért lokálisan is szinkelődni fog." 179 | 180 | #: src/launch.rs:1513 181 | msgid "Both the local item '{}' and remote item '{}' have been updated since the last sync." 182 | msgstr "" 183 | "Mind a helyi '{}' elem, mind a távoli '{}' elem frissítésre került a " 184 | "legutóbbi szinkronizáció óta." 185 | 186 | #: src/launch.rs:1515 187 | msgid "Which item would you like to keep?" 188 | msgstr "Melyik elemet kívánja meghagyni?" 189 | 190 | #: src/launch.rs:1517 191 | msgid "Local" 192 | msgstr "Helyi" 193 | 194 | #: src/launch.rs:1518 195 | msgid "Remote" 196 | msgstr "Távoli" 197 | 198 | #: src/launch.rs:1567 199 | msgid "1 error found." 200 | msgstr "1 hibát találtunk." 201 | 202 | #: src/launch.rs:1569 203 | msgid "{} errors found." 204 | msgstr "{} hibát találtunk." 205 | 206 | #: src/launch.rs:1701 207 | msgid "Checking '{}' for changes..." 208 | msgstr "'{}' változásainak ellenőrzése..." 209 | 210 | #: src/launch.rs:2117 211 | msgid "Checking '{}' on remote for changes..." 212 | msgstr "'{}' távoli változások ellenőrzése..." 213 | 214 | #: src/launch.rs:2530 215 | msgid "Finished sync checks with {} errors." 216 | msgstr "{} hibával zárult a szikelés ellenőrzés." 217 | 218 | #: src/launch.rs:2535 219 | msgid "Finished sync checks." 220 | msgstr "Szink ellenőrzés befejeződött." 221 | 222 | #: src/login/gdrive.rs:211 223 | msgid "Authenticating to {}..." 224 | msgstr "Hitelesítése {}-be..." 225 | 226 | #: src/login/gdrive.rs:212 227 | msgid "Follow the link that opened in your browser, and come back once you've finished." 228 | msgstr "Kövesse a böngészőben megnyíló linket, majd térjen vissza ha végzett." 229 | 230 | #: src/login/gdrive.rs:239 231 | msgid "There was an issue while running the webserver for authentication" 232 | msgstr "Hiba lépett fel a webszerver hitelesítési futtatása során" 233 | 234 | #: src/login/gdrive.rs:248 235 | msgid "There was an issue authenticating to {}" 236 | msgstr "Hiba lépett fel a {} hitelesítése során" 237 | 238 | #: src/login/login_util.rs:15 239 | msgid "Name" 240 | msgstr "Név" 241 | 242 | #: src/login/login_util.rs:27 243 | msgid "Name already exists." 244 | msgstr "Már létező név." 245 | 246 | #: src/login/login_util.rs:29 247 | msgid "Invalid name. Names must:\n" 248 | "- Only contain numbers, letters, '_', '-', '.', and spaces\n" 249 | "- Not start with '-' or a space\n" 250 | "- Not end with a space" 251 | msgstr "" 252 | "Érvénytelen név. Tartalmaznia kell:\n" 253 | "- Csak számokat, betűket, '_', '-', '.', és szóközt tartalmazhat\n" 254 | "- Nem kezdődhet szóközzel vagy kötőjellel\n" 255 | "- Nem végződhet szóközzel" 256 | 257 | #: src/login/login_util.rs:43 258 | msgid "Server URL" 259 | msgstr "Szerver elérési cím" 260 | 261 | #: src/login/login_util.rs:49 262 | msgid "Invalid server URL ({})." 263 | msgstr "Hibás szerver elérési cím ({})." 264 | 265 | #: src/login/login_util.rs:58 266 | msgid "Invalid server URL (no domain specified)." 267 | msgstr "Hibás szerver elérési cím (domain nem került megadásra)." 268 | 269 | #: src/login/login_util.rs:62 270 | msgid "Invalid server URL (password was specified)." 271 | msgstr "Hibás szerver elérési cím (megadott jelszó)." 272 | 273 | #: src/login/login_util.rs:66 274 | msgid "Invalid server URL(unknown server scheme {})." 275 | msgstr "Hibás szerver elérési cím (ismeretlen szerver séma {})." 276 | 277 | #: src/login/login_util.rs:78 278 | msgid "Don't specify '{}' as part of the URL." 279 | msgstr "Ne adja meg a '{}'-t a címben." 280 | 281 | #: src/login/login_util.rs:91 282 | msgid "Username" 283 | msgstr "Felhasználónév" 284 | 285 | #: src/login/login_util.rs:97 286 | msgid "Password" 287 | msgstr "Jelszó" 288 | 289 | #: src/login/login_util.rs:104 290 | msgid "2FA Code" 291 | msgstr "2FA Kód" 292 | 293 | #: src/login/login_util.rs:113 294 | msgid "The provided 2FA code is invalid (should only contain digits)." 295 | msgstr "A megadott 2FA kód helytelen (csak számokat tartalmazhat)." 296 | 297 | #: src/login/login_util.rs:118 298 | msgid "The provided 2FA code is invalid (should be 6 digits long)." 299 | msgstr "A megadott 2FA kód helytelen (csak 6 karakter hosszú lehet)." 300 | 301 | #: src/login/login_util.rs:142 302 | msgid "Log in" 303 | msgstr "Bejelentkezés" 304 | 305 | #: src/login/mod.rs:77 306 | msgid "Unable to connect to the server. Check your internet connection and try again." 307 | msgstr "" 308 | "A szerverhez való csatlakozás sikertelen. Kérem ellenőrizze az internet " 309 | "kapcsolatát és próbálja újra." 310 | 311 | #: src/login/mod.rs:80 312 | msgid "A 2FA code is required to log in to this account. Provide one and try again." 313 | msgstr "" 314 | "2FA kód szükséges a fiókba való belépéshez. Kérem írja be a kódot és " 315 | "próbálja újra." 316 | 317 | #: src/login/mod.rs:83 318 | msgid "Unable to authenticate to the server. Check your login credentials and try again." 319 | msgstr "" 320 | "Sikertelen hitelesítés a szerver felé. Ellenőrizze a bejelentkezési adatait, " 321 | "és próbálja újra." 322 | 323 | #: src/login/mod.rs:87 324 | msgid "Unable to log in" 325 | msgstr "Sikertelen bejelentkezés" 326 | 327 | #. The dropdown for selecting the server type. 328 | #: src/login/mod.rs:124 329 | msgid "Server Type" 330 | msgstr "Szerver Típus" 331 | 332 | #: src/tray.rs:13 333 | msgid "Awaiting sync checks..." 334 | msgstr "Várakozás a szinkelési ellenőrzésre..." 335 | 336 | #: src/tray.rs:60 337 | msgid "Open" 338 | msgstr "Megnyitás" 339 | 340 | #: src/tray.rs:67 341 | msgid "Close" 342 | msgstr "Bezárás" 343 | 344 | #: src/main.rs:188 345 | msgid "Unknown Error" 346 | msgstr "Ismeretlen Hiba" 347 | 348 | #: src/main.rs:195 349 | msgid "An unknown error has occurred while running. This is an internal issue with Celeste and should be reported.\n" 350 | "\n" 351 | "The following backtrace may help with debugging the issue - note that it may contain information such as login tokens/keys, so avoid posting the information publicly:" 352 | msgstr "" 353 | "A futás során ismetelen hiba lépett fel. Ez egy belső Celeste hiba, emiatt " 354 | "jelentse be a hibát.\n" 355 | "\n" 356 | "A következő visszakövetés segíthet a hiba debugolásában - vegye figyelembe, " 357 | "hogy az információ tartalmazhat bejelentkezési tokent/kulcsokat, ezért " 358 | "tartózkodjon publikálásuktól:" 359 | -------------------------------------------------------------------------------- /src/login/gdrive.rs: -------------------------------------------------------------------------------- 1 | //! The data for a Google Drive Rclone config. 2 | use super::ServerType; 3 | use crate::{ 4 | gtk_util, 5 | login::{dropbox, login_util, pcloud}, 6 | mpsc::Sender, 7 | traits::prelude::*, 8 | util, 9 | }; 10 | use adw::{glib, gtk::Button, prelude::*, ApplicationWindow, EntryRow, MessageDialog}; 11 | use nix::{ 12 | sys::signal::{self, Signal}, 13 | unistd::Pid, 14 | }; 15 | use rocket::response::{content::RawHtml, Responder}; 16 | use std::{ 17 | cell::RefCell, 18 | fmt, 19 | io::{BufRead, BufReader}, 20 | process::{Command, Stdio}, 21 | rc::Rc, 22 | sync::{Arc, Mutex}, 23 | thread, 24 | time::Duration, 25 | }; 26 | use tera::{Context, Tera}; 27 | 28 | static DEFAULT_CLIENT_ID: &str = 29 | "617798216802-gpgajsc7o768ukbdegk5esa3jf6aekgj.apps.googleusercontent.com"; 30 | static DEFAULT_CLIENT_SECRET: &str = "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"; 31 | 32 | // The server type we're generating. 33 | pub enum AuthType { 34 | Dropbox, 35 | GDrive, 36 | PCloud, 37 | } 38 | 39 | impl fmt::Display for AuthType { 40 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 41 | write!( 42 | f, 43 | "{}", 44 | match self { 45 | Self::Dropbox => "Dropbox", 46 | Self::GDrive => "Google Drive", 47 | Self::PCloud => "pCloud", 48 | } 49 | ) 50 | } 51 | } 52 | 53 | lazy_static::lazy_static! { 54 | // The state URL for the Rocket HTTP server to use in [`get_google_drive`] below. 55 | static ref STATE_URL: Mutex = Mutex::new(String::new()); 56 | } 57 | 58 | // Rocket routes for our Google Drive integration. 59 | #[rocket::get("/")] 60 | fn get_google_drive() -> RawHtml { 61 | let mut context = Context::new(); 62 | context.insert("state_url", STATE_URL.lock().unwrap().as_str()); 63 | RawHtml( 64 | Tera::one_off( 65 | include_str!("../html/google-drive.tera.html"), 66 | &context, 67 | true, 68 | ) 69 | .unwrap(), 70 | ) 71 | } 72 | 73 | #[derive(Responder)] 74 | #[response(content_type = "image/png")] 75 | struct PngResponse(&'static [u8]); 76 | 77 | #[rocket::get("/google-signin.png")] 78 | fn get_google_signin_png() -> PngResponse { 79 | PngResponse(include_bytes!("../images/google-signin.png")) 80 | } 81 | 82 | #[rocket::get("/google-drive.png")] 83 | fn get_google_drive_png() -> PngResponse { 84 | PngResponse(include_bytes!("../images/google-drive.png")) 85 | } 86 | 87 | #[derive(Clone, Debug, Default)] 88 | pub struct GDriveConfig { 89 | pub server_name: String, 90 | pub client_id: String, 91 | pub client_secret: String, 92 | pub auth_json: String, 93 | } 94 | 95 | impl super::LoginTrait for GDriveConfig { 96 | fn get_sections( 97 | window: &ApplicationWindow, 98 | sender: Sender>, 99 | ) -> (Vec, Button) { 100 | Self::auth_sections( 101 | window, 102 | sender, 103 | AuthType::GDrive, 104 | DEFAULT_CLIENT_ID.to_owned(), 105 | DEFAULT_CLIENT_SECRET.to_owned(), 106 | ) 107 | } 108 | } 109 | 110 | impl GDriveConfig { 111 | pub fn auth_sections( 112 | window: &ApplicationWindow, 113 | sender: Sender>, 114 | auth_type: AuthType, 115 | client_id: String, 116 | client_secret: String, 117 | ) -> (Vec, Button) { 118 | let mut sections: Vec = vec![]; 119 | let server_name = login_util::server_name_input(); 120 | let submit_button = login_util::submit_button(); 121 | 122 | sections.push(server_name.clone()); 123 | 124 | submit_button.connect_clicked(glib::clone!(@weak window, @weak server_name, @strong client_id, @strong client_secret => move |_| { 125 | window.set_sensitive(false); 126 | 127 | // For some reason we get compiler errors without these two lines :P. 128 | let client_id = client_id.clone(); 129 | let client_secret = client_secret.clone(); 130 | 131 | // Set up the rclone auth process. 132 | let mut args = vec!["authorize"]; 133 | args.push(match auth_type { 134 | AuthType::GDrive => "drive", 135 | AuthType::Dropbox => "dropbox", 136 | AuthType::PCloud => "pcloud", 137 | }); 138 | args.push(&client_id); 139 | args.push(&client_secret); 140 | if let AuthType::GDrive = auth_type { 141 | args.push("--auth-no-open-browser"); 142 | } 143 | 144 | let mut process = Command::new("rclone") 145 | .args(&args) 146 | .stdout(Stdio::piped()) 147 | .stderr(Stdio::piped()) 148 | .spawn() 149 | .unwrap(); 150 | let process_stdout = Arc::new(Mutex::new(String::new())); 151 | let process_stderr = Arc::new(Mutex::new(String::new())); 152 | let stdout_handle = process.stdout.take().unwrap(); 153 | let stderr_handle = process.stderr.take().unwrap(); 154 | let _stdout_thread = thread::spawn(glib::clone!(@strong process_stdout => move || { 155 | let reader = BufReader::new(stdout_handle); 156 | for line in reader.lines() { 157 | let string = line.unwrap(); 158 | process_stdout.lock().unwrap().push_str(&string); 159 | process_stdout.lock().unwrap().push('\n'); 160 | } 161 | })); 162 | let _stderr_thread = thread::spawn(glib::clone!(@strong process_stderr => move || { 163 | let reader = BufReader::new(stderr_handle); 164 | for line in reader.lines() { 165 | let string = line.unwrap(); 166 | process_stderr.lock().unwrap().push_str(&string); 167 | process_stderr.lock().unwrap().push('\n'); 168 | } 169 | })); 170 | 171 | // Get the URL rclone will use for authentication by reading the process' stderr. 172 | loop { 173 | // If the rclone process has already aborted, go ahead and break so we can show the error down below. 174 | if process.try_wait().unwrap().is_some() { 175 | break 176 | } 177 | 178 | // Otherwise check if the URL line has been printed, by checking for the auth URL. 179 | // 180 | // We check on both stdout and stderr, as rclone seems to report the authentication 181 | // URL differently depending on the version we're using. 182 | let process_output = format!( 183 | "{}\n{}", 184 | process_stdout.lock().unwrap(), 185 | process_stderr.lock().unwrap(), 186 | ); 187 | if let Some(line) = process_output.lines().find(|line| line.contains("http://127.0.0.1:53682/auth")) { 188 | // The URL will be the last space-separated item on the line. 189 | *STATE_URL.lock().unwrap() = line.split_whitespace().last().unwrap().to_owned(); 190 | break 191 | } 192 | 193 | util::run_in_background(|| thread::sleep(Duration::from_millis(500))); 194 | } 195 | 196 | hw_msg::warningln!("STATE URL: {}", STATE_URL.lock().unwrap()); 197 | let kill_request = Rc::new(RefCell::new(false)); 198 | 199 | // Set up and open the temporary HTTP server. 200 | let runtime = tokio::runtime::Runtime::new().unwrap(); 201 | let handle = runtime.spawn(rocket::build() 202 | .mount("/", rocket::routes![get_google_drive, get_google_signin_png, get_google_drive_png]) 203 | .launch() 204 | ); 205 | if let AuthType::GDrive = auth_type { 206 | Command::new("xdg-open").arg("http://localhost:8000").spawn().unwrap().wait().unwrap().exit_ok().unwrap(); 207 | } 208 | 209 | // Wait for input from the user. 210 | let dialog = MessageDialog::builder() 211 | .heading(&tr::tr!("Authenticating to {}...", auth_type)) 212 | .body(&tr::tr!("Follow the link that opened in your browser, and come back once you've finished.")) 213 | .build(); 214 | dialog.add_response("cancel", &tr::tr!("Cancel")); 215 | dialog.connect_response(None, glib::clone!(@strong kill_request => move |dialog, resp| { 216 | if resp != "cancel" { 217 | return 218 | } 219 | 220 | dialog.close(); 221 | *kill_request.get_mut_ref() = true; 222 | })); 223 | dialog.show(); 224 | 225 | // Run until the process exits or the user clicks 'Cancel'. 226 | loop { 227 | // Sleep a little so the UI has a chance to process. 228 | util::run_in_background(|| thread::sleep(Duration::from_millis(500))); 229 | 230 | // Check if the user clicked cancel. 231 | if *kill_request.get_ref() { 232 | handle.abort(); 233 | signal::kill(Pid::from_raw(process.id().try_into().unwrap()), Signal::SIGTERM).unwrap(); 234 | window.set_sensitive(true); 235 | break; 236 | // Otherwise if the temporary webserver has died off, report it. 237 | } else if handle.is_finished() { 238 | let error_string = util::await_future(handle).unwrap().unwrap_err().to_string(); 239 | gtk_util::show_codeblock_error(&tr::tr!("There was an issue while running the webserver for authentication"), &error_string); 240 | window.set_sensitive(true); 241 | break; 242 | // Otherwise if the command has finished, check if it returned a good exit code and then return it. 243 | } else if let Some(exit_status) = process.try_wait().unwrap() { 244 | handle.abort(); 245 | dialog.close(); 246 | 247 | if !exit_status.success() { 248 | gtk_util::show_codeblock_error(&tr::tr!("There was an issue authenticating to {}", auth_type), &process_stderr.lock().unwrap()); 249 | window.set_sensitive(true); 250 | break; 251 | } else { 252 | let auth_token = { 253 | let lines: Vec = process_stdout.lock().unwrap().lines().map(|string| string.to_owned()).collect(); 254 | lines.get(lines.len() - 2).unwrap().to_owned() 255 | }; 256 | 257 | let server_type = match auth_type { 258 | AuthType::GDrive => ServerType::GDrive(GDriveConfig { 259 | server_name: server_name.text().to_string(), 260 | client_id, 261 | client_secret, 262 | auth_json: auth_token 263 | }), 264 | AuthType::Dropbox => ServerType::Dropbox(dropbox::DropboxConfig { 265 | server_name: server_name.text().to_string(), 266 | client_id, 267 | client_secret, 268 | auth_json: auth_token 269 | }), 270 | AuthType::PCloud => ServerType::PCloud(pcloud::PCloudConfig { 271 | server_name: server_name.text().to_string(), 272 | client_id, 273 | client_secret, 274 | auth_json: auth_token 275 | }), 276 | }; 277 | sender.send(Some(server_type)); 278 | window.set_sensitive(true); 279 | break; 280 | } 281 | } 282 | } 283 | })); 284 | server_name.connect_changed(glib::clone!(@weak submit_button => move |server_name| login_util::check_responses(&[server_name], &submit_button))); 285 | 286 | (sections, submit_button) 287 | } 288 | } 289 | -------------------------------------------------------------------------------- /po/hi.po: -------------------------------------------------------------------------------- 1 | # SOME DESCRIPTIVE TITLE. 2 | # Copyright (C) YEAR Hunter Wittenborn 3 | # This file is distributed under the same license as the Celeste package. 4 | # FIRST AUTHOR , YEAR. 5 | # 6 | msgid "" 7 | msgstr "" 8 | "Project-Id-Version: Celeste 0.8.1\n" 9 | "Report-Msgid-Bugs-To: \n" 10 | "POT-Creation-Date: 2023-11-19 01:22+0000\n" 11 | "PO-Revision-Date: 2024-05-08 04:07+0000\n" 12 | "Last-Translator: Scrambled777 \n" 13 | "Language-Team: Hindi \n" 15 | "Language: hi\n" 16 | "MIME-Version: 1.0\n" 17 | "Content-Type: text/plain; charset=UTF-8\n" 18 | "Content-Transfer-Encoding: 8bit\n" 19 | "Plural-Forms: nplurals=2; plural=n > 1;\n" 20 | "X-Generator: Weblate 5.5.4-rc\n" 21 | 22 | #: src/about.rs:20 23 | msgid "App icons by" 24 | msgstr "ऐप आइकन इनके द्वारा" 25 | 26 | #: src/gtk_util.rs:20 src/gtk_util.rs:43 src/launch.rs:614 src/launch.rs:776 27 | msgid "Ok" 28 | msgstr "ठीक है" 29 | 30 | #: src/launch.rs:99 31 | msgid "Both '{}' and '{}' are more recent than at last sync." 32 | msgstr "'{}' और '{}' दोनों पिछले समन्वयन की तुलना में अधिक हाल के हैं।" 33 | 34 | #: src/launch.rs:158 35 | msgid "Unable to create Celeste's config directory [{}]." 36 | msgstr "Celeste की विन्यास निर्देशिका [{}] बनाने में असमर्थ।" 37 | 38 | #: src/launch.rs:170 39 | msgid "Unable to create Celeste's database file [{}]." 40 | msgstr "Celeste की डेटाबेस फाइल [{}] बनाने में असमर्थ।" 41 | 42 | #: src/launch.rs:180 43 | msgid "Unable to connect to database [{}]." 44 | msgstr "डेटाबेस से जुड़ने में असमर्थ है [{}]।" 45 | 46 | #: src/launch.rs:188 47 | msgid "Unable to run database migrations [{}]" 48 | msgstr "डेटाबेस प्रवासन चलाने में असमर्थ [{}]" 49 | 50 | #: src/launch.rs:288 51 | msgid "Awaiting sync check..." 52 | msgstr "समन्वयन जांच की प्रतीक्षा है…" 53 | 54 | #: src/launch.rs:324 55 | msgid "Sync Errors" 56 | msgstr "समन्वयन त्रुटियां" 57 | 58 | #: src/launch.rs:339 59 | msgid "File/Folder Exclusions" 60 | msgstr "फाइल/फोल्डर बहिष्करण" 61 | 62 | #: src/launch.rs:458 63 | msgid "Stop syncing this directory" 64 | msgstr "इस निर्देशिका को समन्वयित करना बंद करें" 65 | 66 | #: src/launch.rs:475 67 | msgid "Are you sure you want to stop syncing '{}' to '{}'?" 68 | msgstr "क्या आप वाकई '{}' को '{}' से समन्वयित करना बंद करना चाहते हैं?" 69 | 70 | #: src/launch.rs:484 71 | msgid "This directory is currently being processed to no longer be synced." 72 | msgstr "यह निर्देशिका वर्तमान में समन्वयित न होने के लिए संसाधित की जा रही है।" 73 | 74 | #: src/launch.rs:574 75 | msgid "Directories" 76 | msgstr "निर्देशिका" 77 | 78 | #. Get the local folder to sync with. 79 | #: src/launch.rs:597 80 | msgid "Local folder:" 81 | msgstr "स्थानीय फोल्डर:" 82 | 83 | #: src/launch.rs:613 src/launch.rs:775 src/login/gdrive.rs:214 84 | msgid "Cancel" 85 | msgstr "रद्द करें" 86 | 87 | #. Get the remote folder to sync with, and add it. 88 | #. The entry completion code is largely inspired by https://github.com/gtk-rs/gtk4-rs/blob/master/examples/entry_completion/main.rs. I honestly have no clue what half the code for that is doing, I just know the current code is working well enough, and it can be fixed later if it breaks. 89 | #: src/launch.rs:633 90 | msgid "Remote folder:" 91 | msgstr "रिमोट फोल्डर:" 92 | 93 | #: src/launch.rs:820 94 | msgid "The specified remote directory doesn't exist" 95 | msgstr "निर्दिष्ट रिमोट निर्देशिका मौजूद नहीं है" 96 | 97 | #: src/launch.rs:828 98 | msgid "Failed to check if the specified remote directory exists" 99 | msgstr "यह जांचने में विफल रहा कि निर्दिष्ट रिमोट निर्देशिका मौजूद है या नहीं" 100 | 101 | #: src/launch.rs:839 102 | msgid "The specified directory pair is already being synced" 103 | msgstr "निर्दिष्ट निर्देशिका जोड़ी पहले से ही समन्वयित हो रही है" 104 | 105 | #: src/launch.rs:842 106 | msgid "The specified local directory doesn't exist" 107 | msgstr "निर्दिष्ट स्थानीय निर्देशिका मौजूद नहीं है" 108 | 109 | #: src/launch.rs:845 110 | msgid "The specified local path isn't a directory" 111 | msgstr "निर्दिष्ट स्थानीय पथ कोई निर्देशिका नहीं है" 112 | 113 | #: src/launch.rs:848 114 | msgid "The specified local directory needs to be an absolute path" 115 | msgstr "निर्दिष्ट स्थानीय निर्देशिका को एक पूर्ण पथ होना आवश्यक है" 116 | 117 | #: src/launch.rs:876 118 | msgid "Are you sure you want to delete this remote?" 119 | msgstr "क्या आप वाकई इस रिमोट को हटाना चाहते हैं?" 120 | 121 | #: src/launch.rs:877 122 | msgid "All the directories associated with this remote will also stop syncing." 123 | msgstr "इस रिमोट से जुड़ी सभी निर्देशिकाएं भी समन्वयित होना बंद कर देंगी।" 124 | 125 | #: src/launch.rs:1342 src/launch.rs:2509 126 | msgid "Files are synced." 127 | msgstr "फाइलें समन्वयित हैं।" 128 | 129 | #: src/launch.rs:1352 130 | msgid "Syncing '{}'..." 131 | msgstr "'{}' समन्वयित हो रही है…" 132 | 133 | #: src/launch.rs:1373 134 | msgid "Checking for changes..." 135 | msgstr "परिवर्तनों की जाँच की जा रही है…" 136 | 137 | #. Add an error for reporting in the UI. 138 | #: src/launch.rs:1380 139 | msgid "Please resolve the reported syncing issues." 140 | msgstr "कृपया रिपोर्ट की गई समन्वयन समस्याओं का समाधान करें।" 141 | 142 | #: src/launch.rs:1407 143 | msgid "{} errors found. " 144 | msgstr "{} त्रुटियां पाई गई। " 145 | 146 | #: src/launch.rs:1421 147 | msgid "Would you like to dismiss this error?" 148 | msgstr "क्या आप इस त्रुटि को ख़ारिज करना चाहेंगे?" 149 | 150 | #: src/launch.rs:1448 151 | msgid "Failed to sync '{}' to '{}' on remote." 152 | msgstr "रिमोट पर '{}' को '{}' से समन्वयित करने में विफल।" 153 | 154 | #: src/launch.rs:1456 155 | msgid "Failed to sync '{}' on remote to '{}'." 156 | msgstr "रिमोट पर '{}' को '{}' से समन्वयित करने में विफल।" 157 | 158 | #: src/launch.rs:1481 159 | msgid "Unable to fetch data for '{}' from the remote." 160 | msgstr "रिमोट से '{}' के लिए डेटा लाने में असमर्थ।" 161 | 162 | #: src/launch.rs:1490 src/launch.rs:1495 src/launch.rs:1503 163 | msgid "File Update" 164 | msgstr "फाइल अद्यतन" 165 | 166 | #: src/launch.rs:1490 167 | msgid "Neither the local item or remote item exists anymore. This error will now be removed." 168 | msgstr "" 169 | "अब न तो स्थानीय वस्तु और न ही रिमोट वस्तु मौजूद है। अब यह त्रुटि दूर हो " 170 | "जाएगी।" 171 | 172 | #: src/launch.rs:1495 173 | msgid "Only the local item exists now, so it will be synced to the remote." 174 | msgstr "अब केवल स्थानीय आइटम मौजूद है, इसलिए इसे रिमोट से समन्वयित किया जाएगा।" 175 | 176 | #: src/launch.rs:1503 177 | msgid "Only the remote item exists now, so it will be synced to the local machine." 178 | msgstr "" 179 | "अब केवल रिमोट वस्तु मौजूद है, इसलिए इसे स्थानीय मशीन से समन्वयित किया जाएगा।" 180 | 181 | #: src/launch.rs:1513 182 | msgid "Both the local item '{}' and remote item '{}' have been updated since the last sync." 183 | msgstr "" 184 | "स्थानीय वस्तु '{}' और रिमोट वस्तु '{}' दोनों को अंतिम समन्वयन के बाद से अद्" 185 | "यतन किया गया है।" 186 | 187 | #: src/launch.rs:1515 188 | msgid "Which item would you like to keep?" 189 | msgstr "आप कौन सी वस्तु रखना चाहेंगे?" 190 | 191 | #: src/launch.rs:1517 192 | msgid "Local" 193 | msgstr "स्थानीय" 194 | 195 | #: src/launch.rs:1518 196 | msgid "Remote" 197 | msgstr "रिमोट" 198 | 199 | #: src/launch.rs:1567 200 | msgid "1 error found." 201 | msgstr "1 त्रुटि मिली।" 202 | 203 | #: src/launch.rs:1569 204 | msgid "{} errors found." 205 | msgstr "{} त्रुटियां पाई गई।" 206 | 207 | #: src/launch.rs:1701 208 | msgid "Checking '{}' for changes..." 209 | msgstr "परिवर्तनों के लिए '{}' की जाँच की जा रही है…" 210 | 211 | #: src/launch.rs:2117 212 | msgid "Checking '{}' on remote for changes..." 213 | msgstr "परिवर्तनों के लिए रिमोट पर '{}' की जाँच की जा रही है…" 214 | 215 | #: src/launch.rs:2530 216 | msgid "Finished sync checks with {} errors." 217 | msgstr "{} त्रुटियों के साथ समन्वयन जांच पूरी हो गई।" 218 | 219 | #: src/launch.rs:2535 220 | msgid "Finished sync checks." 221 | msgstr "समन्वयन जांच पूरी हो गई।" 222 | 223 | #: src/login/gdrive.rs:211 224 | msgid "Authenticating to {}..." 225 | msgstr "{} को प्रमाणित किया जा रहा है…" 226 | 227 | #: src/login/gdrive.rs:212 228 | msgid "Follow the link that opened in your browser, and come back once you've finished." 229 | msgstr "" 230 | "अपने ब्राउज़र में खुले लिंक का अनुसरण करें, और काम पूरा होने पर वापस आएं।" 231 | 232 | #: src/login/gdrive.rs:239 233 | msgid "There was an issue while running the webserver for authentication" 234 | msgstr "प्रमाणीकरण के लिए वेब सर्वर चलाते समय एक समस्या हुई" 235 | 236 | #: src/login/gdrive.rs:248 237 | msgid "There was an issue authenticating to {}" 238 | msgstr "{} को प्रमाणित करने में एक समस्या हुई" 239 | 240 | #: src/login/login_util.rs:15 241 | msgid "Name" 242 | msgstr "नाम" 243 | 244 | #: src/login/login_util.rs:27 245 | msgid "Name already exists." 246 | msgstr "नाम पहले से मौजूद है।" 247 | 248 | #: src/login/login_util.rs:29 249 | msgid "Invalid name. Names must:\n" 250 | "- Only contain numbers, letters, '_', '-', '.', and spaces\n" 251 | "- Not start with '-' or a space\n" 252 | "- Not end with a space" 253 | msgstr "" 254 | "अमान्य नाम। नाम अवश्य होने चाहिए:\n" 255 | "- केवल संख्याएं, अक्षर, '_', '-', '.', और रिक्त स्थान शामिल हैं\n" 256 | "- '-' या रिक्त स्थान से प्रारंभ न करें\n" 257 | "- रिक्त स्थान के साथ समाप्त नहीं होता" 258 | 259 | #: src/login/login_util.rs:43 260 | msgid "Server URL" 261 | msgstr "सर्वर URL" 262 | 263 | #: src/login/login_util.rs:49 264 | msgid "Invalid server URL ({})." 265 | msgstr "अमान्य सर्वर URL ({})।" 266 | 267 | #: src/login/login_util.rs:58 268 | msgid "Invalid server URL (no domain specified)." 269 | msgstr "अमान्य सर्वर URL (कोई डोमेन निर्दिष्ट नहीं)।" 270 | 271 | #: src/login/login_util.rs:62 272 | msgid "Invalid server URL (password was specified)." 273 | msgstr "अमान्य सर्वर URL (पासवर्ड निर्दिष्ट किया गया था)।" 274 | 275 | #: src/login/login_util.rs:66 276 | msgid "Invalid server URL(unknown server scheme {})." 277 | msgstr "अमान्य सर्वर URL (अज्ञात सर्वर योजना {})।" 278 | 279 | #: src/login/login_util.rs:78 280 | msgid "Don't specify '{}' as part of the URL." 281 | msgstr "URL के भाग के रूप में '{}' निर्दिष्ट न करें।" 282 | 283 | #: src/login/login_util.rs:91 284 | msgid "Username" 285 | msgstr "उपयोक्तानाम" 286 | 287 | #: src/login/login_util.rs:97 288 | msgid "Password" 289 | msgstr "पासवर्ड" 290 | 291 | #: src/login/login_util.rs:104 292 | msgid "2FA Code" 293 | msgstr "2FA कोड" 294 | 295 | #: src/login/login_util.rs:113 296 | msgid "The provided 2FA code is invalid (should only contain digits)." 297 | msgstr "प्रदान किया गया 2FA कोड अमान्य है (इसमें केवल अंक होने चाहिए)।" 298 | 299 | #: src/login/login_util.rs:118 300 | msgid "The provided 2FA code is invalid (should be 6 digits long)." 301 | msgstr "प्रदान किया गया 2FA कोड अमान्य है (6 अंक लंबा होना चाहिए)।" 302 | 303 | #: src/login/login_util.rs:142 304 | msgid "Log in" 305 | msgstr "लॉगिन" 306 | 307 | #: src/login/mod.rs:77 308 | msgid "Unable to connect to the server. Check your internet connection and try again." 309 | msgstr "" 310 | "सर्वर से जुड़ने में अक्षम। अपना इंटरनेट कनेक्शन जांचें और पुनः प्रयास करें।" 311 | 312 | #: src/login/mod.rs:80 313 | msgid "A 2FA code is required to log in to this account. Provide one and try again." 314 | msgstr "" 315 | "इस खाते में लॉग इन करने के लिए 2FA कोड आवश्यक है। एक प्रदान करें और पुनः " 316 | "प्रयास करें।" 317 | 318 | #: src/login/mod.rs:83 319 | msgid "Unable to authenticate to the server. Check your login credentials and try again." 320 | msgstr "" 321 | "सर्वर पर प्रमाणित करने में असमर्थ। अपने लॉगिन क्रेडेंशियल जांचें और पुनः " 322 | "प्रयास करें।" 323 | 324 | #: src/login/mod.rs:87 325 | msgid "Unable to log in" 326 | msgstr "लॉगिन करने में असमर्थ" 327 | 328 | #. The dropdown for selecting the server type. 329 | #: src/login/mod.rs:124 330 | msgid "Server Type" 331 | msgstr "सर्वर प्रकार" 332 | 333 | #: src/tray.rs:13 334 | msgid "Awaiting sync checks..." 335 | msgstr "सिंक जांच की प्रतीक्षा है…" 336 | 337 | #: src/tray.rs:60 338 | msgid "Open" 339 | msgstr "खोलें" 340 | 341 | #: src/tray.rs:67 342 | msgid "Close" 343 | msgstr "बंद करें" 344 | 345 | #: src/main.rs:188 346 | msgid "Unknown Error" 347 | msgstr "अज्ञात त्रुटि" 348 | 349 | #: src/main.rs:195 350 | msgid "An unknown error has occurred while running. This is an internal issue with Celeste and should be reported.\n" 351 | "\n" 352 | "The following backtrace may help with debugging the issue - note that it may contain information such as login tokens/keys, so avoid posting the information publicly:" 353 | msgstr "" 354 | "चलते समय कोई अज्ञात त्रुटि उत्पन्न हुई है। यह Celeste का एक आंतरिक मुद्दा है " 355 | "और इसकी सूचना दी जानी चाहिए।\n" 356 | "\n" 357 | "निम्नलिखित बैकट्रेस समस्या को डीबग करने में मदद कर सकता है - ध्यान दें कि " 358 | "इसमें लॉगिन टोकन/कुंजी जैसी जानकारी हो सकती है, इसलिए जानकारी को सार्वजनिक रू" 359 | "प से पोस्ट करने से बचें:" 360 | --------------------------------------------------------------------------------