Skip to content

Commit

Permalink
Add Proton Drive support (#146)
Browse files Browse the repository at this point in the history
  • Loading branch information
hwittenborn committed Oct 5, 2023
1 parent e3ad1ca commit 4cec322
Show file tree
Hide file tree
Showing 15 changed files with 209 additions and 33 deletions.
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,11 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [Unreleased]

## [0.6.0] - 2023-10-05
### Added
- Added Proton Drive support.

### Fixed
- Fixed missing `description` tags in metainfo's `releases` section.

Expand Down
12 changes: 6 additions & 6 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ indexmap = "1.9.2"
futures = "0.3.25"
lazy_static = "1.4.0"
libappindicator = "0.7.1"
librclone = { version = "0.5.0" }
librclone = { version = "0.6.0" }
nipper = "0.1.9"
nix = "0.26.2"
quit = "1.1.4"
Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ Celeste can currently connect to the following cloud providers:
- Nextcloud
- Owncloud
- pCloud
- Proton Drive
- WebDAV

## Installation
Expand Down
28 changes: 28 additions & 0 deletions assets/com.hunterwittenborn.Celeste.metainfo.xml
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,34 @@
</screenshot>
</screenshots>
<releases>
<release date="2023-10-05" version="0.6.0">
<description>
<p>
New features in this release:
</p>
<ul>
<li>
Added Proton Drive support.
</li>
</ul>
<p>
Fixes in this release:
</p>
<ul>
<li>
Fixed missing
<code>
description
</code>
tags in metainfo's
<code>
releases
</code>
section.
</li>
</ul>
</description>
</release>
<release date="2023-09-16" version="0.5.8">
<description>
<p>
Expand Down
2 changes: 1 addition & 1 deletion celeste-tray/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "celeste-tray"
version = "0.5.8"
version = "0.6.0"
edition = "2021"

[dependencies]
Expand Down
2 changes: 1 addition & 1 deletion celeste/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "celeste"
version = "0.5.8"
version = "0.6.0"
edition = "2021"

[dependencies]
Expand Down
44 changes: 42 additions & 2 deletions celeste/src/login/login_util.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
//! A collection of helper functions for generating login UIs.
use crate::rclone::{self};
use adw::{
gtk::{Align, Button, Label},
glib,
gtk::{Align, Button, CheckButton, Label},
prelude::*,
EntryRow, PasswordEntryRow,
};
Expand Down Expand Up @@ -97,6 +98,45 @@ pub fn password_input() -> PasswordEntryRow {
.build()
}

/// Get the input for TOTP/2FA codes.
pub fn totp_input() -> EntryRow {
let input = EntryRow::builder()
.title(&tr::tr!("2FA Code"))
.editable(false)
.build();
input.connect_changed(move |input| {
let text = input.text();

if text.chars().any(|c| !c.is_numeric()) {
input.add_css_class("error");
input.set_tooltip_text(Some(&tr::tr!(
"The provided 2FA code is invalid (should only contain digits)."
)));
} else if text.len() != 6 {
input.add_css_class("error");
input.set_tooltip_text(Some(&tr::tr!(
"The provided 2FA code is invalid (should be 6 digits long)."
)));
} else {
input.remove_css_class("error");
input.set_tooltip_text(None);
}
});
let check = CheckButton::new();
check.connect_toggled(glib::clone!(@weak input => move |check| {
let active = check.is_active();
input.set_editable(active);

if !active {
input.set_text("");
input.remove_css_class("error");
input.set_tooltip_text(None);
}
}));
input.add_prefix(&check);
input
}

/// Get the login button.
pub fn submit_button() -> Button {
let label = Label::builder().label(&tr::tr!("Log in")).build();
Expand All @@ -117,7 +157,7 @@ pub fn check_responses(responses: &[&EntryRow], submit_button: &Button) {
let mut no_errors = true;

for resp in responses {
if resp.is_sensitive() && (resp.has_css_class("error") || resp.text().is_empty()) {
if resp.is_editable() && (resp.has_css_class("error") || resp.text().is_empty()) {
no_errors = false;
}
}
Expand Down
23 changes: 23 additions & 0 deletions celeste/src/login/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ pub mod login_util;
mod nextcloud;
mod owncloud;
mod pcloud;
mod proton_drive;
mod webdav;

use adw::{
Expand All @@ -25,6 +26,7 @@ use gdrive::GDriveConfig;
use nextcloud::NextcloudConfig;
use owncloud::OwncloudConfig;
use pcloud::PCloudConfig;
use proton_drive::ProtonDriveConfig;
use std::{cell::RefCell, rc::Rc};
use webdav::WebDavConfig;

Expand All @@ -47,6 +49,7 @@ pub enum ServerType {
Nextcloud(nextcloud::NextcloudConfig),
Owncloud(owncloud::OwncloudConfig),
PCloud(pcloud::PCloudConfig),
ProtonDrive(proton_drive::ProtonDriveConfig),
WebDav(webdav::WebDavConfig),
}

Expand All @@ -58,6 +61,7 @@ impl ToString for ServerType {
Self::Nextcloud(_) => "Nextcloud",
Self::Owncloud(_) => "Owncloud",
Self::PCloud(_) => "pCloud",
Self::ProtonDrive(_) => "Proton Drive",
Self::WebDav(_) => "WebDAV",
}
.to_string()
Expand All @@ -71,6 +75,8 @@ pub fn can_login(_app: &Application, config_name: &str) -> bool {
tr::tr!(
"Unable to connect to the server. Check your internet connection and try again."
)
} else if err.error.contains("this account requires a 2FA code") {
tr::tr!("A 2FA code is required to log in to this account. Provide one and try again.")
} else {
tr::tr!(
"Unable to authenticate to the server. Check your login credentials and try again."
Expand Down Expand Up @@ -110,6 +116,7 @@ pub fn login(app: &Application, db: &DatabaseConnection) -> Option<RemotesModel>
let nextcloud_name = ServerType::Nextcloud(Default::default()).to_string();
let owncloud_name = ServerType::Owncloud(Default::default()).to_string();
let pcloud_name = ServerType::PCloud(Default::default()).to_string();
let proton_drive_name = ServerType::ProtonDrive(Default::default()).to_string();
let webdav_name = ServerType::WebDav(Default::default()).to_string();

// The dropdown for selecting the server type.
Expand All @@ -120,6 +127,7 @@ pub fn login(app: &Application, db: &DatabaseConnection) -> Option<RemotesModel>
nextcloud_name.as_str(),
owncloud_name.as_str(),
pcloud_name.as_str(),
proton_drive_name.as_str(),
webdav_name.as_str(),
];
let server_types = StringList::new(&server_types_array);
Expand All @@ -145,6 +153,7 @@ pub fn login(app: &Application, db: &DatabaseConnection) -> Option<RemotesModel>
let nextcloud_items = NextcloudConfig::get_sections(&window, sender.clone());
let owncloud_items = OwncloudConfig::get_sections(&window, sender.clone());
let pcloud_items = PCloudConfig::get_sections(&window, sender.clone());
let proton_drive_items = ProtonDriveConfig::get_sections(&window, sender.clone());
let webdav_items = WebDavConfig::get_sections(&window, sender);

// Store the active items.
Expand All @@ -162,6 +171,7 @@ pub fn login(app: &Application, db: &DatabaseConnection) -> Option<RemotesModel>
"nextcloud" => nextcloud_items.clone(),
"owncloud" => owncloud_items.clone(),
"pcloud" => pcloud_items.clone(),
"proton drive" => proton_drive_items.clone(),
"webdav" => webdav_items.clone(),
_ => unreachable!()
};
Expand Down Expand Up @@ -213,6 +223,7 @@ pub fn login(app: &Application, db: &DatabaseConnection) -> Option<RemotesModel>
ServerType::Nextcloud(config) => config.server_name.clone(),
ServerType::Owncloud(config) => config.server_name.clone(),
ServerType::PCloud(config) => config.server_name.clone(),
ServerType::ProtonDrive(config) => config.server_name.clone(),
ServerType::WebDav(config) => config.server_name.clone(),
};

Expand Down Expand Up @@ -276,6 +287,18 @@ pub fn login(app: &Application, db: &DatabaseConnection) -> Option<RemotesModel>
"obscure": true
}
}),
ServerType::ProtonDrive(config) => json!({
"name": config_name,
"parameters": {
"username": config.username,
"password": config.password,
"2fa": config.totp
},
"type": "protondrive",
"opt": {
"obscure": true
}
}),
ServerType::WebDav(config) => json!({
"name": config_name,
"parameters": {
Expand Down
49 changes: 49 additions & 0 deletions celeste/src/login/proton_drive.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
//! The data for a Proton Drive Rclone config.
use super::ServerType;
use crate::{login::login_util, mpsc::Sender};
use adw::{glib, gtk::Button, prelude::*, ApplicationWindow, EntryRow};

#[derive(Clone, Debug, Default)]
pub struct ProtonDriveConfig {
pub server_name: String,
pub username: String,
pub password: String,
pub totp: String,
}

impl super::LoginTrait for ProtonDriveConfig {
fn get_sections(
_window: &ApplicationWindow,
sender: Sender<Option<ServerType>>,
) -> (Vec<EntryRow>, Button) {
let mut sections = vec![];
let server_name = login_util::server_name_input();
let username = login_util::username_input();
let password = login_util::password_input();
let totp = login_util::totp_input();
let submit_button = login_util::submit_button();

sections.push(server_name.clone());
sections.push(username.clone());
sections.push(password.clone().into());
sections.push(totp.clone());

submit_button.connect_clicked(
glib::clone!(@weak server_name, @weak username, @weak password, @weak totp => move |_| {
sender.send(Some(ServerType::ProtonDrive(ProtonDriveConfig {
server_name: server_name.text().to_string(),
username: username.text().to_string(),
password: password.text().to_string(),
totp: totp.text().to_string()
})));
}),
);

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)));
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)));
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)));
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)));

(sections, submit_button)
}
}
15 changes: 15 additions & 0 deletions celeste/src/rclone.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,10 @@ pub fn get_remote<T: ToString>(remote: T) -> Option<Remote> {
client_id: config["client_id"].clone(),
client_secret: config["client_secret"].clone(),
})),
"protondrive" => Some(Remote::ProtonDrive(ProtonDriveRemote {
remote_name: remote,
username: config["username"].clone(),
})),
"webdav" => {
let vendor = match config["vendor"].as_str() {
"nextcloud" => WebDavVendors::Nextcloud,
Expand Down Expand Up @@ -77,6 +81,7 @@ pub enum Remote {
Dropbox(DropboxRemote),
GDrive(GDriveRemote),
PCloud(PCloudRemote),
ProtonDrive(ProtonDriveRemote),
WebDav(WebDavRemote),
}

Expand All @@ -86,6 +91,7 @@ impl Remote {
Remote::Dropbox(remote) => remote.remote_name.clone(),
Remote::GDrive(remote) => remote.remote_name.clone(),
Remote::PCloud(remote) => remote.remote_name.clone(),
Remote::ProtonDrive(remote) => remote.remote_name.clone(),
Remote::WebDav(remote) => remote.remote_name.clone(),
}
}
Expand Down Expand Up @@ -124,6 +130,15 @@ pub struct PCloudRemote {
pub client_secret: String,
}

// The Proton Drive remote type.
#[derive(Clone, Debug)]
pub struct ProtonDriveRemote {
/// The name of the remote.
pub remote_name: String,
/// the username.
pub username: String,
}

// The WebDav remote type.
#[derive(Clone, Debug)]
pub struct WebDavRemote {
Expand Down
1 change: 0 additions & 1 deletion justfile
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,6 @@ update-versions:
set -euo pipefail
version="$(just get-version)"
sed -i "s|version = .*|version = \"${version}\"|" celeste/Cargo.toml celeste-tray/Cargo.toml libceleste/Cargo.toml
sed -i "s|version: .*|version: '${version}'|" snap/snapcraft.yaml
date="$(cat CHANGELOG.md | grep "^## \[${version}\]" | grep -o '[^ ]*$')"
notes="$(parse-changelog CHANGELOG.md "${version}")"
Expand Down
2 changes: 1 addition & 1 deletion libceleste/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "libceleste"
version = "0.5.8"
version = "0.6.0"
edition = "2021"

[lib]
Expand Down
2 changes: 1 addition & 1 deletion makedeb/PKGBUILD
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Maintainer: Hunter Wittenborn <[email protected]>
pkgname=celeste
pkgver=0.5.8
pkgver=0.6.0
pkgrel=1
pkgdesc='Sync your cloud files'
arch=('any')
Expand Down
Loading

0 comments on commit 4cec322

Please sign in to comment.