mirror of
https://github.com/narrrl/proton-stream.git
synced 2026-11-03 16:41:26 +01:00
feat(core,app,android,ios): rename a share
ShareStore::rename changes only the name; the id that the catalog, watch state and offline files are keyed by stays the same. Sync carries the change as it does any other change to the share's register. The desktop renames in the share's row. The phones rename from the share's menu. Before, the name given at add time was permanent short of removing the share.
This commit is contained in:
1 parent
5b931111b3
commit
abb3e3aaf8
10 files changed
+185
No files matched your search
@@ -1386,6 +1386,14 @@ impl AndroidEngine {
|
||||
Ok(share_record(share))
|
||||
}
|
||||
|
||||
/// Call a share something else; see `ShareStore::rename`.
|
||||
pub fn rename_share(&self, share_id: String, name: String) -> Result<ShareRecord, BridgeError> {
|
||||
self.store
|
||||
.rename(&share_id, &name)
|
||||
.map(share_record)
|
||||
.map_err(BridgeError::from_display)
|
||||
}
|
||||
|
||||
pub fn remove_share(&self, share_id: String) -> Result<(), BridgeError> {
|
||||
let _publication = self.share_publication.lock();
|
||||
let catalog = self.catalog.lock();
|
||||
|
||||
@@ -146,6 +146,11 @@ pub enum Action {
|
||||
OpenShareForm,
|
||||
/// Stop offering posters and synopses on the library page.
|
||||
DismissMetadataOffer,
|
||||
/// Call a share something else.
|
||||
RenameShare {
|
||||
id: String,
|
||||
name: String,
|
||||
},
|
||||
/// Store a new link, and password, for a share already in the library.
|
||||
RepairShare {
|
||||
id: String,
|
||||
@@ -563,6 +568,9 @@ pub struct ShareForm {
|
||||
pub repairing: Option<String>,
|
||||
pub repair_url: String,
|
||||
pub repair_password: String,
|
||||
/// The share being renamed in its own row, if any, and the name so far.
|
||||
pub renaming: Option<String>,
|
||||
pub rename_to: String,
|
||||
}
|
||||
|
||||
pub struct App {
|
||||
@@ -1671,6 +1679,10 @@ impl App {
|
||||
self.form.error = None;
|
||||
self.note(ctx, "share link pasted — add it, or name it first", false);
|
||||
}
|
||||
Action::RenameShare { id, name } => {
|
||||
self.form.renaming = None;
|
||||
self.engine.rename_share(id, name);
|
||||
}
|
||||
Action::RepairShare { id, url, password } => {
|
||||
self.crawling = true;
|
||||
self.form.repairing = None;
|
||||
|
||||
@@ -696,6 +696,16 @@ impl Engine {
|
||||
});
|
||||
}
|
||||
|
||||
/// Call a share something else. See [`ShareStore::rename`].
|
||||
pub fn rename_share(&self, id: String, name: String) {
|
||||
let engine = self.clone();
|
||||
self.runtime
|
||||
.spawn_blocking(move || match engine.store.rename(&id, &name) {
|
||||
Ok(_) => engine.load_shares(),
|
||||
Err(error) => engine.fail("rename the share", error),
|
||||
});
|
||||
}
|
||||
|
||||
/// Re-supply the link behind a share whose stored secret is unreadable —
|
||||
/// a keyring that was reset — or whose custom password changed, keeping
|
||||
/// its catalog, watch state and downloads. See
|
||||
|
||||
@@ -108,6 +108,10 @@ fn share_row(
|
||||
if ui.button("Crawl").clicked() {
|
||||
actions.push(Action::Crawl(Some(share.id.clone())));
|
||||
}
|
||||
if ui.button("Rename").clicked() {
|
||||
form.renaming = Some(share.id.clone());
|
||||
form.rename_to = share.name.clone();
|
||||
}
|
||||
// Not for a folder of the account, which has no link.
|
||||
if share.folder.is_none()
|
||||
&& ui
|
||||
@@ -127,10 +131,41 @@ fn share_row(
|
||||
if form.repairing.as_deref() == Some(share.id.as_str()) {
|
||||
repair_form(ui, share, form, actions);
|
||||
}
|
||||
if form.renaming.as_deref() == Some(share.id.as_str()) {
|
||||
rename_form(ui, share, form, actions);
|
||||
}
|
||||
});
|
||||
ui.add_space(theme::space::S);
|
||||
}
|
||||
|
||||
/// A new name for a share, in its own row. Enter saves, as everywhere else.
|
||||
fn rename_form(ui: &mut egui::Ui, share: &Share, form: &mut ShareForm, actions: &mut Vec<Action>) {
|
||||
ui.add_space(theme::space::M);
|
||||
ui.horizontal(|ui| {
|
||||
let field = ui.add(
|
||||
egui::TextEdit::singleline(&mut form.rename_to)
|
||||
.hint_text("Name")
|
||||
.desired_width(320.0),
|
||||
);
|
||||
let ready = !form.rename_to.trim().is_empty();
|
||||
let entered = field.lost_focus() && ui_enter_pressed(ui.ctx());
|
||||
if ui
|
||||
.add_enabled_ui(ready, |ui| ui::accent_button(ui, "Save"))
|
||||
.inner
|
||||
.clicked()
|
||||
|| (ready && entered)
|
||||
{
|
||||
actions.push(Action::RenameShare {
|
||||
id: share.id.clone(),
|
||||
name: form.rename_to.trim().to_owned(),
|
||||
});
|
||||
}
|
||||
if ui.button("Cancel").clicked() {
|
||||
form.renaming = None;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/// The same link again, and a password if it has one, for a share already in
|
||||
/// the library.
|
||||
fn repair_form(ui: &mut egui::Ui, share: &Share, form: &mut ShareForm, actions: &mut Vec<Action>) {
|
||||
|
||||
@@ -567,6 +567,25 @@ impl ShareStore {
|
||||
Ok(updated)
|
||||
}
|
||||
|
||||
/// Call a share something else. Only the name changes: the id, which the
|
||||
/// catalog, watch state and offline files are keyed by, stays. Sync sees
|
||||
/// the new name as a change to the share's register, like any other.
|
||||
pub fn rename(&self, id: &str, name: &str) -> Result<Share> {
|
||||
let name = name.trim();
|
||||
if name.is_empty() {
|
||||
return Err(Error::Config("a share needs a name".to_owned()));
|
||||
}
|
||||
let mut shares = self.list()?;
|
||||
let share = shares
|
||||
.iter_mut()
|
||||
.find(|share| share.id == id)
|
||||
.ok_or_else(|| Error::NotFound(format!("no share with id {id}")))?;
|
||||
share.name = name.to_owned();
|
||||
let renamed = share.clone();
|
||||
write_json(&self.dirs.shares_file(), &shares)?;
|
||||
Ok(renamed)
|
||||
}
|
||||
|
||||
/// Forget a share and its secrets.
|
||||
///
|
||||
/// The config entry goes first here, for the mirror-image reason: if the
|
||||
@@ -1257,6 +1276,25 @@ mod tests {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_renamed_share_keeps_its_id_and_refuses_an_empty_name() {
|
||||
let (store, _) = store_in("rename");
|
||||
let added = store
|
||||
.add(
|
||||
"Library",
|
||||
"https://drive.proton.me/urls/ABC123#s3cr3t",
|
||||
None,
|
||||
)
|
||||
.expect("add");
|
||||
|
||||
let renamed = store.rename(&added.id, " Anime ").expect("rename");
|
||||
assert_eq!(renamed.id, added.id);
|
||||
assert_eq!(renamed.name, "Anime");
|
||||
assert_eq!(store.list().expect("list")[0].name, "Anime");
|
||||
assert!(store.rename(&added.id, " ").is_err());
|
||||
assert!(store.rename("share-missing", "Anime").is_err());
|
||||
}
|
||||
|
||||
/// A different link is a different share. Repointing this one at it would
|
||||
/// leave every catalog row and offline file describing files that are not
|
||||
/// there.
|
||||
|
||||
Reference in new issue
Block a user