max / goingson
29 files changed,
+105 insertions,
-140 deletions
| @@ -95,11 +95,10 @@ pub async fn process_fetched_emails( | |||
| 95 | 95 | let mut reply_to_ids: Vec<String> = Vec::new(); | |
| 96 | 96 | ||
| 97 | 97 | for email in emails { | |
| 98 | - | if let Some(ref msg_id) = email.message_id { | |
| 99 | - | if existing_ids.contains(msg_id) { | |
| 98 | + | if let Some(ref msg_id) = email.message_id | |
| 99 | + | && existing_ids.contains(msg_id) { | |
| 100 | 100 | continue; | |
| 101 | 101 | } | |
| 102 | - | } | |
| 103 | 102 | ||
| 104 | 103 | // thread_id groups conversations: use in_reply_to if this is a reply, | |
| 105 | 104 | // otherwise fall back to message_id (starts a new thread). This means | |
| @@ -140,12 +139,11 @@ pub async fn process_fetched_emails( | |||
| 140 | 139 | ||
| 141 | 140 | // Clear waiting status for emails that received replies | |
| 142 | 141 | for reply_to_msg_id in &reply_to_ids { | |
| 143 | - | if let Ok(Some(original)) = email_repo.get_by_message_id(user_id, reply_to_msg_id).await { | |
| 144 | - | if original.waiting_for_response { | |
| 142 | + | if let Ok(Some(original)) = email_repo.get_by_message_id(user_id, reply_to_msg_id).await | |
| 143 | + | && original.waiting_for_response { | |
| 145 | 144 | let _ = email_repo.clear_waiting(original.id, user_id).await; | |
| 146 | 145 | result.waiting_cleared += 1; | |
| 147 | 146 | } | |
| 148 | - | } | |
| 149 | 147 | } | |
| 150 | 148 | ||
| 151 | 149 | Ok(result) |
| @@ -195,7 +195,7 @@ fn parse_relative_date(s: &str, from: NaiveDate, time: NaiveTime) -> Option<Date | |||
| 195 | 195 | ||
| 196 | 196 | let (num_str, unit) = s.split_at(s.len() - 1); | |
| 197 | 197 | let num: i64 = num_str.parse().ok()?; | |
| 198 | - | if num < 0 || num > MAX_RELATIVE_DATE_DAYS { | |
| 198 | + | if !(0..=MAX_RELATIVE_DATE_DAYS).contains(&num) { | |
| 199 | 199 | return None; | |
| 200 | 200 | } | |
| 201 | 201 |
| @@ -51,7 +51,7 @@ pub fn calculate_next_due_in_tz( | |||
| 51 | 51 | let local = dt.with_timezone(&tz); | |
| 52 | 52 | let day = local.day(); | |
| 53 | 53 | let month_len = days_in_month(local.year(), local.month()); | |
| 54 | - | if day == month_len && day >= 29 && day < 31 { | |
| 54 | + | if day == month_len && (29..31).contains(&day) { | |
| 55 | 55 | Some(31) | |
| 56 | 56 | } else { | |
| 57 | 57 | None | |
| @@ -246,7 +246,7 @@ pub fn calculate_next_due_rich_in_tz( | |||
| 246 | 246 | let target_day = { | |
| 247 | 247 | let day = local.day(); | |
| 248 | 248 | let month_len = days_in_month(local.year(), local.month()); | |
| 249 | - | if day == month_len && day >= 29 && day < 31 { Some(31) } else { None } | |
| 249 | + | if day == month_len && (29..31).contains(&day) { Some(31) } else { None } | |
| 250 | 250 | }; | |
| 251 | 251 | Some(add_months(base, interval as i32, target_day, tz)) | |
| 252 | 252 | } | |
| @@ -260,6 +260,7 @@ pub fn calculate_next_due_rich_in_tz( | |||
| 260 | 260 | /// `week`: 1-4 for ordinal, -1 for last. | |
| 261 | 261 | /// `weekday`: 0=Mon..6=Sun. | |
| 262 | 262 | /// `year`/`month`/`hour`/`minute`/`second` are civil fields in `tz`. | |
| 263 | + | #[allow(clippy::too_many_arguments)] // civil date/time fields are clearer flat than boxed in a struct | |
| 263 | 264 | fn nth_weekday_in_month( | |
| 264 | 265 | year: i32, month: u32, | |
| 265 | 266 | week: i8, weekday: u8, |
| @@ -128,11 +128,10 @@ pub fn parse_search_query(input: &str) -> ParsedSearchQuery { | |||
| 128 | 128 | ||
| 129 | 129 | // is: filter | |
| 130 | 130 | if let Some(value) = strip_prefix_ci(token, "is:") { | |
| 131 | - | if let Some(filter) = IsFilter::parse(value) { | |
| 132 | - | if !result.is_filters.contains(&filter) { | |
| 131 | + | if let Some(filter) = IsFilter::parse(value) | |
| 132 | + | && !result.is_filters.contains(&filter) { | |
| 133 | 133 | result.is_filters.push(filter); | |
| 134 | 134 | } | |
| 135 | - | } | |
| 136 | 135 | continue; | |
| 137 | 136 | } | |
| 138 | 137 | ||
| @@ -156,11 +155,10 @@ pub fn parse_search_query(input: &str) -> ParsedSearchQuery { | |||
| 156 | 155 | ||
| 157 | 156 | // type: filter | |
| 158 | 157 | if let Some(value) = strip_prefix_ci(token, "type:") { | |
| 159 | - | if let Some(result_type) = parse_result_type(value) { | |
| 160 | - | if !result.result_types.contains(&result_type) { | |
| 158 | + | if let Some(result_type) = parse_result_type(value) | |
| 159 | + | && !result.result_types.contains(&result_type) { | |
| 161 | 160 | result.result_types.push(result_type); | |
| 162 | 161 | } | |
| 163 | - | } | |
| 164 | 162 | continue; | |
| 165 | 163 | } | |
| 166 | 164 |
| @@ -120,11 +120,10 @@ impl Validate for UpdateTask { | |||
| 120 | 120 | impl Validate for NewEvent { | |
| 121 | 121 | fn validate(&self) -> Result<(), CoreError> { | |
| 122 | 122 | validate_required_string("title", &self.title, MAX_EVENT_TITLE_LENGTH)?; | |
| 123 | - | if let Some(end) = self.end_time { | |
| 124 | - | if end <= self.start_time { | |
| 123 | + | if let Some(end) = self.end_time | |
| 124 | + | && end <= self.start_time { | |
| 125 | 125 | return Err(CoreError::validation("end_time", "must be after start_time")); | |
| 126 | 126 | } | |
| 127 | - | } | |
| 128 | 127 | Ok(()) | |
| 129 | 128 | } | |
| 130 | 129 | } | |
| @@ -133,11 +132,10 @@ impl Validate for NewEvent { | |||
| 133 | 132 | impl Validate for UpdateEvent { | |
| 134 | 133 | fn validate(&self) -> Result<(), CoreError> { | |
| 135 | 134 | validate_required_string("title", &self.title, MAX_EVENT_TITLE_LENGTH)?; | |
| 136 | - | if let Some(end) = self.end_time { | |
| 137 | - | if end <= self.start_time { | |
| 135 | + | if let Some(end) = self.end_time | |
| 136 | + | && end <= self.start_time { | |
| 138 | 137 | return Err(CoreError::validation("end_time", "must be after start_time")); | |
| 139 | 138 | } | |
| 140 | - | } | |
| 141 | 139 | Ok(()) | |
| 142 | 140 | } | |
| 143 | 141 | } |
| @@ -230,7 +230,7 @@ fn prune_old_backups(backup_dir: &std::path::Path, max_to_keep: usize) -> Result | |||
| 230 | 230 | .collect(); | |
| 231 | 231 | ||
| 232 | 232 | // Sort by creation time, newest first | |
| 233 | - | backups.sort_by(|a, b| b.1.cmp(&a.1)); | |
| 233 | + | backups.sort_by_key(|b| std::cmp::Reverse(b.1)); | |
| 234 | 234 | ||
| 235 | 235 | // Remove backups beyond the limit | |
| 236 | 236 | for (path, _) in backups.into_iter().skip(max_to_keep) { |
| @@ -120,11 +120,10 @@ pub async fn get_day_planning( | |||
| 120 | 120 | events.extend(expanded); | |
| 121 | 121 | // Check if the original also falls on this day | |
| 122 | 122 | let effective_end = r.end_time.unwrap_or(r.start_time + Duration::hours(1)); | |
| 123 | - | if effective_end >= day_start && r.start_time <= day_end { | |
| 124 | - | if !existing_ids.contains(&r.id) { | |
| 123 | + | if effective_end >= day_start && r.start_time <= day_end | |
| 124 | + | && !existing_ids.contains(&r.id) { | |
| 125 | 125 | events.push(r); | |
| 126 | 126 | } | |
| 127 | - | } | |
| 128 | 127 | } | |
| 129 | 128 | } | |
| 130 | 129 | events.sort_by_key(|e| e.start_time); |
| @@ -474,11 +474,10 @@ pub async fn cleanup_stale_temp_files() { | |||
| 474 | 474 | Err(_) => return, | |
| 475 | 475 | }; | |
| 476 | 476 | while let Ok(Some(entry)) = entries.next_entry().await { | |
| 477 | - | if let Some(name) = entry.file_name().to_str() { | |
| 478 | - | if name.starts_with("goingson_email_") && name.ends_with(".html") { | |
| 477 | + | if let Some(name) = entry.file_name().to_str() | |
| 478 | + | && name.starts_with("goingson_email_") && name.ends_with(".html") { | |
| 479 | 479 | let _ = tokio::fs::remove_file(entry.path()).await; | |
| 480 | 480 | } | |
| 481 | - | } | |
| 482 | 481 | } | |
| 483 | 482 | } | |
| 484 | 483 | ||
| @@ -525,7 +524,7 @@ pub async fn create_email(state: State<'_, Arc<AppState>>, input: EmailInput) -> | |||
| 525 | 524 | #[tauri::command] | |
| 526 | 525 | #[instrument(skip_all)] | |
| 527 | 526 | pub async fn save_email_draft(state: State<'_, Arc<AppState>>, input: DraftInput) -> Result<EmailResponse, ApiError> { | |
| 528 | - | let id = input.id.unwrap_or_else(EmailId::new); | |
| 527 | + | let id = input.id.unwrap_or_default(); | |
| 529 | 528 | let account_id = input.account_id; | |
| 530 | 529 | let from = if let Some(aid) = account_id { | |
| 531 | 530 | let acct = state.email_accounts.get_by_id(aid, DESKTOP_USER_ID).await?; | |
| @@ -667,7 +666,7 @@ async fn send_email_inner(state: &Arc<AppState>, input: SendEmailInput) -> Resul | |||
| 667 | 666 | let message_id = if uses_jmap(&account) { | |
| 668 | 667 | return Err(ApiError::bad_request("JMAP email sending not yet implemented - use IMAP account")); | |
| 669 | 668 | } else if uses_oauth_imap(&account) { | |
| 670 | - | let access_token = get_valid_access_token(&state, &account).await?; | |
| 669 | + | let access_token = get_valid_access_token(state, &account).await?; | |
| 671 | 670 | let smtp_client = SmtpClient::with_oauth( | |
| 672 | 671 | &account.smtp_server, | |
| 673 | 672 | account.smtp_port as u16, | |
| @@ -775,8 +774,7 @@ async fn create_implicit_contacts( | |||
| 775 | 774 | ||
| 776 | 775 | // Derive display name from the local part of the email address | |
| 777 | 776 | let display_name = addr.split('@').next().unwrap_or(&addr) | |
| 778 | - | .replace('.', " ") | |
| 779 | - | .replace('_', " ") | |
| 777 | + | .replace(['.', '_'], " ") | |
| 780 | 778 | .split_whitespace() | |
| 781 | 779 | .map(|w| { | |
| 782 | 780 | let mut c = w.chars(); | |
| @@ -866,13 +864,11 @@ pub async fn move_email_to_folder( | |||
| 866 | 864 | let current_folder = email.source_folder.as_deref().unwrap_or("INBOX"); | |
| 867 | 865 | ||
| 868 | 866 | // Attempt IMAP move if the email has an account and UID | |
| 869 | - | if let (Some(account_id), Some(uid)) = (email.email_account_id, email.imap_uid) { | |
| 870 | - | if let Some((imap_client, _)) = build_imap_client(&state, account_id, "move_to_folder").await { | |
| 871 | - | if let Err(e) = imap_client.move_message(uid as u32, current_folder, &folder).await { | |
| 867 | + | if let (Some(account_id), Some(uid)) = (email.email_account_id, email.imap_uid) | |
| 868 | + | && let Some((imap_client, _)) = build_imap_client(&state, account_id, "move_to_folder").await | |
| 869 | + | && let Err(e) = imap_client.move_message(uid as u32, current_folder, &folder).await { | |
| 872 | 870 | warn!("IMAP move failed (local update will proceed): {}", e); | |
| 873 | 871 | } | |
| 874 | - | } | |
| 875 | - | } | |
| 876 | 872 | ||
| 877 | 873 | // Always update locally | |
| 878 | 874 | Ok(state.emails.update_source_folder(id, DESKTOP_USER_ID, &folder).await?) | |
| @@ -913,13 +909,11 @@ pub async fn archive_email(state: State<'_, Arc<AppState>>, id: EmailId) -> Resu | |||
| 913 | 909 | .await? | |
| 914 | 910 | .or_not_found("email", id)?; | |
| 915 | 911 | ||
| 916 | - | if let (Some(account_id), Some(imap_uid)) = (email.email_account_id, email.imap_uid) { | |
| 917 | - | if let Some((imap_client, archive_folder)) = build_imap_client(&state, account_id, "archive").await { | |
| 918 | - | if let Err(e) = imap_client.archive_message(imap_uid as u32, &archive_folder).await { | |
| 912 | + | if let (Some(account_id), Some(imap_uid)) = (email.email_account_id, email.imap_uid) | |
| 913 | + | && let Some((imap_client, archive_folder)) = build_imap_client(&state, account_id, "archive").await | |
| 914 | + | && let Err(e) = imap_client.archive_message(imap_uid as u32, &archive_folder).await { | |
| 919 | 915 | warn!("Failed to archive on IMAP server: {}", e); | |
| 920 | 916 | } | |
| 921 | - | } | |
| 922 | - | } | |
| 923 | 917 | ||
| 924 | 918 | Ok(state.emails.archive(id, DESKTOP_USER_ID).await?) | |
| 925 | 919 | } | |
| @@ -936,13 +930,11 @@ pub async fn unarchive_email(state: State<'_, Arc<AppState>>, id: EmailId) -> Re | |||
| 936 | 930 | .await? | |
| 937 | 931 | .or_not_found("email", id)?; | |
| 938 | 932 | ||
| 939 | - | if let (Some(account_id), Some(imap_uid)) = (email.email_account_id, email.imap_uid) { | |
| 940 | - | if let Some((imap_client, archive_folder)) = build_imap_client(&state, account_id, "unarchive").await { | |
| 941 | - | if let Err(e) = imap_client.unarchive_message(imap_uid as u32, &archive_folder).await { | |
| 933 | + | if let (Some(account_id), Some(imap_uid)) = (email.email_account_id, email.imap_uid) | |
| 934 | + | && let Some((imap_client, archive_folder)) = build_imap_client(&state, account_id, "unarchive").await | |
| 935 | + | && let Err(e) = imap_client.unarchive_message(imap_uid as u32, &archive_folder).await { | |
| 942 | 936 | warn!("Failed to unarchive on IMAP server: {}", e); | |
| 943 | 937 | } | |
| 944 | - | } | |
| 945 | - | } | |
| 946 | 938 | ||
| 947 | 939 | Ok(state.emails.unarchive(id, DESKTOP_USER_ID).await?) | |
| 948 | 940 | } |
| @@ -257,11 +257,10 @@ pub async fn create_email_account(state: State<'_, Arc<AppState>>, input: EmailA | |||
| 257 | 257 | return Err(ApiError::validation("smtpServer", "SMTP server is required")); | |
| 258 | 258 | } | |
| 259 | 259 | ||
| 260 | - | if let Some(ref folder) = input.archive_folder_name { | |
| 261 | - | if folder.contains('\r') || folder.contains('\n') || folder.chars().any(|c| c.is_control()) { | |
| 260 | + | if let Some(ref folder) = input.archive_folder_name | |
| 261 | + | && (folder.contains('\r') || folder.contains('\n') || folder.chars().any(|c| c.is_control())) { | |
| 262 | 262 | return Err(ApiError::validation("archiveFolderName", "Folder name contains invalid characters")); | |
| 263 | 263 | } | |
| 264 | - | } | |
| 265 | 264 | ||
| 266 | 265 | // Never write the password to the DB column; store it in the OS keychain | |
| 267 | 266 | // and leave the column empty (mirrors the OAuth token path). | |
| @@ -301,11 +300,10 @@ pub async fn update_email_account(state: State<'_, Arc<AppState>>, id: EmailAcco | |||
| 301 | 300 | return Err(ApiError::validation("emailAddress", "Invalid email address")); | |
| 302 | 301 | } | |
| 303 | 302 | ||
| 304 | - | if let Some(ref folder) = input.archive_folder_name { | |
| 305 | - | if folder.contains('\r') || folder.contains('\n') || folder.chars().any(|c| c.is_control()) { | |
| 303 | + | if let Some(ref folder) = input.archive_folder_name | |
| 304 | + | && (folder.contains('\r') || folder.contains('\n') || folder.chars().any(|c| c.is_control())) { | |
| 306 | 305 | return Err(ApiError::validation("archiveFolderName", "Folder name contains invalid characters")); | |
| 307 | 306 | } | |
| 308 | - | } | |
| 309 | 307 | ||
| 310 | 308 | // Route a changed password through the keychain. Pass `Some("")` to the | |
| 311 | 309 | // repo so any stale plaintext in the column is cleared; `None` leaves the |
| @@ -171,11 +171,10 @@ async fn sync_imap_account_inner( | |||
| 171 | 171 | ||
| 172 | 172 | // --- Update sync states --- | |
| 173 | 173 | if let Some(validity) = inbox_result.uid_validity { | |
| 174 | - | if let Some(ref prev) = inbox_sync_state { | |
| 175 | - | if prev.uid_validity != validity { | |
| 174 | + | if let Some(ref prev) = inbox_sync_state | |
| 175 | + | && prev.uid_validity != validity { | |
| 176 | 176 | state.email_accounts.delete_folder_sync_state(id, "INBOX").await.ok(); | |
| 177 | 177 | } | |
| 178 | - | } | |
| 179 | 178 | let new_max = inbox_result.max_uid_fetched | |
| 180 | 179 | .unwrap_or_else(|| inbox_sync_state.as_ref().map_or(0, |s| s.last_seen_uid)); | |
| 181 | 180 | if new_max > 0 { | |
| @@ -184,11 +183,10 @@ async fn sync_imap_account_inner( | |||
| 184 | 183 | } | |
| 185 | 184 | ||
| 186 | 185 | if let Some(validity) = archive_uid_validity { | |
| 187 | - | if let Some(ref prev) = archive_sync_state { | |
| 188 | - | if prev.uid_validity != validity { | |
| 186 | + | if let Some(ref prev) = archive_sync_state | |
| 187 | + | && prev.uid_validity != validity { | |
| 189 | 188 | state.email_accounts.delete_folder_sync_state(id, &archive_folder).await.ok(); | |
| 190 | 189 | } | |
| 191 | - | } | |
| 192 | 190 | let new_max = archive_max_uid | |
| 193 | 191 | .unwrap_or_else(|| archive_sync_state.as_ref().map_or(0, |s| s.last_seen_uid)); | |
| 194 | 192 | if new_max > 0 { | |
| @@ -470,10 +468,9 @@ fn write_pending_blobs(blobs_dir: &Path, pending: Vec<(String, Vec<u8>)>) { | |||
| 470 | 468 | } | |
| 471 | 469 | for (hash, data) in pending { | |
| 472 | 470 | let blob_path = blobs_dir.join(&hash); | |
| 473 | - | if !blob_path.exists() { | |
| 474 | - | if let Err(e) = std::fs::write(&blob_path, &data) { | |
| 471 | + | if !blob_path.exists() | |
| 472 | + | && let Err(e) = std::fs::write(&blob_path, &data) { | |
| 475 | 473 | warn!(blob_hash = %hash, "Failed to write attachment blob: {}", e); | |
| 476 | 474 | } | |
| 477 | - | } | |
| 478 | 475 | } | |
| 479 | 476 | } |
| @@ -331,11 +331,10 @@ pub async fn create_event(state: State<'_, Arc<AppState>>, input: EventInput) -> | |||
| 331 | 331 | } | |
| 332 | 332 | ||
| 333 | 333 | // Validate end_time > start_time if end_time is provided | |
| 334 | - | if let Some(end_time) = input.end_time { | |
| 335 | - | if end_time <= input.start_time { | |
| 334 | + | if let Some(end_time) = input.end_time | |
| 335 | + | && end_time <= input.start_time { | |
| 336 | 336 | return Err(ApiError::validation("endTime", "End time must be after start time")); | |
| 337 | 337 | } | |
| 338 | - | } | |
| 339 | 338 | ||
| 340 | 339 | let recurrence = input.recurrence.as_deref().map(Recurrence::from_str_or_default).unwrap_or(Recurrence::None); | |
| 341 | 340 | let block_type = input.block_type.as_deref().and_then(BlockType::from_str_opt); |
| @@ -318,7 +318,7 @@ pub async fn list_backups(app: tauri::AppHandle) -> Result<Vec<BackupInfoRespons | |||
| 318 | 318 | } | |
| 319 | 319 | ||
| 320 | 320 | // Sort by creation time, newest first | |
| 321 | - | backups.sort_by(|a, b| b.created_at.cmp(&a.created_at)); | |
| 321 | + | backups.sort_by_key(|b| std::cmp::Reverse(b.created_at)); | |
| 322 | 322 | ||
| 323 | 323 | Ok(backups) | |
| 324 | 324 | } | |
| @@ -435,7 +435,7 @@ pub async fn delete_backup( | |||
| 435 | 435 | } | |
| 436 | 436 | ||
| 437 | 437 | // Verify it's actually a backup file | |
| 438 | - | if !canonical_path.extension().is_some_and(|ext| ext == "gz") | |
| 438 | + | if canonical_path.extension().is_none_or(|ext| ext != "gz") | |
| 439 | 439 | || !canonical_path.to_string_lossy().ends_with(".json.gz") | |
| 440 | 440 | { | |
| 441 | 441 | return Err(ApiError::bad_request( |
| @@ -303,8 +303,8 @@ pub async fn import_ics( | |||
| 303 | 303 | } | |
| 304 | 304 | ||
| 305 | 305 | // Dedup by UID (real or synthetic) | |
| 306 | - | if let Some(ref uid) = parsed.external_id { | |
| 307 | - | if let Ok(Some(_)) = state | |
| 306 | + | if let Some(ref uid) = parsed.external_id | |
| 307 | + | && let Ok(Some(_)) = state | |
| 308 | 308 | .events | |
| 309 | 309 | .find_by_external_id("ics", uid, DESKTOP_USER_ID) | |
| 310 | 310 | .await | |
| @@ -312,7 +312,6 @@ pub async fn import_ics( | |||
| 312 | 312 | skipped += 1; | |
| 313 | 313 | continue; | |
| 314 | 314 | } | |
| 315 | - | } | |
| 316 | 315 | ||
| 317 | 316 | let new_event = NewEvent { | |
| 318 | 317 | user_id: Some(DESKTOP_USER_ID), |
| @@ -172,8 +172,8 @@ pub fn get_snooze_options() -> SnoozeOptionsResponse { | |||
| 172 | 172 | Some(now + Duration::hours(3)) | |
| 173 | 173 | }; | |
| 174 | 174 | ||
| 175 | - | if let Some(lt) = later_today { | |
| 176 | - | if lt > now { | |
| 175 | + | if let Some(lt) = later_today | |
| 176 | + | && lt > now { | |
| 177 | 177 | let utc_time = lt.with_timezone(&Utc); | |
| 178 | 178 | options.push(SnoozeOption { | |
| 179 | 179 | key: "laterToday".to_string(), | |
| @@ -182,7 +182,6 @@ pub fn get_snooze_options() -> SnoozeOptionsResponse { | |||
| 182 | 182 | formatted: format_snooze_time(<), | |
| 183 | 183 | }); | |
| 184 | 184 | } | |
| 185 | - | } | |
| 186 | 185 | ||
| 187 | 186 | // Tomorrow 9am | |
| 188 | 187 | let tomorrow = today + Duration::days(1); |
| @@ -626,15 +626,14 @@ pub async fn complete_task(state: State<'_, Arc<AppState>>, id: TaskId) -> Resul | |||
| 626 | 626 | // check naturally prevents auto-complete when a task recurs. | |
| 627 | 627 | if let Some(milestone_id) = task.milestone_id { | |
| 628 | 628 | let remaining = state.tasks.count_incomplete_by_milestone(milestone_id, DESKTOP_USER_ID).await?; | |
| 629 | - | if remaining == 0 { | |
| 630 | - | if let Some(ms) = state.milestones.get_by_id(milestone_id, DESKTOP_USER_ID).await? { | |
| 629 | + | if remaining == 0 | |
| 630 | + | && let Some(ms) = state.milestones.get_by_id(milestone_id, DESKTOP_USER_ID).await? { | |
| 631 | 631 | state.milestones.update( | |
| 632 | 632 | milestone_id, DESKTOP_USER_ID, | |
| 633 | 633 | &ms.name, &ms.description, ms.target_date, | |
| 634 | 634 | &MilestoneStatus::Completed, | |
| 635 | 635 | ).await?; | |
| 636 | 636 | } | |
| 637 | - | } | |
| 638 | 637 | } | |
| 639 | 638 | ||
| 640 | 639 | Ok(CompleteTaskResponse { |
| @@ -147,6 +147,8 @@ pub async fn open_external_url( | |||
| 147 | 147 | #[cfg(not(any(target_os = "ios", target_os = "android")))] | |
| 148 | 148 | { | |
| 149 | 149 | use tauri_plugin_shell::ShellExt; | |
| 150 | + | // TODO: migrate to tauri-plugin-opener; shell().open is deprecated. | |
| 151 | + | #[allow(deprecated)] | |
| 150 | 152 | app.shell() | |
| 151 | 153 | .open(trimmed.to_string(), None) | |
| 152 | 154 | .map_api_err("Failed to open URL", ApiError::internal)?; |
| @@ -208,7 +208,7 @@ impl ImapClient { | |||
| 208 | 208 | // Build a set of UIDs that are safe to fetch | |
| 209 | 209 | while let Some(result) = size_stream.next().await { | |
| 210 | 210 | if let Ok(msg) = result { | |
| 211 | - | let over_limit = msg.size.map_or(false, |s| s > MAX_EMAIL_SIZE); | |
| 211 | + | let over_limit = msg.size.is_some_and(|s| s > MAX_EMAIL_SIZE); | |
| 212 | 212 | if over_limit { | |
| 213 | 213 | skipped_large += 1; | |
| 214 | 214 | tracing::warn!(uid = ?msg.uid, size = ?msg.size, folder = %folder, "Skipping oversized email"); | |
| @@ -383,18 +383,16 @@ impl ImapClient { | |||
| 383 | 383 | let mut safe_uids: Vec<u32> = Vec::new(); | |
| 384 | 384 | let mut skipped_large = 0usize; | |
| 385 | 385 | while let Some(result) = size_stream.next().await { | |
| 386 | - | if let Ok(msg) = result { | |
| 387 | - | if let Some(uid) = msg.uid { | |
| 388 | - | if let Some(size) = msg.size { | |
| 389 | - | if size > MAX_EMAIL_SIZE { | |
| 386 | + | if let Ok(msg) = result | |
| 387 | + | && let Some(uid) = msg.uid { | |
| 388 | + | if let Some(size) = msg.size | |
| 389 | + | && size > MAX_EMAIL_SIZE { | |
| 390 | 390 | skipped_large += 1; | |
| 391 | 391 | tracing::warn!(uid, size, folder = %folder, "Skipping oversized email ({} bytes)", size); | |
| 392 | 392 | continue; | |
| 393 | 393 | } | |
| 394 | - | } | |
| 395 | 394 | safe_uids.push(uid); | |
| 396 | 395 | } | |
| 397 | - | } | |
| 398 | 396 | } | |
| 399 | 397 | drop(size_stream); | |
| 400 | 398 |
| @@ -172,5 +172,5 @@ fn rand_skip(backoff: u32) -> bool { | |||
| 172 | 172 | .duration_since(SystemTime::UNIX_EPOCH) | |
| 173 | 173 | .unwrap_or_default() | |
| 174 | 174 | .subsec_nanos(); | |
| 175 | - | (nanos % backoff) != 0 | |
| 175 | + | !nanos.is_multiple_of(backoff) | |
| 176 | 176 | } |
| @@ -121,8 +121,8 @@ fn parse_datetime_property(properties: &[Property], name: &str) -> Option<DateTi | |||
| 121 | 121 | }); | |
| 122 | 122 | ||
| 123 | 123 | // Try parsing with timezone | |
| 124 | - | if let Some(tz_name) = tzid { | |
| 125 | - | if let Some(ndt) = parse_ical_datetime(value) { | |
| 124 | + | if let Some(tz_name) = tzid | |
| 125 | + | && let Some(ndt) = parse_ical_datetime(value) { | |
| 126 | 126 | // Resolve IANA timezone and convert local time to UTC | |
| 127 | 127 | if let Ok(tz) = tz_name.parse::<Tz>() { | |
| 128 | 128 | // .earliest() returns None for times in a DST spring-forward gap. | |
| @@ -140,7 +140,6 @@ fn parse_datetime_property(properties: &[Property], name: &str) -> Option<DateTi | |||
| 140 | 140 | // intent than a blind UTC stamp). | |
| 141 | 141 | return naive_local_to_utc(&ndt); | |
| 142 | 142 | } | |
| 143 | - | } | |
| 144 | 143 | ||
| 145 | 144 | // UTC (ends with Z) | |
| 146 | 145 | if let Some(clean) = value.strip_suffix('Z') { | |
| @@ -179,7 +178,7 @@ fn parse_ical_datetime(s: &str) -> Option<NaiveDateTime> { | |||
| 179 | 178 | let hour: u32 = time_part.get(0..2)?.parse().ok()?; | |
| 180 | 179 | let min: u32 = time_part.get(2..4)?.parse().ok()?; | |
| 181 | 180 | let sec: u32 = time_part.get(4..6)?.parse().ok()?; | |
| 182 | - | Some(date.and_hms_opt(hour, min, sec)?) | |
| 181 | + | date.and_hms_opt(hour, min, sec) | |
| 183 | 182 | } | |
| 184 | 183 | ||
| 185 | 184 | /// Parse a DATE-only value to midnight UTC. | |
| @@ -264,11 +263,10 @@ fn parse_rrule(rule: &str) -> Recurrence { | |||
| 264 | 263 | } | |
| 265 | 264 | ||
| 266 | 265 | // Complex rules with BYDAY having multiple days → skip | |
| 267 | - | if let Some(byday) = parts.get("BYDAY") { | |
| 268 | - | if byday.contains(',') { | |
| 266 | + | if let Some(byday) = parts.get("BYDAY") | |
| 267 | + | && byday.contains(',') { | |
| 269 | 268 | return Recurrence::None; | |
| 270 | 269 | } | |
| 271 | - | } | |
| 272 | 270 | ||
| 273 | 271 | // UNTIL or COUNT → still map the frequency (they just limit recurrence) | |
| 274 | 272 | match freq.as_str() { |
| @@ -73,11 +73,10 @@ pub fn parse_vcf(content: &str) -> Result<Vec<ParsedVCard>, String> { | |||
| 73 | 73 | in_card = true; | |
| 74 | 74 | lines.clear(); | |
| 75 | 75 | } else if trimmed.eq_ignore_ascii_case("END:VCARD") { | |
| 76 | - | if in_card { | |
| 77 | - | if let Some(card) = parse_single_vcard(&lines) { | |
| 76 | + | if in_card | |
| 77 | + | && let Some(card) = parse_single_vcard(&lines) { | |
| 78 | 78 | contacts.push(card); | |
| 79 | 79 | } | |
| 80 | - | } | |
| 81 | 80 | in_card = false; | |
| 82 | 81 | } else if in_card { | |
| 83 | 82 | lines.push(line); | |
| @@ -378,8 +377,8 @@ fn decode_quoted_printable(input: &str) -> String { | |||
| 378 | 377 | continue; | |
| 379 | 378 | } | |
| 380 | 379 | // Hex-encoded byte: =XX | |
| 381 | - | if i + 2 < bytes.len() { | |
| 382 | - | if let (Some(h), Some(l)) = ( | |
| 380 | + | if i + 2 < bytes.len() | |
| 381 | + | && let (Some(h), Some(l)) = ( | |
| 383 | 382 | hex_val(bytes[i + 1]), | |
| 384 | 383 | hex_val(bytes[i + 2]), | |
| 385 | 384 | ) { | |
| @@ -387,7 +386,6 @@ fn decode_quoted_printable(input: &str) -> String { | |||
| 387 | 386 | i += 3; | |
| 388 | 387 | continue; | |
| 389 | 388 | } | |
| 390 | - | } | |
| 391 | 389 | } | |
| 392 | 390 | decoded_bytes.push(bytes[i]); | |
| 393 | 391 | i += 1; |