Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions i18n/en/cosmicding.ftl
Original file line number Diff line number Diff line change
Expand Up @@ -57,8 +57,10 @@ import-bookmarks = Import Bookmarks
import-bookmarks-body = Select account to import bookmarks to:
import-bookmarks-error = Failed to import bookmarks: {$error}
import-bookmarks-file-not-found = Import file not found at {$path}. Please place your bookmarks HTML file at this location.
import-bookmarks-finished = Successfully imported {$count} bookmarks
import-bookmarks-no-path = Please select a file path for import
import-bookmarks-started = Importing {$count} bookmarks...
importing-bookmarks = Importing Bookmarks
instance = Instance
invalid-api-token = Invalid API token
items-per-page = Items Per Page - {{$count}}
Expand All @@ -81,6 +83,7 @@ refresh = Refresh
refresh-bookmarks = Refresh Bookmarks
refreshed-bookmarks = Refreshed bookmarks
refreshed-bookmarks-for-account = Refreshed account {$acc}
refreshing-accounts = Refreshing Accounts
remove = Remove
remove-account-confirm = Are you sure you wish to delete this account?
remove-bookmark-confirm = Are you sure you wish to delete this bookmark?
Expand Down
3 changes: 3 additions & 0 deletions i18n/sv/cosmicding.ftl
Original file line number Diff line number Diff line change
Expand Up @@ -57,8 +57,10 @@ import-bookmarks = Importera bokmärken
import-bookmarks-body = Välj konto att importera bokmärken till:
import-bookmarks-error = Kunde inte importera bokmärken: {$error}
import-bookmarks-file-not-found = Importfilen hittades inte på {$path}. Placera din bokmärkes-HTML-fil på den här platsen.
import-bookmarks-finished = Importerade {$count} bokmärken
import-bookmarks-no-path = Välj en filsökväg för import
import-bookmarks-started = Importerar {$count} bokmärken...
importing-bookmarks = Importerar bokmärken
instance = Instans
invalid-api-token = Ogiltig API-token
items-per-page = Poster per sida - {$count}
Expand All @@ -81,6 +83,7 @@ refresh = Uppdatera
refresh-bookmarks = Uppdatera bokmärken
refreshed-bookmarks = Uppdaterat bokmärken
refreshed-bookmarks-for-account = Uppdaterat konto {$acc}
refreshing-accounts = Uppdaterar konton
remove = Ta bort
remove-account-confirm = Är du säker på att du vill ta bort det här kontot?
remove-bookmark-confirm = Är du säker på att du vill ta bort det här bokmärket?
Expand Down
432 changes: 280 additions & 152 deletions src/app.rs

Large diffs are not rendered by default.

29 changes: 24 additions & 5 deletions src/app/actions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,13 +33,17 @@ pub enum ApplicationAction {
DialogCancel,
DialogUpdate(DialogPage),
DoneAddAccount(Account, Option<LinkdingAccountApiResponse>),
DoneAddBookmark(Account, Option<BookmarkCheckDetailsResponse>),
DoneAddBookmark(
Account,
Option<BookmarkCheckDetailsResponse>,
Option<ImportAction>,
Vec<(Bookmark, ImportAction)>,
),
DoneEditAccount(Account, Option<LinkdingAccountApiResponse>),
DoneEditBookmark(Account, Option<BookmarkCheckDetailsResponse>),
DoneFetchFaviconForBookmark(String, Bytes),
DoneRefreshAccountProfile(Account, Option<LinkdingAccountApiResponse>),
DoneRefreshBookmarksForAccount(Account, Vec<DetailedResponse>),
DoneRefreshBookmarksForAllAccounts(Vec<DetailedResponse>),
DoneRefreshSingleAccount(DetailedResponse, Vec<Account>),
DoneRemoveBookmark(Account, Bookmark, Option<BookmarkRemoveResponse>),
EditAccountForm(Account),
EditBookmarkForm(i64, Bookmark),
Expand All @@ -55,7 +59,8 @@ pub enum ApplicationAction {
SetImportPath(Option<PathBuf>),
PerformExportBookmarks(Vec<Account>),
PerformImportBookmarks(Account),
DoneImportBookmarks,
CancelImportBookmarks(u64),
DoneImportBookmarks(usize),
IncrementPageIndex(String),
InputBookmarkDescription(widget::text_editor::Action),
InputBookmarkNotes(widget::text_editor::Action),
Expand Down Expand Up @@ -86,7 +91,12 @@ pub enum ApplicationAction {
SetItemsPerPage(u8),
SortOption(SortOption),
StartAddAccount(Account),
StartAddBookmark(Account, Bookmark),
StartAddBookmark(
Account,
Bookmark,
Option<ImportAction>,
Vec<(Bookmark, ImportAction)>,
),
StartEditAccount(Account),
StartEditBookmark(Account, Bookmark),
StartFetchFaviconForBookmark(Bookmark),
Expand All @@ -105,6 +115,7 @@ pub enum ApplicationAction {
#[derive(Debug, Clone)]
pub enum AccountsAction {
AddAccount,
CancelImport(u64),
DecrementPageIndex,
DeleteAccount(Account),
EditAccount(Account),
Expand All @@ -116,6 +127,7 @@ pub enum AccountsAction {
#[derive(Debug, Clone)]
pub enum BookmarksAction {
AddBookmark,
CancelImport(u64),
ClearSearch,
DecrementPageIndex,
DeleteBookmark(i64, Bookmark),
Expand All @@ -128,3 +140,10 @@ pub enum BookmarksAction {
SearchBookmarks(String),
ViewNotes(Bookmark),
}

#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct ImportAction {
pub import_id: u64,
pub total_count: usize,
pub current_index: usize,
}
8 changes: 7 additions & 1 deletion src/app/menu.rs
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,13 @@ pub fn menu_bar<'a>(
menu::items(
key_binds,
vec![
Item::Button(fl!("add-account"), None, MenuAction::AddAccount),
if accounts_present && matches!(app_state, ApplicationState::Ready)
|| matches!(app_state, ApplicationState::NoEnabledAccounts)
{
Item::Button(fl!("add-account"), None, MenuAction::AddAccount)
} else {
Item::ButtonDisabled(fl!("add-account"), None, MenuAction::AddAccount)
},
if accounts_present && matches!(app_state, ApplicationState::Ready) {
Item::Button(fl!("add-bookmark"), None, MenuAction::AddBookmark)
} else {
Expand Down
2 changes: 2 additions & 0 deletions src/app/nav.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ impl AppNavPage {
app.sync_status,
&app.accounts_cursor,
&app.timeline,
app.operation_progress.as_ref(),
)
.map(ApplicationAction::AccountsView),
AppNavPage::BookmarksView => app
Expand All @@ -51,6 +52,7 @@ impl AppNavPage {
&app.bookmarks_cursor,
app.accounts_cursor.total_entries == 0,
&app.timeline,
app.operation_progress.as_ref(),
)
.map(ApplicationAction::BookmarksView),
}
Expand Down
165 changes: 108 additions & 57 deletions src/http/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,29 +23,21 @@ use std::{
};
use urlencoding::encode;

pub async fn fetch_bookmarks_from_all_accounts(accounts: Vec<Account>) -> Vec<DetailedResponse> {
let mut all_responses: Vec<DetailedResponse> = Vec::new();
for account in accounts {
if account.enabled {
match fetch_bookmarks_for_account(&account).await {
Ok(new_response) => {
all_responses.push(new_response);
}
Err(e) => {
let epoch_timestamp = SystemTime::now()
.duration_since(UNIX_EPOCH)
.expect("")
.as_secs();
#[allow(clippy::cast_possible_wrap)]
let error_response =
DetailedResponse::new(account, epoch_timestamp as i64, false, None);
all_responses.push(error_response);
log::error!("Error fetching bookmarks: {e}");
}
}
pub async fn fetch_bookmarks_for_single_account(account: Account) -> DetailedResponse {
match fetch_bookmarks_for_account(&account).await {
Ok(response) => response,
Err(e) => {
let epoch_timestamp = SystemTime::now()
.duration_since(UNIX_EPOCH)
.expect("")
.as_secs();
#[allow(clippy::cast_possible_wrap)]
let error_response =
DetailedResponse::new(account, epoch_timestamp as i64, false, None);
log::error!("Error fetching bookmarks: {e}");
error_response
}
}
all_responses
}

// NOTE: (vkhitrin) perhaps this method should be split into three:
Expand Down Expand Up @@ -273,6 +265,7 @@ pub async fn populate_bookmark(
account: Account,
bookmark: Bookmark,
check_remote: bool,
disable_scraping: bool,
) -> Option<BookmarkCheckDetailsResponse> {
let rest_api_url: String = account.instance.clone() + "/api/bookmarks/";
let mut headers = HeaderMap::new();
Expand Down Expand Up @@ -304,7 +297,7 @@ pub async fn populate_bookmark(
let bookmark_url =
parse_serde_json_value_to_raw_string(transformed_json_value.get("url").unwrap());
if check_remote {
match check_bookmark_on_instance(&account, bookmark_url.clone()).await {
match check_bookmark_on_instance(&account, bookmark_url.clone(), disable_scraping).await {
Ok(check) => {
let metadata = check.metadata;
if check.bookmark.is_some() {
Expand Down Expand Up @@ -354,45 +347,92 @@ pub async fn populate_bookmark(
api_response.bookmark = Some(bookmark);
}
if api_response.is_new {
let response: reqwest::Response = http_client
.post(rest_api_url)
.headers(headers)
.json(&transformed_json_value)
.send()
.await
.unwrap();
let max_retries = 3;
let mut retry_count = 0;
let mut last_error: Option<String> = None;

match response.status() {
StatusCode::CREATED => match response.json::<Bookmark>().await {
Ok(mut value) => {
value.linkding_internal_id = value.id;
value.user_account_id = account.id;
value.id = None;
api_response.bookmark = Some(value);
api_response.successful = true;
}
Err(_e) => api_response.error = Some(fl!("failed-to-parse-response")),
},
status => {
api_response.error = Some(fl!(
"http-error",
http_rc = status.to_string(),
http_err = response.text().await.unwrap()
));
log::error!(
"Error adding bookmark: {}",
api_response.error.as_ref().unwrap()
while retry_count <= max_retries {
if retry_count > 0 {
let backoff_ms = 1000 * u64::pow(2, retry_count - 1);
log::warn!(
"Retrying bookmark creation (attempt {}/{}) after {}ms backoff",
retry_count,
max_retries,
backoff_ms
);
tokio::time::sleep(Duration::from_millis(backoff_ms)).await;
}

let response_result = http_client
.post(&rest_api_url)
.headers(headers.clone())
.json(&transformed_json_value)
.send()
.await;

match response_result {
Ok(response) => match response.status() {
StatusCode::CREATED => match response.json::<Bookmark>().await {
Ok(mut value) => {
value.linkding_internal_id = value.id;
value.user_account_id = account.id;
value.id = None;
api_response.bookmark = Some(value);
api_response.successful = true;
break;
}
Err(_e) => {
api_response.error = Some(fl!("failed-to-parse-response"));
break;
}
},
StatusCode::SERVICE_UNAVAILABLE => {
let error_msg = response.text().await.unwrap_or_default();
last_error = Some(fl!(
"http-error",
http_rc = StatusCode::SERVICE_UNAVAILABLE.to_string(),
http_err = error_msg
));
log::error!(
"Error adding bookmark (503): {}",
last_error.as_ref().unwrap()
);
retry_count += 1;
}
status => {
api_response.error = Some(fl!(
"http-error",
http_rc = status.to_string(),
http_err = response.text().await.unwrap_or_default()
));
log::error!(
"Error adding bookmark: {}",
api_response.error.as_ref().unwrap()
);
break;
}
},
Err(e) => {
api_response.error = Some(format!("Request failed: {e}"));
log::error!("Error sending request: {e}");
break;
}
}
}
} else {
match edit_bookmark(&account, &api_response.bookmark.clone().unwrap().clone()).await {

if retry_count > max_retries && api_response.error.is_none() {
api_response.error = last_error;
}
} else if let Some(bookmark) = &api_response.bookmark {
match edit_bookmark(&account, bookmark).await {
Ok(value) => {
api_response.bookmark = Some(value);
api_response.successful = true;
}
Err(_e) => api_response.error = Some(fl!("failed-to-parse-response")),
}
} else {
api_response.error = Some(fl!("failed-to-parse-response"));
}
Some(api_response)
}
Expand Down Expand Up @@ -576,15 +616,26 @@ pub async fn check_account_on_instance(
pub async fn check_bookmark_on_instance(
account: &Account,
url: String,
disable_scraping: bool,
) -> Result<LinkdingBookmarksApiCheckResponse, Box<dyn std::error::Error>> {
let mut rest_api_url: String = String::new();
let encoded_bookmark_url = encode(&url);
write!(
&mut rest_api_url,
"{}/api/bookmarks/check/?url={}",
account.instance, encoded_bookmark_url
)
.unwrap();

if disable_scraping {
write!(
&mut rest_api_url,
"{}/api/bookmarks/check/?url={}&disable_scraping=true",
account.instance, encoded_bookmark_url
)
.unwrap();
} else {
write!(
&mut rest_api_url,
"{}/api/bookmarks/check/?url={}",
account.instance, encoded_bookmark_url
)
.unwrap();
}
let mut headers = HeaderMap::new();
let http_client = ClientBuilder::new()
.danger_accept_invalid_certs(account.trust_invalid_certs)
Expand Down
1 change: 1 addition & 0 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ mod models;
mod pages;
mod style;
mod utils;
mod widgets;

use core::settings;

Expand Down
1 change: 1 addition & 0 deletions src/models/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,5 @@ pub mod account;
pub mod bookmarks;
pub mod db_cursor;
pub mod favicon_cache;
pub mod operation;
pub mod sync_status;
8 changes: 8 additions & 0 deletions src/models/operation.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
#[derive(Debug, Clone)]
pub struct OperationProgress {
pub operation_id: u64,
pub total: usize,
pub current: usize,
pub operation_label: String,
pub cancellable: bool,
}
Loading