Compare commits
3 commits
e757a1474c
...
1d93482209
Author | SHA1 | Date | |
---|---|---|---|
1d93482209 | |||
3b75134572 | |||
ca96431208 |
6 changed files with 446 additions and 64 deletions
198
Cargo.lock
generated
198
Cargo.lock
generated
|
@ -26,6 +26,22 @@ dependencies = [
|
|||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "arboard"
|
||||
version = "3.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c1df21f715862ede32a0c525ce2ca4d52626bb0007f8c18b87a384503ac33e70"
|
||||
dependencies = [
|
||||
"clipboard-win",
|
||||
"log",
|
||||
"objc2",
|
||||
"objc2-app-kit",
|
||||
"objc2-foundation",
|
||||
"parking_lot",
|
||||
"percent-encoding",
|
||||
"x11rb",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ashpd"
|
||||
version = "0.11.0"
|
||||
|
@ -192,6 +208,15 @@ version = "0.2.1"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724"
|
||||
|
||||
[[package]]
|
||||
name = "clipboard-win"
|
||||
version = "5.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "15efe7a882b08f34e38556b14f2fb3daa98769d06c7f0c1b076dfd0d983bc892"
|
||||
dependencies = [
|
||||
"error-code",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "color-eyre"
|
||||
version = "0.6.4"
|
||||
|
@ -340,6 +365,12 @@ dependencies = [
|
|||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "error-code"
|
||||
version = "3.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dea2df4cf52843e0452895c455a1a2cfbb842a1e7329671acf418fdc53ed4c59"
|
||||
|
||||
[[package]]
|
||||
name = "event-listener"
|
||||
version = "5.4.0"
|
||||
|
@ -579,6 +610,16 @@ dependencies = [
|
|||
"system-deps",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "gethostname"
|
||||
version = "0.4.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0176e0459c2e4a1fe232f984bca6890e681076abb9934f6cea7c326f3fc47818"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"windows-targets 0.48.5",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "getrandom"
|
||||
version = "0.2.16"
|
||||
|
@ -1136,6 +1177,12 @@ dependencies = [
|
|||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "linux-raw-sys"
|
||||
version = "0.4.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab"
|
||||
|
||||
[[package]]
|
||||
name = "linux-raw-sys"
|
||||
version = "0.9.4"
|
||||
|
@ -1280,6 +1327,7 @@ dependencies = [
|
|||
"bitflags",
|
||||
"block2",
|
||||
"objc2",
|
||||
"objc2-core-graphics",
|
||||
"objc2-foundation",
|
||||
]
|
||||
|
||||
|
@ -1294,6 +1342,19 @@ dependencies = [
|
|||
"objc2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "objc2-core-graphics"
|
||||
version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "989c6c68c13021b5c2d6b71456ebb0f9dc78d752e86a98da7c716f4f9470f5a4"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"dispatch2 0.3.0",
|
||||
"objc2",
|
||||
"objc2-core-foundation",
|
||||
"objc2-io-surface",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "objc2-encode"
|
||||
version = "4.1.0"
|
||||
|
@ -1311,6 +1372,17 @@ dependencies = [
|
|||
"objc2-core-foundation",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "objc2-io-surface"
|
||||
version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7282e9ac92529fa3457ce90ebb15f4ecbc383e8338060960760fa2cf75420c3c"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"objc2",
|
||||
"objc2-core-foundation",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "object"
|
||||
version = "0.36.7"
|
||||
|
@ -1384,6 +1456,29 @@ version = "2.2.1"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba"
|
||||
|
||||
[[package]]
|
||||
name = "parking_lot"
|
||||
version = "0.12.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27"
|
||||
dependencies = [
|
||||
"lock_api",
|
||||
"parking_lot_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "parking_lot_core"
|
||||
version = "0.9.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"libc",
|
||||
"redox_syscall",
|
||||
"smallvec",
|
||||
"windows-targets 0.52.6",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "percent-encoding"
|
||||
version = "2.3.1"
|
||||
|
@ -1555,6 +1650,15 @@ version = "0.6.2"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "20675572f6f24e9e76ef639bc5552774ed45f1c30e2951e1e99c59888861c539"
|
||||
|
||||
[[package]]
|
||||
name = "redox_syscall"
|
||||
version = "0.5.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "928fca9cf2aa042393a8325b9ead81d2f0df4cb12e1e24cef072922ccd99c5af"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "redox_users"
|
||||
version = "0.5.0"
|
||||
|
@ -1752,6 +1856,19 @@ dependencies = [
|
|||
"semver",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustix"
|
||||
version = "0.38.44"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"errno",
|
||||
"libc",
|
||||
"linux-raw-sys 0.4.15",
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustix"
|
||||
version = "1.0.7"
|
||||
|
@ -1761,7 +1878,7 @@ dependencies = [
|
|||
"bitflags",
|
||||
"errno",
|
||||
"libc",
|
||||
"linux-raw-sys",
|
||||
"linux-raw-sys 0.9.4",
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
|
@ -2032,7 +2149,7 @@ dependencies = [
|
|||
"fastrand",
|
||||
"getrandom 0.3.3",
|
||||
"once_cell",
|
||||
"rustix",
|
||||
"rustix 1.0.7",
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
|
@ -2280,12 +2397,15 @@ checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b"
|
|||
name = "tyrolienne"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"arboard",
|
||||
"color-eyre",
|
||||
"dirs",
|
||||
"futures",
|
||||
"relm4",
|
||||
"reqwest",
|
||||
"rfd",
|
||||
"serde",
|
||||
"tokio-util",
|
||||
"toml",
|
||||
"tracing",
|
||||
"tracing-subscriber",
|
||||
|
@ -2578,6 +2698,21 @@ dependencies = [
|
|||
"windows-targets 0.52.6",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-targets"
|
||||
version = "0.48.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c"
|
||||
dependencies = [
|
||||
"windows_aarch64_gnullvm 0.48.5",
|
||||
"windows_aarch64_msvc 0.48.5",
|
||||
"windows_i686_gnu 0.48.5",
|
||||
"windows_i686_msvc 0.48.5",
|
||||
"windows_x86_64_gnu 0.48.5",
|
||||
"windows_x86_64_gnullvm 0.48.5",
|
||||
"windows_x86_64_msvc 0.48.5",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-targets"
|
||||
version = "0.52.6"
|
||||
|
@ -2610,6 +2745,12 @@ dependencies = [
|
|||
"windows_x86_64_msvc 0.53.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_gnullvm"
|
||||
version = "0.48.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8"
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_gnullvm"
|
||||
version = "0.52.6"
|
||||
|
@ -2622,6 +2763,12 @@ version = "0.53.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "86b8d5f90ddd19cb4a147a5fa63ca848db3df085e25fee3cc10b39b6eebae764"
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_msvc"
|
||||
version = "0.48.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc"
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_msvc"
|
||||
version = "0.52.6"
|
||||
|
@ -2634,6 +2781,12 @@ version = "0.53.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c7651a1f62a11b8cbd5e0d42526e55f2c99886c77e007179efff86c2b137e66c"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_gnu"
|
||||
version = "0.48.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_gnu"
|
||||
version = "0.52.6"
|
||||
|
@ -2658,6 +2811,12 @@ version = "0.53.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9ce6ccbdedbf6d6354471319e781c0dfef054c81fbc7cf83f338a4296c0cae11"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_msvc"
|
||||
version = "0.48.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_msvc"
|
||||
version = "0.52.6"
|
||||
|
@ -2670,6 +2829,12 @@ version = "0.53.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "581fee95406bb13382d2f65cd4a908ca7b1e4c2f1917f143ba16efe98a589b5d"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnu"
|
||||
version = "0.48.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnu"
|
||||
version = "0.52.6"
|
||||
|
@ -2682,6 +2847,12 @@ version = "0.53.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2e55b5ac9ea33f2fc1716d1742db15574fd6fc8dadc51caab1c16a3d3b4190ba"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnullvm"
|
||||
version = "0.48.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnullvm"
|
||||
version = "0.52.6"
|
||||
|
@ -2694,6 +2865,12 @@ version = "0.53.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0a6e035dd0599267ce1ee132e51c27dd29437f63325753051e71dd9e42406c57"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_msvc"
|
||||
version = "0.48.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_msvc"
|
||||
version = "0.52.6"
|
||||
|
@ -2730,6 +2907,23 @@ version = "0.6.1"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ea2f10b9bb0928dfb1b42b65e1f9e36f7f54dbdf08457afefb38afcdec4fa2bb"
|
||||
|
||||
[[package]]
|
||||
name = "x11rb"
|
||||
version = "0.13.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5d91ffca73ee7f68ce055750bf9f6eca0780b8c85eff9bc046a3b0da41755e12"
|
||||
dependencies = [
|
||||
"gethostname",
|
||||
"rustix 0.38.44",
|
||||
"x11rb-protocol",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "x11rb-protocol"
|
||||
version = "0.13.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ec107c4503ea0b4a98ef47356329af139c0a4f7750e621cf2973cd3385ebcb3d"
|
||||
|
||||
[[package]]
|
||||
name = "yoke"
|
||||
version = "0.8.0"
|
||||
|
|
|
@ -4,12 +4,15 @@ version = "0.1.0"
|
|||
edition = "2024"
|
||||
|
||||
[dependencies]
|
||||
arboard = { version = "3.5.0", default-features = false }
|
||||
color-eyre = "0.6.4"
|
||||
dirs = "6.0.0"
|
||||
futures = "0.3.31"
|
||||
relm4 = { version = "0.9.1", features = ["gnome_47", "libadwaita"] }
|
||||
reqwest = { version = "0.12.15", default-features = false, features = ["http2", "charset", "rustls-tls", "json", "multipart", "stream"] }
|
||||
rfd = { version = "0.15.3", default-features = false, features = ["tokio", "xdg-portal"] }
|
||||
serde = { version = "1.0.219", features = ["derive"] }
|
||||
tokio-util = { version = "0.7.15", features = ["io"] }
|
||||
toml = "0.8.22"
|
||||
tracing = "0.1.41"
|
||||
tracing-subscriber = { version = "0.3.19", features = ["env-filter"] }
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
libadwaita,
|
||||
pkg-config,
|
||||
wrapGAppsHook4,
|
||||
zenity,
|
||||
}:
|
||||
let
|
||||
cargoToml = lib.importTOML ./Cargo.toml;
|
||||
|
@ -27,6 +28,12 @@ craneLib.buildPackage {
|
|||
|
||||
doCheck = false;
|
||||
|
||||
preFixup = ''
|
||||
gappsWrapperArgs+=(
|
||||
--prefix PATH : ${lib.makeBinPath [ zenity ]}
|
||||
)
|
||||
'';
|
||||
|
||||
meta = with lib; {
|
||||
mainProgram = cargoToml.package.name;
|
||||
description = "simple tool to convert, upload, and embed videos to zipline";
|
||||
|
|
182
src/main.rs
182
src/main.rs
|
@ -6,9 +6,9 @@ use std::{borrow::Cow, path::PathBuf, time::Duration};
|
|||
|
||||
use color_eyre::eyre::{OptionExt, Result, bail};
|
||||
use gobject::GtkZiplineFolder;
|
||||
use relm::{Dialog, DialogInput};
|
||||
use relm::{Dialog, DialogInput, Toast, ToastInput};
|
||||
use relm4::{
|
||||
Component, ComponentController, Controller, RelmApp,
|
||||
Component, ComponentController, Controller, RelmApp, Sender,
|
||||
adw::{self, prelude::*},
|
||||
gtk::{self, gio},
|
||||
prelude::{AsyncComponent, AsyncComponentParts},
|
||||
|
@ -43,17 +43,33 @@ impl Config {
|
|||
enum Message {
|
||||
OpenFilePicker,
|
||||
SetFolder(ZiplineFolder),
|
||||
LockAndStart,
|
||||
StartTheProcess,
|
||||
Nothing,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
enum ProgressMessage {
|
||||
SetTotal(usize),
|
||||
Progress(usize),
|
||||
Finish(String),
|
||||
Error(String),
|
||||
}
|
||||
|
||||
struct UploadInfo {
|
||||
config: Config,
|
||||
video_path: Option<PathBuf>,
|
||||
folder: Option<ZiplineFolder>,
|
||||
}
|
||||
|
||||
struct Tyrolienne {
|
||||
config: Config,
|
||||
locked: bool,
|
||||
progress: usize,
|
||||
total: usize,
|
||||
video_path: Option<PathBuf>,
|
||||
folder: Option<ZiplineFolder>,
|
||||
dialog: Controller<Dialog>,
|
||||
toast: Controller<Toast>,
|
||||
}
|
||||
|
||||
impl Tyrolienne {
|
||||
|
@ -63,13 +79,21 @@ impl Tyrolienne {
|
|||
.map(|p| p.to_string_lossy())
|
||||
.unwrap_or(Cow::Borrowed("None"))
|
||||
}
|
||||
|
||||
fn clone_as_info(&self) -> UploadInfo {
|
||||
UploadInfo {
|
||||
config: self.config.clone(),
|
||||
video_path: self.video_path.clone(),
|
||||
folder: self.folder.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[relm4::component(async)]
|
||||
impl AsyncComponent for Tyrolienne {
|
||||
type Input = Message;
|
||||
type Output = ();
|
||||
type CommandOutput = ();
|
||||
type CommandOutput = ProgressMessage;
|
||||
type Init = Config;
|
||||
|
||||
view! {
|
||||
|
@ -77,54 +101,69 @@ impl AsyncComponent for Tyrolienne {
|
|||
set_title: Some("Tyrolienne"),
|
||||
set_default_width: 500,
|
||||
|
||||
|
||||
gtk::Box {
|
||||
set_orientation: gtk::Orientation::Vertical,
|
||||
set_spacing: 0,
|
||||
|
||||
adw::HeaderBar {},
|
||||
|
||||
gtk::Box {
|
||||
set_orientation: gtk::Orientation::Vertical,
|
||||
set_spacing: 32,
|
||||
set_margin_top: 32,
|
||||
set_margin_bottom: 32,
|
||||
set_margin_start: 32,
|
||||
set_margin_end: 32,
|
||||
#[local_ref]
|
||||
toast_overlay -> adw::ToastOverlay {
|
||||
gtk::Box {
|
||||
set_orientation: gtk::Orientation::Vertical,
|
||||
set_spacing: 32,
|
||||
set_margin_top: 32,
|
||||
set_margin_bottom: 32,
|
||||
set_margin_start: 32,
|
||||
set_margin_end: 32,
|
||||
|
||||
gtk::ListBox {
|
||||
set_selection_mode: gtk::SelectionMode::None,
|
||||
set_css_classes: &["boxed-list"],
|
||||
gtk::ListBox {
|
||||
set_selection_mode: gtk::SelectionMode::None,
|
||||
set_css_classes: &["boxed-list"],
|
||||
|
||||
adw::ActionRow {
|
||||
set_activatable: true,
|
||||
set_css_classes: &["property"],
|
||||
set_title: "Video file",
|
||||
#[watch]
|
||||
set_subtitle: &model.display_video_path(),
|
||||
connect_activated => Message::OpenFilePicker,
|
||||
adw::ActionRow {
|
||||
set_activatable: true,
|
||||
set_css_classes: &["property"],
|
||||
set_title: "Video file",
|
||||
#[watch]
|
||||
set_subtitle: &model.display_video_path(),
|
||||
connect_activated => Message::OpenFilePicker,
|
||||
},
|
||||
|
||||
adw::ComboRow {
|
||||
set_title: "Folder",
|
||||
set_model: Some(&folder_store),
|
||||
set_expression: Some(&folder_expression),
|
||||
connect_activated[sender] => move |r| {
|
||||
if let Some(item) = r
|
||||
.selected_item()
|
||||
.and_then(|i| i.downcast::<GtkZiplineFolder>().ok())
|
||||
{
|
||||
sender.input(Message::SetFolder(item.as_folder()));
|
||||
}
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
adw::ComboRow {
|
||||
set_title: "Folder",
|
||||
set_model: Some(&folder_store),
|
||||
set_expression: Some(&folder_expression),
|
||||
connect_activated[sender] => move |r| {
|
||||
if let Some(item) = r
|
||||
.selected_item()
|
||||
.and_then(|i| i.downcast::<GtkZiplineFolder>().ok())
|
||||
{
|
||||
sender.input(Message::SetFolder(item.as_folder()));
|
||||
}
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
gtk::Button {
|
||||
#[watch]
|
||||
set_label: if model.locked { "Uploading..." } else { "Send" },
|
||||
#[watch]
|
||||
set_sensitive: !model.locked && model.video_path.is_some(),
|
||||
connect_clicked => Message::LockAndStart,
|
||||
gtk::Box {
|
||||
set_orientation: gtk::Orientation::Vertical,
|
||||
set_spacing: 10,
|
||||
|
||||
gtk::ProgressBar {
|
||||
#[watch]
|
||||
set_fraction: model.progress as f64 / model.total as f64,
|
||||
},
|
||||
|
||||
gtk::Button {
|
||||
#[watch]
|
||||
set_label: if model.locked { "Uploading..." } else { "Send" },
|
||||
#[watch]
|
||||
set_sensitive: !model.locked && model.video_path.is_some(),
|
||||
connect_clicked => Message::StartTheProcess,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -136,13 +175,20 @@ impl AsyncComponent for Tyrolienne {
|
|||
root: Self::Root,
|
||||
sender: relm4::AsyncComponentSender<Self>,
|
||||
) -> AsyncComponentParts<Self> {
|
||||
let toast_overlay = adw::ToastOverlay::new();
|
||||
|
||||
let model = Tyrolienne {
|
||||
config,
|
||||
locked: false,
|
||||
progress: 0,
|
||||
total: 1,
|
||||
video_path: None,
|
||||
folder: None,
|
||||
dialog: Dialog::builder()
|
||||
.launch(root.clone())
|
||||
.launch(toast_overlay.clone())
|
||||
.forward(sender.input_sender(), |_| Message::Nothing),
|
||||
toast: Toast::builder()
|
||||
.launch(toast_overlay.clone())
|
||||
.forward(sender.input_sender(), |_| Message::Nothing),
|
||||
};
|
||||
|
||||
|
@ -199,21 +245,47 @@ impl AsyncComponent for Tyrolienne {
|
|||
self.video_path = Some(file.path().to_owned());
|
||||
}
|
||||
}
|
||||
Message::LockAndStart => {
|
||||
// a little bit convoluted, but i don't really know how to force a view update
|
||||
// before starting the "long" process
|
||||
self.locked = true;
|
||||
sender.input(Message::StartTheProcess);
|
||||
}
|
||||
Message::StartTheProcess => {
|
||||
match the_process(self).await {
|
||||
Ok(url) => tracing::info!("{url}"),
|
||||
Err(e) => self.dialog.emit(DialogInput::Show {
|
||||
self.locked = true;
|
||||
let info = self.clone_as_info();
|
||||
sender.command(|out, shutdown| {
|
||||
Box::pin(
|
||||
shutdown
|
||||
.register(async move {
|
||||
match the_process(info, &out).await {
|
||||
Ok(url) => out.emit(ProgressMessage::Finish(url)),
|
||||
Err(e) => out.emit(ProgressMessage::Error(e.to_string())),
|
||||
}
|
||||
})
|
||||
.drop_on_shutdown(),
|
||||
)
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async fn update_cmd(
|
||||
&mut self,
|
||||
message: Self::CommandOutput,
|
||||
_sender: relm4::AsyncComponentSender<Self>,
|
||||
_root: &Self::Root,
|
||||
) {
|
||||
match message {
|
||||
ProgressMessage::SetTotal(total) => self.total = total,
|
||||
ProgressMessage::Progress(prog) => self.progress += prog,
|
||||
ProgressMessage::Finish(_) | ProgressMessage::Error(_) => {
|
||||
self.locked = false;
|
||||
self.progress = 0;
|
||||
self.total = 1;
|
||||
|
||||
if let ProgressMessage::Finish(url) = message {
|
||||
self.toast.emit(ToastInput::Show(url));
|
||||
} else if let ProgressMessage::Error(e) = message {
|
||||
self.dialog.emit(DialogInput::Show {
|
||||
heading: "An error occurred".into(),
|
||||
body: e.to_string(),
|
||||
}),
|
||||
});
|
||||
}
|
||||
self.locked = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -264,7 +336,7 @@ fn get_config() -> Result<Config> {
|
|||
Ok(config)
|
||||
}
|
||||
|
||||
async fn the_process(app: &Tyrolienne) -> Result<String> {
|
||||
async fn the_process(app: UploadInfo, sender: &Sender<ProgressMessage>) -> Result<String> {
|
||||
if let Some(folder) = app.folder.as_ref() {
|
||||
tracing::info!("uploading to folder '{}'...", folder.name);
|
||||
} else {
|
||||
|
@ -275,7 +347,7 @@ async fn the_process(app: &Tyrolienne) -> Result<String> {
|
|||
bail!("No video given!");
|
||||
};
|
||||
|
||||
let res = zipline::upload_file(&app.config, app.folder.as_ref(), video_path).await?;
|
||||
let res = zipline::upload_file(&app.config, sender, app.folder.as_ref(), video_path).await?;
|
||||
let zp_file = &res.files[0];
|
||||
|
||||
tracing::info!("recalculating thumbnails...");
|
||||
|
|
78
src/relm.rs
78
src/relm.rs
|
@ -5,7 +5,9 @@ use relm4::{
|
|||
};
|
||||
|
||||
pub struct Dialog {
|
||||
window: adw::ApplicationWindow,
|
||||
// toast overlay looks like an odd choice but i think it's fitting considering it acts as a
|
||||
// "box" for the main content
|
||||
window: adw::ToastOverlay,
|
||||
visible: bool,
|
||||
heading: String,
|
||||
body: String,
|
||||
|
@ -22,7 +24,7 @@ pub struct DialogWidgets {
|
|||
}
|
||||
|
||||
impl SimpleComponent for Dialog {
|
||||
type Init = adw::ApplicationWindow;
|
||||
type Init = adw::ToastOverlay;
|
||||
type Root = adw::AlertDialog;
|
||||
type Widgets = DialogWidgets;
|
||||
type Input = DialogInput;
|
||||
|
@ -80,3 +82,75 @@ impl SimpleComponent for Dialog {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Toast {
|
||||
root: adw::ToastOverlay,
|
||||
visible: bool,
|
||||
text: String,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum ToastInput {
|
||||
Show(String),
|
||||
Copy,
|
||||
Dismiss,
|
||||
}
|
||||
|
||||
#[relm4::component(pub)]
|
||||
impl SimpleComponent for Toast {
|
||||
type Input = ToastInput;
|
||||
type Output = ();
|
||||
type Init = adw::ToastOverlay;
|
||||
|
||||
view! {
|
||||
#[name = "toast"]
|
||||
adw::Toast {
|
||||
#[watch]
|
||||
set_title: &model.text,
|
||||
set_button_label: Some("Copy"),
|
||||
connect_button_clicked => ToastInput::Copy,
|
||||
connect_dismissed => ToastInput::Dismiss,
|
||||
}
|
||||
}
|
||||
|
||||
fn init(
|
||||
init: Self::Init,
|
||||
root: Self::Root,
|
||||
_sender: relm4::ComponentSender<Self>,
|
||||
) -> relm4::ComponentParts<Self> {
|
||||
let model = Self {
|
||||
root: init,
|
||||
visible: false,
|
||||
text: String::new(),
|
||||
};
|
||||
|
||||
let widgets = view_output!();
|
||||
relm4::ComponentParts { model, widgets }
|
||||
}
|
||||
|
||||
fn update(&mut self, message: Self::Input, _sender: relm4::ComponentSender<Self>) {
|
||||
match message {
|
||||
ToastInput::Show(text) => {
|
||||
self.visible = true;
|
||||
self.text = text;
|
||||
}
|
||||
ToastInput::Copy => {
|
||||
if let Err(e) =
|
||||
arboard::Clipboard::new().and_then(|mut c| c.set_text(self.text.clone()))
|
||||
{
|
||||
tracing::error!("could not copy url to clipboard: {e}");
|
||||
self.text = "Could not copy".into();
|
||||
} else {
|
||||
self.visible = false;
|
||||
}
|
||||
}
|
||||
ToastInput::Dismiss => self.visible = false,
|
||||
}
|
||||
}
|
||||
|
||||
fn post_view() {
|
||||
if model.visible {
|
||||
model.root.add_toast(widgets.toast.clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,9 +1,16 @@
|
|||
use std::{path::Path, sync::LazyLock};
|
||||
|
||||
use color_eyre::eyre::{Result, bail};
|
||||
use reqwest::{Client, StatusCode, header::AUTHORIZATION, multipart::Form};
|
||||
use futures::StreamExt;
|
||||
use relm4::{Sender, tokio::fs::File};
|
||||
use reqwest::{
|
||||
Body, Client, StatusCode,
|
||||
header::AUTHORIZATION,
|
||||
multipart::{Form, Part},
|
||||
};
|
||||
use tokio_util::io::ReaderStream;
|
||||
|
||||
use crate::Config;
|
||||
use crate::{Config, ProgressMessage};
|
||||
|
||||
static CLIENT: LazyLock<Client> = LazyLock::new(Client::new);
|
||||
|
||||
|
@ -42,6 +49,31 @@ impl ZiplineFileInfo {
|
|||
}
|
||||
}
|
||||
|
||||
async fn wrap_file(path: &Path, sender: Sender<ProgressMessage>) -> Result<Part> {
|
||||
let file_name = path
|
||||
.file_name()
|
||||
.map(|filename| filename.to_string_lossy().into_owned());
|
||||
|
||||
let file = File::open(path).await?;
|
||||
let len = file.metadata().await?.len();
|
||||
sender.emit(ProgressMessage::SetTotal(len as usize));
|
||||
|
||||
let stream = ReaderStream::new(file).map(move |b| {
|
||||
if let Ok(ref bytes) = b {
|
||||
sender.emit(ProgressMessage::Progress(bytes.len()));
|
||||
}
|
||||
b
|
||||
});
|
||||
|
||||
let field = Part::stream_with_length(Body::wrap_stream(stream), len).mime_str("video/webm")?;
|
||||
|
||||
Ok(if let Some(file_name) = file_name {
|
||||
field.file_name(file_name)
|
||||
} else {
|
||||
field
|
||||
})
|
||||
}
|
||||
|
||||
pub async fn get_folders(config: &Config) -> Result<Vec<ZiplineFolder>> {
|
||||
let url = format!("{}api/user/folders?noincl=true", config.fixed_url());
|
||||
|
||||
|
@ -64,14 +96,14 @@ pub async fn get_folders(config: &Config) -> Result<Vec<ZiplineFolder>> {
|
|||
|
||||
pub async fn upload_file(
|
||||
config: &Config,
|
||||
sender: &Sender<ProgressMessage>,
|
||||
folder: Option<&ZiplineFolder>,
|
||||
file_path: &Path,
|
||||
) -> Result<ZiplineUploadResponse> {
|
||||
let url = format!("{}api/upload", config.fixed_url());
|
||||
|
||||
// TODO use Part::stream to provide a wrapped file with a custom stream impl to send progress
|
||||
// (i hope it works)
|
||||
let form = Form::new().file("file", file_path).await?;
|
||||
let wrapped_file = wrap_file(file_path, sender.clone()).await?;
|
||||
let form = Form::new().part("file", wrapped_file);
|
||||
|
||||
let mut req = CLIENT
|
||||
.post(url)
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue