diff --git a/docs/Guide_technique_ReadRSS.md b/docs/Guide_technique_ReadRSS.md new file mode 100644 index 0000000..b95c2ab --- /dev/null +++ b/docs/Guide_technique_ReadRSS.md @@ -0,0 +1,820 @@ +# ReadRSS — Guide technique détaillé et pédagogique (≈ 30 minutes) + +Ce document est un guide COMPLÈTEMENT autonome pour expliquer ReadRSS à un public mixte (débutants en Rust compris) et servir de script d’oral. Chaque section correspond à ~1 minute. Vous trouverez pour chaque sujet: un objectif clair, les dépendances, le fonctionnement interne, un petit lexique, des schémas mentaux et, quand utile, de courts extraits de code tirés du projet. + +Astuce d’utilisation: lisez linéairement si vous découvrez le projet; pour une présentation, traitez 1 section = 1 diapo/minute. + +--- + +## 01 — Vision, promesse utilisateur et contraintes + +Objectif: un lecteur RSS/Atom local, rapide, fiable, sans complexité inutile. + +- Promesse: “j’ajoute des flux, ça se met à jour tout seul, je lis, je classe, j’ouvre dans le navigateur”. +- Contraintes de sûreté: HTTPS obligatoire (sauf loopback en dev/tests), limite 10 MiB par flux, timeouts et retries. +- Contraintes d’UX: démarrage rapide, UI réactive (aucune E/S ou réseau ne bloque le rendu), paramètres persistés immédiatement. + +Lexique: +- RSS/Atom: formats XML listant des items d’actualité. +- Poller: tâche périodique qui récupère les flux. +- Déduplication: éviter de ré‑annoncer un article déjà vu. + +Extrait (contrainte HTTPS côté core): +```rust +// rss-core/src/poller.rs +#[cfg(not(test))] +if url.scheme() != "https" { + let host_ok = matches!( + url.host_str(), + Some("localhost") | Some("127.0.0.1") | Some("::1") + ); + if !host_ok { return Err(PollError::UnsupportedScheme); } +} +``` +Décryptage simple: +- `#[cfg(not(test))]` signifie: on applique cette règle partout sauf dans les tests. Utile pour autoriser des URLs locales en tests. +- `url.scheme()` récupère le schéma (http, https…). On exige `https` en production. +- `matches!` vérifie que l’hôte est local. Si ce n’est pas le cas, on renvoie une erreur claire `UnsupportedScheme` qui sera loggée sans faire planter l’appli. + +--- + +## 02 — Carte d’architecture (vue macro) + +Workspace Cargo: +- `rss-core` (lib): modèle, parsing, polling, persistance, erreurs, “seen store”. +- `rss-gui` (app): eframe/egui (wgpu), navigation, thèmes, logique UI. + +Flux logique (de gauche à droite): + +``` +Réseau (reqwest) → Parsing (rss/atom_syndication) → FeedEntry → SeenStore (dédup) + ↘ DataApi (persist JSON) → UI (egui) +``` + +Dépendances clefs: `tokio` (async), `reqwest` (HTTP, rustls), `rss` et `atom_syndication` (parsing), `serde` (JSON), `egui/eframe` (UI), `tracing` (logs). + +Extrait (exports du coeur): +```rust +// rss-core/src/lib.rs +pub mod config; pub mod data; pub mod error; pub mod feed; pub mod poller; pub mod storage; +pub use config::{AppConfig, FeedConfig, ThemeConfig, UiConfig}; +pub use data::DataApi; +pub use error::PollError; +pub use feed::{FeedDescriptor, FeedEntry, SharedFeedList}; +pub use feed::{add_feed, list_feeds, remove_feed, shared_feed_list}; +pub use poller::{poll_once, spawn_poller, Event, PollConfig, PollerHandle}; +pub use storage::SeenStore; +``` +Décryptage simple: +- `pub mod` rend les modules visibles; `pub use` ré-exporte des types/fonctions pour une importation facile côté UI. +- L’app (`rss-gui`) peut ainsi écrire `use rss_core::spawn_poller;` sans connaître l’arborescence interne. + +--- + +## 03 — Modules (core) et responsabilités + +- `config`: charge/sauvegarde `AppConfig` (thème, UI, params de polling). +- `poller`: cadence, timeouts, retries, émet `Event::NewArticles`. +- `feed`: structures `FeedDescriptor`, `FeedEntry` et conversions RSS/Atom. +- `data`: API persistante (feeds, “lus”, cache d’articles) écriture atomique `.tmp`. +- `storage`: `SeenStore` (déduplication persistée). +- `error`: `PollError` (réseau, parsing, schéma, taille, tâche…). + +Code d’export (`rss-core/src/lib.rs`) pour tout réutiliser côté app. + +Extrait (erreurs centralisées): +```rust +// rss-core/src/error.rs +#[derive(Debug, thiserror::Error)] +pub enum PollError { + #[error("network error: {0}")] Network(#[from] reqwest::Error), + #[error("feed parsing error: {0}")] Parse(#[from] rss::Error), + #[error("poller task failed: {0}")] Task(#[from] tokio::task::JoinError), + #[error("update channel closed unexpectedly")] UpdateChannelClosed, + #[error("unsupported URL scheme (https required)")] UnsupportedScheme, + #[error("invalid feed url: {0}")] InvalidUrl(#[from] url::ParseError), + #[error("feed too large: {0} bytes")] TooLarge(u64), +} +``` +Décryptage simple: +- Chaque variante représente une cause d’échec précise. `#[from]` permet la conversion automatique depuis une erreur tierce. +- Les messages entre guillemets seront affichés tels quels dans les logs ou l’UI si besoin. + +--- + +## 04 — Lexique minimal Rust et async + +- Crate: paquet Rust (lib ou binaire). Workspace: ensemble de crates. +- Trait `Send + Sync`: partagabilité entre threads. +- `Arc`: pointeur partagé thread‑safe; `RwLock`: verrou lecture/écriture. +- `async/await`: écriture asynchrone; `tokio::spawn`: lance une tâche concurrente. +- `mpsc`/`broadcast`: canaux asynchrones (point‑à‑point / un‑à‑N). + +But: comprendre la mécanique sans plonger dans tous les détails bas niveau. + +Extrait (partage thread‑safe): +```rust +// rss-core/src/feed.rs +pub type SharedFeedList = Arc>>; +pub fn shared_feed_list(initial: Vec) -> SharedFeedList { + Arc::new(RwLock::new(initial)) +} +``` +Décryptage simple: +- `Arc>` = partage multi‑threads + plusieurs lecteurs ou un seul écrivain. +- La liste des flux est donc sûre en concurrence (poller/GUI) avec peu de contention. + +--- + +## 05 — Configuration: AppConfig (où, quand, comment) + +Chemin: `rss-core/src/config.rs` + +Rôle: centraliser les préférences utilisateur (couleurs, largeur panneau, pagination) et les paramètres réseau (timeouts, intervalle, retries). Fichier stocké par OS dans le dossier `readrss` de l’utilisateur. + +Contrat: +- Entrée: JSON partiel accepté (valeurs par défaut appliquées si clés manquantes). +- Sortie: objet `AppConfig` utilisable partout (UI + runtime). +- Erreurs: en cas d’échec de lecture, on crée un défaut et on le sauvegarde. + +Extrait: +```rust +#[derive(Debug, Clone, Serialize, Deserialize, Default)] +pub struct AppConfig { pub theme: ThemeConfig, pub feeds: FeedConfig, pub ui: UiConfig } +impl AppConfig { pub fn load() -> Self { /* défaut si lecture échoue + auto-save */ } } +``` + +Dépend: `dirs` (chemin config), `serde`/`serde_json`. +Utilisé par: `rss-gui` (thème et sliders), construction de `PollConfig`. + +Extraits supplémentaires: +```rust +// rss-core/src/config.rs +pub fn config_file_path() -> Result> { + let config_dir = dirs::config_dir().ok_or("Impossible de trouver le dossier de configuration")?; + let app_config_dir = config_dir.join("readrss"); + std::fs::create_dir_all(&app_config_dir)?; + Ok(app_config_dir.join("config.json")) +} + +pub fn save(&self) -> Result<(), Box> { + let config_path = Self::config_file_path()?; + let config_json = serde_json::to_string_pretty(self)?; + std::fs::write(config_path, config_json)?; Ok(()) +} +``` +Décryptage simple: +- `dirs::config_dir()` retourne le dossier config de l’OS (ex: `~/.config` sous Linux). +- On crée le sous‑dossier `readrss` au besoin et on y place `config.json` lisible par l’utilisateur. +- `serde_json::to_string_pretty` produit un JSON facile à éditer à la main. + +--- + +## 06 — Données: FeedDescriptor et FeedEntry (schémas) + +Chemin: `rss-core/src/feed.rs` + +Schémas: +- `FeedDescriptor { id, title, url }` décrit un flux suivi. +- `FeedEntry` représente un article normalisé (titre, url, auteur, date, guid…). + +Points clés: +- `identity()` fabrique une clé stable (GUID > URL > titre@timestamp) — sert à la déduplication. +- Conversions depuis RSS et Atom remplissent au mieux les champs. + +Extrait: +```rust +impl FeedEntry { + pub fn identity(&self) -> String { /* GUID > URL > titre@ts */ } +} +``` + +Conversions concrètes: +```rust +// rss-core/src/feed.rs +pub fn from_rss_item(feed_id: &str, item: &rss::Item) -> Self { + let published_at = item.pub_date() + .and_then(|v| DateTime::parse_from_rfc2822(v).ok()).map(|dt| dt.with_timezone(&Utc)); + let author = item.dublin_core_ext().and_then(|dc| dc.creators().first().cloned()) + .or_else(|| item.author().map(|s| s.to_string())); + let category = item.categories().first().map(|c| c.name().to_string()) + .or_else(|| item.dublin_core_ext().and_then(|dc| dc.subjects().first().cloned())); + let content_html = item.extensions().get("content").and_then(|m| m.get("encoded")) + .and_then(|v| v.first()).and_then(|ext| ext.value.clone()); + let image_url = item.enclosure().map(|e| e.url().to_string()); + Self { /* … champs remplis … */ feed_id: feed_id.to_owned(), title: item.title().unwrap_or_default().to_owned(), + summary: item.description().map(ToOwned::to_owned), url: item.link().unwrap_or_default().to_owned(), + published_at, guid: item.guid().map(|g| g.value().to_owned()), author, category, content_html, image_url } +} +``` +Décryptage simple: +- On tente d’abord `pub_date` (format RFC 2822) et on convertit la timezone vers UTC. +- Métadonnées supplémentaires via extensions (Dublin Core, content:encoded, enclosure image). +- Les `Option` évitent les `null`/paniques: si une info manque, on ne casse rien. + +--- + +## 07 — Persistance des données: DataApi (contrats) + +Chemin: `rss-core/src/data.rs` + +Responsabilités: +- Feeds: ajouter/supprimer/lister avec persistance (`feeds.json`). +- Read‑state: marquer “lu” (`read_store.json`). +Décryptage simple: +- Écriture atomique: on écrit d’abord un fichier temporaire `.tmp`, puis on le renomme. En cas de coupure, on évite un fichier final corrompu. +- Articles: cache par feed (`articles_store.json`), déduplication + tri + truncate. + +Contrats fonctionnels: +- `add_feed(feed)` — Entrée: `FeedDescriptor`; Effet: persiste et met à jour la liste. +- `mark_read(entry)` — Entrée: `FeedEntry`; Effet: persiste la marque “lu”. +- `upsert_articles(feed_id, entries)` — Entrée: liste d’articles; Effet: fusion, tri, limite, persistance atomique. + +Note: écriture atomique via fichier `.tmp` puis `rename()`. + +Extrait (écriture atomique): +```rust +// rss-core/src/data.rs +async fn persist_feeds(&self) { +Décryptage simple: +- `existing` garde les clés déjà présentes pour éviter les doublons. +- Tri décroissant par date; on tronque pour maîtriser la taille disque/mémoire. + let feeds = list_feeds(&self.feeds).await; + if let Ok(bytes) = serde_json::to_vec_pretty(&feeds) { + if let Some(parent) = self.feeds_path.parent() { let _ = tokio::fs::create_dir_all(parent).await; } + let tmp = self.feeds_path.with_extension("json.tmp"); + let _ = tokio::fs::write(&tmp, &bytes).await; let _ = tokio::fs::rename(&tmp, &self.feeds_path).await; } +} +``` + +Extrait (fusion d’articles): +```rust +pub async fn upsert_articles(&self, feed_id: &str, entries: Vec) { + const MAX_PER_FEED: usize = 300; + let mut inner = self.articles_inner.write().await; + let slot = inner.entry(feed_id.to_string()).or_default(); + let mut existing: HashSet = slot.iter().map(|e| e.identity()).collect(); + for e in entries { if existing.insert(e.identity()) { slot.push(e); } } + slot.sort_by(|a,b| b.published_at.cmp(&a.published_at)); + if slot.len() > MAX_PER_FEED { slot.truncate(MAX_PER_FEED); } + drop(inner); self.persist_articles().await; +} +``` + +--- + +## 08 — Déduplication persistée: SeenStore + +Chemin: `rss-core/src/storage.rs` + +Rôle: empêcher l’UI de recevoir à nouveau un article déjà diffusé. Différent de “lu” (qui relève de l’utilisateur). + +Contrat: +- `is_new_and_mark(entry) -> bool`: retourne true s’il n’a jamais été vu (et le marque immédiatemment), sinon false. + +Structure de données: `HashMap>` sérialisé en JSON. + +Extrait: +```rust +// rss-core/src/storage.rs +pub async fn is_new_and_mark(&self, entry: &FeedEntry) -> bool { + let key = entry.identity(); + let feed_id = entry.feed_id.clone(); + let mut inner = self.inner.write().await; + let set = inner.seen.entry(feed_id).or_default(); + if set.contains(&key) { false } else { + set.insert(key); drop(inner); let _ = self.persist().await; true + } +} +``` +Décryptage simple: +- “Vu ?” Si non: on ajoute la clé et on persiste. Si oui: on ne republie pas l’article à l’UI. +- La persistance est asynchrone; en cas d’échec, on logge mais on ne bloque pas l’UI. + +--- + +## 09 — Réseau et sécurité: fetch_feed (politique) + +Chemin: `rss-core/src/poller.rs` + +Garanties: +- HTTPS requis hors tests/dev (exception loopback). +- Taille max 10 MiB; streaming du body pour limiter la mémoire. +- Timeout configurable par `PollConfig`. + +Extrait de contrôle de schéma: +```rust +#[cfg(not(test))] +if url.scheme() != "https" { /* autorise localhost/127.0.0.1/::1 sinon UnsupportedScheme */ } +``` + +Extrait (streaming et limite 10 MiB): +```rust +// rss-core/src/poller.rs +const MAX_FEED_BYTES: usize = 10 * 1024 * 1024; +let response = client.get(url).timeout(timeout).send().await?; +if let Some(len) = response.content_length() { if len > MAX_FEED_BYTES as u64 { return Err(PollError::TooLarge(len)); } } +let mut bytes_buf = bytes::BytesMut::new(); let mut stream = response.bytes_stream(); +while let Some(chunk) = stream.next().await { + let chunk = chunk?; if bytes_buf.len() + chunk.len() > MAX_FEED_BYTES { return Err(PollError::TooLarge((bytes_buf.len()+chunk.len()) as u64)); } + bytes_buf.extend_from_slice(&chunk); +} +``` +Décryptage simple: +- On lit par morceaux (streaming) pour ne pas exploser la mémoire. +- Double barrière: longueur annoncée ET vérification cumulée en cours de lecture. + +--- + +## 10 — Parsing: d’abord RSS, puis fallback Atom + +Stratégie: essayer RSS; si parsing échoue, tenter Atom; si les deux échouent, retourner l’erreur RSS (plus informative). + +Extrait simplifié: +```rust +match rss::Channel::read_from(&mut cursor_rss) { + Ok(channel) => map_items(channel.items()), + Err(rss_err) => match atom_syndication::Feed::read_from(&mut cursor_atom) { + Ok(feed) => map_entries(feed.entries()), + Err(_) => Err(PollError::from(rss_err)) + } +} +``` + +Extrait (normalisation des dates): +```rust +// rss-core/src/poller.rs +let entries = channel.items().iter().map(|item| { + let mut entry = FeedEntry::from_rss_item(&feed.id, item); + if entry.published_at.is_none() { entry.published_at = Some(Utc::now()); } + entry +}).collect(); +``` +Décryptage simple: +- Certains flux n’ont pas de date fiable; on complète raisonnablement par “maintenant” pour garantir un tri cohérent. + +--- + +## 11 — PollConfig et backoff (retry exponentiel) + +Paramètres: +- `interval`: cadence du polling. +- `request_timeout`: timeout HTTP par requête. +- `max_retries`: nb max de tentatives. +- `retry_backoff_ms`: base du backoff exponentiel. + +Extrait: +```rust +let backoff = cfg.retry_backoff_ms * (1u64 << (attempt - 1)); +tokio::time::sleep(Duration::from_millis(backoff)).await; +``` + +Définition et valeurs par défaut: +```rust +// rss-core/src/poller.rs +#[derive(Debug, Clone)] +pub struct PollConfig { pub interval: Duration, pub request_timeout: Duration, pub max_retries: usize, pub retry_backoff_ms: u64 } +impl Default for PollConfig { fn default() -> Self { Self { interval: Duration::from_secs(300), request_timeout: Duration::from_secs(15), max_retries: 3, retry_backoff_ms: 500 } } } +``` +Décryptage simple: +- Des valeurs prudentes par défaut: 5 minutes d’intervalle, 15 s de timeout, 3 tentatives, backoff base 500 ms. + +--- + +## 12 — Tâche de polling: spawn_poller (concurrence) + +Mécanique: +- `tokio::spawn` crée une tâche qui réveille un `interval`. +- À chaque tick: snapshot des feeds, fetch en séquence (simple et sûr), émission d’évènements. +- Arrêt: canal `broadcast` (envoi `()`), `join.await` dans `stop()`. + +Contrats d’erreur: toute erreur de réseau/parsing est loggée, pas fatale. + +Extrait: +```rust +pub fn spawn_poller(/*…*/) -> PollerHandle { + let (cancel_tx, mut cancel_rx) = broadcast::channel(1); + let join = tokio::spawn(async move { + let mut ticker = tokio::time::interval(config.interval); + ticker.set_missed_tick_behavior(tokio::time::MissedTickBehavior::Skip); + loop { tokio::select! { _ = cancel_rx.recv() => break, _ = ticker.tick() => { /* boucle feeds + fetch */ } } } + }); + PollerHandle { cancel_tx, join } +} +``` +Décryptage simple: +- `broadcast` sert à signaler l’arrêt à la tâche. `join` attend la fin propre de la tâche (utile à la fermeture). +- `select!` écoute soit l’arrêt, soit l’horloge périodique. + +--- + +## 13 — Évènements: Event::NewArticles + +Chemin: `rss-core/src/poller.rs` + +Rôle: isoler l’UI des détails réseau. L’UI ne “scrape” jamais directement: elle consomme des évènements. + +Format: `NewArticles(feed_id, Vec)` + +Dépendances: `mpsc::Sender` passé à `spawn_poller`. + +--- + +## 14 — Point d’entrée GUI (main.rs) + +Chemin: `rss-gui/src/main.rs` + +Étapes: +1. Initialiser tracing (logs filtrables via `RUST_LOG`). +2. Créer un runtime Tokio et les services (DataApi, SeenStore, client HTTP). +3. Dériver `PollConfig` à partir d’`AppConfig` (cohérence UI/runtime). +4. Lancer le poller et démarrer la fenêtre eframe/egui. + +Extrait: +```rust +let poller = spawn_poller(feeds.clone(), poll_config.clone(), client, update_tx, seen_store); +eframe::run_native("ReadRSS", NativeOptions { /* … */ }, Box::new(move |_| Box::new(RssApp::new(init)))) +``` + +Autres extraits utiles: +```rust +// rss-gui/src/main.rs +let client = ClientBuilder::new().redirect(redirect::Policy::limited(5)) + .user_agent("ReadRSS/0.1 (+https://github.com/xAMA0x/ReadRSS)").build()?; + +fn load_poll_config() -> PollConfig { + let app_cfg = AppConfig::load(); + PollConfig { interval: Duration::from_secs(app_cfg.feeds.update_interval_minutes.max(1) * 60), + request_timeout: Duration::from_secs(app_cfg.feeds.request_timeout_seconds.max(1)), + max_retries: app_cfg.feeds.retry_attempts.max(1) as usize, ..PollConfig::default() } +} +``` +Décryptage simple: +- On limite les redirections HTTP (sécurité/perf) et on définit un user‑agent clair. +- `PollConfig` dérive des préférences utilisateur (UI ↔ runtime alignés). + +--- + +## 15 — Architecture UI: vues et navigation + +Chemin: `rss-gui/src/app.rs` + +Vues principales: +- Liste d’articles, Détail d’article, Discover (catégories), Paramètres. + +Navigation: +- Panneau gauche: ajout/recherche, accès Discover/Paramètres, sélection de flux. +- Panneau central: route selon `current_view`. + +--- + +## 16 — Thème et styles (egui) + +Règles: +- Couleurs et arrondis issus d’`AppConfig`. +- Objectif lisibilité (contraste, hover, active). + +Extrait (simplifié): +```rust +style.visuals.dark_mode = true; +style.visuals.widgets.active.bg_fill = accent_color; +ctx.set_style(style); +``` + +Extrait complet (sélection): +```rust +// rss-gui/src/app.rs +style.visuals.widgets.hovered.bg_stroke = Stroke::new(1.0, accent_color); +style.visuals.selection.bg_fill = Color32::from_rgba_unmultiplied(0,122,204,60); +style.spacing.item_spacing = egui::vec2(10.0, 8.0); +style.visuals.widgets.noninteractive.rounding = Rounding::same(3.0); +``` +Décryptage simple: +- On améliore la lisibilité: états visuels cohérents (hover/active/sélection), espacements confortables, arrondis discrets. + +--- + +## 17 — Ajout d’un flux: validation et feedback + +UX: titre optionnel, URL obligatoire et en HTTPS (sinon message d’erreur). Après ajout: déclenchement d’un `poll_once` immédiat pour “voir un résultat tout de suite”. + +Extrait: +```rust +if parsed.scheme() != "https" { self.add_feedback = Some((false, "Seules les URLs HTTPS…".into())); } +``` + +Extrait (ajout + rafraîchissement): +```rust +// rss-gui/src/app.rs +let descriptor = FeedDescriptor { id, title: title_owned_or_url, url: url_owned.clone() }; +self.runtime.block_on(self.data_api.add_feed(descriptor.clone())); +let events = self.runtime.block_on(async { poll_once(&[descriptor], &self.poll_config, &self.client, &self.seen_store).await }); +for evt in events { if let Event::NewArticles(feed_id, mut entries) = evt { self.runtime.block_on(self.data_api.upsert_articles(&feed_id, entries.clone())); self.articles.append(&mut entries); } } +``` +Décryptage simple: +- Après ajout, on force un “mini polling” du seul nouveau flux (`poll_once`). +- On persiste immédiatement les articles et on les affiche triés dans l’UI. + +--- + +## 18 — Discover: recommandations prêtes à suivre + +Principe: listes statiques de flux classées par catégorie (Tech, Dev, Science, Actu FR). Bouton “Suivre” → ajout + rafraîchissement instantané. + +But: onboarding immédiat sans chercher des URLs. + +--- + +## 19 — Liste d’articles: agrégation et filtrage + +Fonctions: +- Vue “Tous” (agrégée) ou par flux. +- Tri par date décroissante, pagination via `articles_per_page`. +- “Non lus” uniquement (en s’appuyant sur `DataApi.is_read`). + +Extraits: +```rust +// Filtre Non lus +if self.show_unread_only && self.runtime.block_on(self.data_api.is_read(&article)) { continue; } + +// Aperçu texte depuis HTML/summary +let preview_text = if let Some(html) = &article.content_html { html2text::from_read(html.as_bytes(), 100) } + else if let Some(summary) = &article.summary { html2text::from_read(summary.as_bytes(), 100) } else { String::new() }; +``` +Décryptage simple: +- Le filtre “Non lus” interroge l’état persistant (`DataApi.is_read`). +- `html2text` rend les aperçus lisibles et sûrs (texte brut, pas de scripts). + +--- + +## 20 — Détail d’un article et actions + +Rendu: `html2text` transforme le HTML en texte brut (lisible, sûr). +Actions: Ouvrir dans le navigateur (mise en page native), Copier le lien. + +Sécurité: l’UI ne rend pas du HTML riche (pas de WebView), donc pas d’exécution de scripts. + +Extrait: +```rust +if ui.button("Ouvrir dans le navigateur").clicked() { let _ = webbrowser::open(&article.url); } +if ui.button("Copier le lien").clicked() { ui.output_mut(|o| o.copied_text = article.url.clone()); } +``` +Décryptage simple: +- On délègue l’affichage enrichi au navigateur par défaut; l’UI reste simple et sûre. +- Copier le lien utilise le presse‑papiers géré par egui. + +--- + +## 21 — Paramètres: thème, interface et flux + +Sauvegarde immédiate: chaque slider/checkbox écrit le JSON. +Impact: thème appliqué à chaud; paramètres des feeds pris en compte à la relance (ou conversion vers `PollConfig` dès l’entrée). + +Extrait (sauvegarde immédiate): +```rust +if ui.color_edit_button_rgb(&mut bg).changed() { + self.config.theme.background_color = [(bg[0]*255.0) as u8, (bg[1]*255.0) as u8, (bg[2]*255.0) as u8]; + let _ = self.config.save(); +} +``` +Décryptage simple: +- Tout changement UI est immédiatement sauvegardé en JSON — pas de bouton “Enregistrer”. + +--- + +## 22 — Concurrence et canaux (modèle mental) + +- Poller: tâche async autonome qui pousse des évènements. +- UI: boucle egui qui consomme les évènements et persiste via `DataApi`. +- Partage: `Arc>>` pour la liste des flux. + +Avantage: découplage réseau/UI, robustesse, simplicité de debug. + +Extrait (consommation des évènements): +```rust +while let Ok(evt) = self.updates.try_recv() { + if let Event::NewArticles(feed_id, mut entries) = evt { + self.runtime.block_on(self.data_api.upsert_articles(&feed_id, entries.clone())); + self.articles.append(&mut entries); + self.articles.sort_by(|a,b| b.published_at.cmp(&a.published_at)); + } +} +``` +Décryptage simple: +- `try_recv` lit sans bloquer la boucle de rendu; l’UI reste fluide même si le poller publie beaucoup. + +--- + +## 23 — Robustesse: limites et timeouts + +Pourquoi 10 MiB? Éviter les flux anormalement gros (DoS mémoire/temps). +Pourquoi des retries? L’Internet est faillible; on retente avec backoff exponentiel. +Pourquoi `MissedTickBehavior::Skip`? On ne rattrape pas un retard si l’app a été gelée (préserve la réactivité). + +Extrait: +```rust +let mut ticker = tokio::time::interval(config.interval); +ticker.set_missed_tick_behavior(tokio::time::MissedTickBehavior::Skip); +``` +Décryptage simple: +- Si l’app a “raté” un tick (PC occupé/suspendu), on n’enchaîne pas des dizaines de rattrapages. + +--- + +## 24 — Gestion des erreurs et logs + +`PollError` catégorise les échecs (réseau, parsing, scheme, taille, task join). +`tracing` permet `RUST_LOG=info`/`debug` pour diagnostiquer. + +Extrait: +```rust +#[derive(Debug, Error)] +pub enum PollError { Network(#[from] reqwest::Error), Parse(#[from] rss::Error), /* … */ } +``` + +Extrait (logging côté poller): +```rust +warn!(feed = %feed.url, error = %err, "failed to fetch feed"); +``` +Décryptage simple: +- `tracing::warn!` enregistre un message structuré: on voit l’URL du flux et l’erreur précise. + +--- + +## 25 — Formats et chemins de persistance + +Fichiers côté utilisateur: +- `config.json`, `feeds.json`, `read_store.json`, `articles_store.json`, `seen_store.json`. +- Dossiers: Linux `~/.config/readrss/`, macOS `~/Library/Application Support/readrss/`, Windows `%APPDATA%/readrss/`. + +Lecture/écriture JSON via `serde_json` (lisible et diffable). + +Extrait (chemin config, côté GUI): +```rust +fn config_dir() -> PathBuf { let mut dir = dirs::config_dir().unwrap_or_else(|| std::env::current_dir().unwrap()); dir.push("readrss"); dir } +``` +Décryptage simple: +- Si `dirs::config_dir()` n’est pas dispo (cas rare), on utilise le répertoire courant — l’app reste utilisable. + +--- + +## 26 — Tests et “poll_once” + +`poll_once` exécute un tour synchrone (utile pour tests ou action “rafraîchir maintenant”). + +Mocks: `wiremock` côté requêtes HTTP (injectable car on utilise `reqwest`). + +Extrait (API de test synchronisable): +```rust +// rss-core/src/poller.rs +pub async fn poll_once(feeds: &[FeedDescriptor], cfg: &PollConfig, client: &Client, seen: &SeenStore) -> Vec { + let mut out = Vec::new(); + for feed in feeds { if let Ok(mut entries) = fetch_feed_with_retries(client, feed, cfg).await { + let mut new_entries = Vec::new(); for e in entries { if seen.is_new_and_mark(&e).await { new_entries.push(e); } } + if !new_entries.is_empty() { out.push(Event::NewArticles(feed.id.clone(), new_entries)); } + }} out +} +``` +Décryptage simple: +- Utile pour un “rafraîchir maintenant” sans lancer le poller complet: un tour, des évènements, terminé. + +--- + +## 27 — Packaging et Release CI + +Local: `scripts/build_deb.sh` (utilise `cargo-deb`). +CI Release: artefacts Linux (.tar.gz + .deb) et Windows (.zip). +Précaution Linux: `cargo deb --no-build` après la compilation pour éviter le double `--release`. + +Extrait (workflow): +```yaml +# .github/workflows/release.yml (extrait) +- name: Build .deb + run: cargo deb -p rss-gui --no-build +``` +Décryptage simple: +- `--no-build` réutilise le binaire déjà compilé (évite l’erreur de double `--release`). + +--- + +## 28 — Sécurité élargie (menaces ↔ contre‑mesures) + +- HTTP refusé: évite downgrade/MITM. +- Taille max: réduit le risque DoS. +- Pas d’HTML riche: surface XSS nulle dans l’UI. +- Déduplication: évite re‑push infini d’articles répétés. + +Limites connues: pas de sandbox réseau avancée; confiance dans `reqwest/rustls`. + +Extrait (refus HTTP côté UI aussi): +```rust +if parsed.scheme() != "https" { self.add_feedback = Some((false, "Seules les URLs HTTPS sont autorisées".to_string())); return; } +``` +Décryptage simple: +- Filtre côté UI également: cohérence d’expérience (on bloque tôt, avec message clair). + +--- + +## 29 — Performance et mémoire + +- Download en streaming; pas de copie inutile (Buffers BytesMut → freeze). +- Structures compactes; tri et truncate pour borner la taille des caches. +- UI: wgpu/egui rapide, pas de DOM. + +Mesure recommandée: profiler `tracing` + `cargo flamegraph` si besoin. + +Extrait (BytesMut → freeze): +```rust +let mut bytes_buf = bytes::BytesMut::new(); +// … remplissage … +let bytes = bytes_buf.freeze(); +``` +Décryptage simple: +- `freeze()` convertit le buffer mutable en `Bytes` immuable sans recopier les données — performant et sûr. + +--- + +## Scénarios fil rouge (du clic utilisateur au rendu) + +### Scénario 1 — J’ajoute un flux et je vois des articles + +1) UI: l’utilisateur saisit l’URL et clique “Ajouter”. + - Validation: refus des URLs non‑HTTPS; feedback immédiat. +2) Données: `DataApi.add_feed` persiste le flux dans `feeds.json`. +3) Rafraîchissement: l’app appelle `poll_once` pour ce flux uniquement. + - Réseau: `reqwest` télécharge le flux en streaming (≤ 10 MiB, timeout). + - Parsing: RSS, sinon fallback Atom. + - Déduplication: `SeenStore.is_new_and_mark` ne garde que les nouveaux. +4) Persistance: `DataApi.upsert_articles` fusionne, trie et sauve `articles_store.json`. +5) UI: réception `Event::NewArticles` → ajout dans la liste, tri par date, affichage. + +Résultat tangible: l’utilisateur voit des articles quelques secondes après l’ajout. + +### Scénario 2 — Je lance l’application le matin + +1) Démarrage: `AppConfig::load` charge les préférences; `DataApi::load_from_dir` restaure feeds, “lus” et cache d’articles. +2) Services: on crée le client HTTP; `load_poll_config` dérive `PollConfig` depuis `AppConfig`. +3) Poller: `spawn_poller` démarre la tâche périodique (intervalle choisi par l’utilisateur). +4) Première passe: à chaque tick, fetch des flux, déduplication, persistance; l’UI consomme les évènements sans bloquer. +5) Utilisation: l’utilisateur ouvre un article (navigateur), marque “tout lu”, ajuste le thème; tout est sauvegardé immédiatement. + +--- + +## Glossaire (rapide et utile) + +- Arc: compteur de références thread‑safe; partage d’un même objet entre threads. +- RwLock: verrou lecture/écriture (plusieurs lecteurs OU un écrivain). +- async/await: style d’écriture pour code non bloquant; garde l’UI fluide. +- mpsc/broadcast: canaux asynchrones (point‑à‑point / diffusion à plusieurs). +- serde/serde_json: sérialisation/désérialisation Rust ↔ JSON. +- reqwest: client HTTP asynchrone, ici avec rustls. +- egui/eframe: toolkit UI immédiat sur wgpu (rendu GPU), portable. +- backoff exponentiel: on attend de plus en plus longtemps entre les tentatives après un échec. +- écriture atomique: écriture dans un fichier temporaire puis renommage, pour éviter les données corrompues. + +--- + +## 30 — Dépannage & Roadmap + +Checklist panne: +- Aucun article: vérifier URL HTTPS, connectivité, logs (`RUST_LOG=info`). +- Emojis cassés sous Linux: installer `fonts-noto-color-emoji`. +- JSON corrompu: les `.tmp` servent de secours (recréer si besoin). + +Roadmap: macOS artefacts, .desktop+icône, recherche plein‑texte, OPML, i18n, thèmes. + +--- + +## Annexes — extraits de code clés (références rapides) + +### A1. spawn_poller (boucle) +```rust +pub fn spawn_poller(/* … */) -> PollerHandle { + let (cancel_tx, mut cancel_rx) = broadcast::channel(1); + let join = tokio::spawn(async move { + let mut ticker = tokio::time::interval(config.interval); + ticker.set_missed_tick_behavior(tokio::time::MissedTickBehavior::Skip); + loop { tokio::select! { _ = cancel_rx.recv() => break, _ = ticker.tick() => { /* fetch */ } } } + }); + PollerHandle { cancel_tx, join } +} +``` + +### A2. DataApi.upsert_articles (tri et borne) +```rust +slot.sort_by(|a, b| b.published_at.cmp(&a.published_at)); +if slot.len() > MAX_PER_FEED { slot.truncate(MAX_PER_FEED); } +``` + +### A3. Validation URL en UI +```rust +if parsed.scheme() != "https" { /* feedback et refus */ } +``` + +### A4. Entrée main.rs +```rust +eframe::run_native("ReadRSS", NativeOptions { /* viewport */ }, + Box::new(move |cc| { install_emoji_friendly_fonts(&cc.egui_ctx); Box::new(RssApp::new(init)) })) +``` + +Fin du guide. \ No newline at end of file diff --git a/docs/RECAP_AMELIORATIONS.md b/docs/RECAP_AMELIORATIONS.md deleted file mode 100644 index 993d7dc..0000000 --- a/docs/RECAP_AMELIORATIONS.md +++ /dev/null @@ -1,183 +0,0 @@ -# Récapitulatif des améliorations apportées au projet ReadRSS - -## 🎯 Objectif principal -Transformer le lecteur RSS basique existant en une application moderne avec une interface graphique sombre inspirée de VS Code, incluant : -- Un panel latéral pour la gestion des flux -- Une vue principale pour les articles avec métadonnées étendues -- Un système de navigation et de lecture détaillée des articles - -## 📋 Actions réalisées - -### 1. ✅ Analyse du projet existant -**Fichiers examinés :** -- `Cargo.toml` : Structure du workspace et dépendances -- `rss-core/src/lib.rs`, `feed.rs`, `poller.rs` : Core RSS existant -- `rss-gui/src/app.rs`, `main.rs` : Interface basique existante - -**Constats :** -- Base fonctionnelle solide avec polling en arrière-plan -- Interface GUI minimaliste à enrichir -- Structure de données de base à étendre - -### 2. ✅ Amélioration du modèle de données - -**Fichier modifié :** `rss-core/src/feed.rs` - -**Changements apportés :** -```rust -// Ajout de nouveaux champs dans FeedEntry -pub struct FeedEntry { - // ... champs existants ... - pub author: Option, // Nouveau - pub category: Option, // Nouveau -} - -// Amélioration de la méthode from_rss_item -impl FeedEntry { - pub fn from_rss_item(feed_id: &str, item: &rss::Item) -> Self { - // Extraction de l'auteur depuis Dublin Core ou champ author - let author = item.dublin_core_ext() - .and_then(|dc| dc.creators().first().map(|s| s.to_string())) - .or_else(|| item.author().map(|s| s.to_string())); - - // Extraction de la catégorie depuis categories ou Dublin Core subject - let category = item.categories().first() - .map(|cat| cat.name().to_string()) - .or_else(|| { - item.dublin_core_ext() - .and_then(|dc| dc.subjects().first().map(|s| s.to_string())) - }); - // ... - } -} -``` - -### 3. ✅ Refonte complète de l'interface graphique - -**Fichier transformé :** `rss-gui/src/app.rs` - -#### 3.1 Nouveau système de thème sombre -```rust -fn setup_dark_theme(&self, ctx: &egui::Context) { - // Couleurs VS Code Dark Theme - let bg_color = Color32::from_rgb(30, 30, 30); // Arrière-plan principal - let panel_color = Color32::from_rgb(37, 37, 38); // Panneaux latéraux - let border_color = Color32::from_rgb(62, 62, 66); // Bordures - let text_color = Color32::from_rgb(204, 204, 204); // Texte principal - let accent_color = Color32::from_rgb(0, 122, 204); // Bleu accent VS Code - // Configuration complète du style... -} -``` - -#### 3.2 Nouveau système de navigation -```rust -#[derive(Debug, Clone)] -enum AppView { - ArticleList, // Vue liste des articles - ArticleDetail(FeedEntry), // Vue détaillée d'un article -} -``` - -#### 3.3 Fonctionnalités du panel latéral -- **Section d'ajout de flux** avec titre et URL -- **Barre de recherche** pour filtrer les flux -- **Liste des flux** avec sélection et suppression -- **Navigation** : bouton "Tous" pour voir tous les articles - -#### 3.4 Zone principale des articles -- **Mode liste** : Aperçu avec titre, auteur, catégorie, date, résumé tronqué -- **Mode détail** : Vue complète de l'article avec toutes les métadonnées -- **Actions** : Lecture, ouverture dans navigateur, copie de lien - -### 4. ✅ Ajout de nouvelles dépendances - -**Fichiers modifiés :** -- `Cargo.toml` (workspace) : Ajout de `webbrowser = "0.8"` -- `rss-gui/Cargo.toml` : Ajout des dépendances `webbrowser` et `chrono` - -### 5. ✅ Fonctionnalités implémentées - -#### Interface utilisateur -- ✅ Thème sombre VS Code -- ✅ Panel latéral redimensionnable (280-350px) -- ✅ Zone principale responsive -- ✅ Icônes émojis pour améliorer l'UX -- ✅ Groupes visuels et séparateurs - -#### Gestion des flux -- ✅ Ajout de flux avec titre et URL -- ✅ Recherche/filtrage des flux en temps réel -- ✅ Suppression de flux avec confirmation -- ✅ Sélection de flux pour filtrer les articles -- ✅ Bouton "Tous" pour voir tous les articles - -#### Affichage des articles -- ✅ Liste avec titre cliquable, auteur, catégorie, date -- ✅ Résumé tronqué (200 caractères max) -- ✅ Vue détaillée avec contenu complet -- ✅ Navigation retour depuis la vue détaillée -- ✅ Formatage des dates (DD/MM/YYYY HH:MM) - -#### Actions sur les articles -- ✅ Ouverture dans le navigateur système -- ✅ Copie du lien dans le presse-papiers -- ✅ Basculement entre vue liste et vue détaillée - -### 6. ✅ Gestion des erreurs et compilation - -**Problèmes résolus :** -- ❌ Erreur de délimiteur non fermé → ✅ Structure `impl` corrigée -- ❌ Import `chrono` manquant → ✅ Dépendance ajoutée -- ❌ Champs de style egui obsolètes → ✅ Utilisation de `override_text_color` -- ❌ Erreurs d'emprunt (borrow checker) → ✅ Clonage des données avant utilisation -- ❌ Variables non utilisées → ✅ Suppression des avertissements - -### 7. ✅ Documentation et README - -**Nouveau README.md complet :** -- 🚀 Section fonctionnalités avec émojis -- 🛠️ Architecture technique détaillée -- 📦 Instructions d'installation et compilation -- 🎯 Guide d'utilisation complet -- 🔧 Options de configuration -- 🎨 Guide de personnalisation du thème -- 📁 Documentation des structures de données -- 🚧 Roadmap des améliorations futures - -## 🎊 Résultat final - -### Interface transformée -**Avant :** Interface basique avec liste simple des flux et articles -**Après :** Interface moderne VS Code Dark avec : -- Panel latéral organisé avec recherche et gestion des flux -- Zone principale avec vue liste et vue détaillée -- Thème sombre professionnel -- Navigation fluide et intuitive - -### Fonctionnalités ajoutées -1. **Métadonnées enrichies** : auteur et catégorie des articles -2. **Recherche et filtrage** : des flux en temps réel -3. **Navigation avancée** : vue liste ↔ vue détaillée -4. **Actions utilisateur** : ouverture navigateur, copie lien -5. **Thème professionnel** : couleurs et styles VS Code Dark -6. **UX améliorée** : icônes, groupes visuels, feedback utilisateur - -### Stabilité et performance -- ✅ Compilation sans erreurs ni avertissements -- ✅ Gestion mémoire optimisée (250 articles max) -- ✅ Interface responsive et réactive -- ✅ Polling en arrière-plan maintenu -- ✅ Gestion d'erreurs robuste - -## 🔄 Processus de développement - -1. **Analyse** → Compréhension du code existant -2. **Planification** → Définition des objectifs et structure -3. **Extension données** → Ajout champs auteur/catégorie -4. **Refonte UI** → Implémentation thème et navigation -5. **Intégration** → Assemblage des composants -6. **Débogage** → Résolution erreurs compilation -7. **Test** → Vérification fonctionnement -8. **Documentation** → Mise à jour README et guides - -Le projet ReadRSS est maintenant une application moderne et professionnelle avec une interface utilisateur riche et intuitive, tout en conservant la robustesse de l'architecture de base. diff --git a/docs/packaging.md b/docs/packaging.md deleted file mode 100644 index 8ee061a..0000000 --- a/docs/packaging.md +++ /dev/null @@ -1,18 +0,0 @@ -# Packaging - -Objectif: produire des artefacts installables pour Linux/Windows/macOS. - -## Linux -- Binaire `rss-gui` en `target/release/`. Fournir une archive `.tar.gz` contenant le binaire et le README. -- Piste AppImage (à planifier): utiliser `appimagetool` + recette. Vérifier dépendances à l'exécution. - -## Windows -- Archive `.zip` avec `rss-gui.exe`. -- Piste MSI: WiX Toolset ou `cargo wix` (à planifier). - -## macOS -- Piste app bundle via `cargo-bundle` (à planifier). - -## Notes -- Les binaires sont auto-suffisants; la configuration utilisateur est stockée dans `~/.config/readrss`. -- Vérifier les licences des dépendances avant distribution. diff --git a/docs/plan_travail.md b/docs/plan_travail.md deleted file mode 100644 index 9321d32..0000000 --- a/docs/plan_travail.md +++ /dev/null @@ -1,49 +0,0 @@ -# Plan de travail détaillé - -Ce document découpe le travail restant en quatre grands volets afin de répartir efficacement les tâches au sein de l'équipe. Chaque section précise les objectifs, les actions concrètes à mener, les livrables attendus et les critères de validation. - -## 1. Service de surveillance des flux RSS (Monitoring & Polling) -- **Objectif** : disposer d'un service robuste capable de surveiller automatiquement les flux RSS configurés et de détecter les nouveaux articles. -- **Actions à mener** : - - Finaliser la configuration du `PollConfig` (intervalle personnalisable, éventuellement via un fichier de configuration ou une interface CLI). - - Implémenter un mécanisme de persistance légère (fichier JSON ou base SQLite) pour mémoriser les articles déjà vus et éviter les doublons. - - Ajouter une gestion fine des erreurs réseau (timeouts, flux inaccessibles) avec une stratégie de retry et de journalisation claire. - - Exposer des événements structurés (ex. `NewArticles(feed_id, Vec)`) via un canal ou un bus interne pour que l'interface puisse se synchroniser. -- **Livrables** : module `rss-core` enrichi, tests unitaires sur le poller, rapport d'état (log) clair. -- **Résultats attendus** : le service tourne en tâche de fond, détecte les nouveautés et les met à disposition du front sans doublons ni plantages. - -## 2. Gestion des données et synchronisation (Stockage & API interne) -- **Objectif** : fournir un socle de données cohérent (flux, articles, préférences) accessible autant au poller qu'à l'interface. -- **Actions à mener** : - - Définir un modèle de données partagé (structs + sérialisation) pour les feeds, articles, catégories, tags éventuels. - - Mettre en place un stockage persistant (SQLite via `sqlx` ou `rusqlite`, ou fichiers JSON versionnés) avec migrations simples. - - Concevoir une petite API interne (fonctions ou façade) pour : ajouter/supprimer un flux, marquer un article comme lu, récupérer l’état courant (flux + derniers articles). - - Prévoir un mécanisme de synchronisation entre le runtime asynchrone et l’interface (verrous RwLock, cache en mémoire, notifications). -- **Livrables** : module de stockage documenté, scénario de test (ex. ajout d’un flux, récupération d’articles, suppression) et script de migration initiale. -- **Résultats attendus** : données persistées de manière fiable, accessibles par tous les composants, avec une API claire pour le front. - -## 3. Interface graphique (Responsable : Dan) -- **Objectif** : concevoir une application bureau moderne, épurée et ergonomique qui met en valeur les flux et articles suivis. -- **Actions à mener** : - - Définir un design system léger (palette de couleurs, typographies, variantes de cartes d’articles) en respectant l'esprit minimaliste demandé. - - Concevoir les écrans principaux : liste des flux, détail d’un flux avec ses articles, vue d’article détaillée (titre, résumé, contenu enrichi si possible), panneau de gestion (ajout/suppression de flux, paramétrage des intervalles, thèmes). - - Implémenter les composants `egui` correspondants : navigation, panneaux latéraux, listes scrollables, zones de recherche, boutons d’action. - - Ajouter des animations ou transitions légères (éventuellement via `egui` custom painting) pour donner un aspect « ultra moderne ». - - Prévoir la gestion des modes clair/sombre et l’adaptation responsive (fenêtre redimensionnée). -- **Livrables** : maquettes (sketch ou Figma), puis implémentation `egui` complète avec un thème personnalisable. -- **Résultats attendus** : l’utilisateur peut parcourir les flux et articles de manière fluide, avec une interface soignée et cohérente. - -## 4. Intégration, QA et packaging -- **Objectif** : garantir que l’ensemble fonctionne de bout en bout et préparer la distribution de l’application. -- **Actions à mener** : - - Configurer des tests d’intégration : lancement du poller, arrivée d’un nouvel article, affichage dans l’interface. - - Mettre en place un pipeline de CI simple (GitHub Actions ou autre) pour exécuter `cargo fmt`, `cargo clippy`, `cargo test`, et vérifier les builds. - - Documenter les procédures d’installation (README et wiki) : dépendances système (X11/Wayland), commandes de build, étapes de packaging. - - Préparer un packaging multiplateforme (AppImage, MSI, DMG ou zip) pour livrer l’application finale. - - Organiser une session de QA interne : checklist de fonctionnalités, tests manuels, collecte de feedback et corrections. -- **Livrables** : scripts CI, documentation utilisateur/développeur, artefacts de build. -- **Résultats attendus** : application prête à être testée/présentée, processus de build reproductible, documentation claire pour l’équipe. - ---- - -En répartissant les tâches ainsi, chaque membre peut se concentrer sur un bloc cohérent tout en respectant les interdépendances (le storage alimente le poller et l’UI, le packaging dépend de la stabilité des trois autres volets, etc.). Ce plan constitue la feuille de route jusqu’à une version aboutie du lecteur RSS. diff --git a/docs/qa_checklist.md b/docs/qa_checklist.md deleted file mode 100644 index b488b08..0000000 --- a/docs/qa_checklist.md +++ /dev/null @@ -1,12 +0,0 @@ -# Checklist QA interne - -- [ ] Lancement de l'application sans erreur (Linux/Wayland et X11) -- [ ] Ajout d'un flux valide (ex: https://blog.rust-lang.org/feed.xml) -- [ ] Réception d'articles et affichage dans la liste -- [ ] Pas de doublons après plusieurs cycles de poll -- [ ] Suppression d'un flux et arrêt des mises à jour associées -- [ ] Gestion des erreurs réseau: feed injoignable -> logs visibles, application stable -- [ ] Délais/intervalle: modification via `~/.config/readrss/config.json` prise en compte au prochain lancement -- [ ] Performance UI: scroll fluide avec 200+ articles -- [ ] Mode sombre/clair (si activé par l'OS) respecté par `egui` -- [ ] Tests automatisés passent (`cargo test`) diff --git a/docs/recap_2025-10-28.md b/docs/recap_2025-10-28.md deleted file mode 100644 index 639dcef..0000000 --- a/docs/recap_2025-10-28.md +++ /dev/null @@ -1,42 +0,0 @@ -# Récapitulatif des modifications — 2025-10-28 - -Ce document résume l’ensemble des corrections et améliorations réalisées ce soir. - -## Correctifs critiques - -- Correction d’un crash lors de l’affichage des aperçus d’articles (UTF-8): - - Problème: la troncature du texte utilisait des indices d’octets, provoquant un panic sur les caractères accentués/emoji (« byte index … is not a char boundary »). - - Solution: troncature Unicode-safe via `.chars()` avec ellipse, dans `rss-gui/src/app.rs`. - --- WebView (wry + tao): la tentative d’intégration a été abandonnée en raison de soucis de compatibilité Linux persistants (fenêtre blanche). Décision: suppression complète de la fonctionnalité et des dépendances; retour au comportement simple « ouvrir dans le navigateur ». - -## Améliorations UX / stabilité - -- Police/emoji: le chargement via fontconfig (Noto Color Emoji / Noto Sans Symbols2 / DejaVu Sans) est conservé; les logs confirment les polices trouvées. -- La fenêtre WebView n’est plus forcée « always-on-top » et affiche des bordures système pour limiter les soucis d’empilement. - -### Fonctionnalités UI ajoutées/précisées - -- Vue agrégée « Tous »: affiche les articles de l’ensemble des flux, avec labels de source colorés pour la lisibilité. -- Boutons de rafraîchissement: - - « Refresh » au niveau d’un flux (rafraîchit un seul flux) - - « Refresh all » global (rafraîchit tous les flux) -- Onglet « Discover »: catégories de flux recommandés et action « Suivre » rapide. -- Barre de recherche (latérale): recherche par titre d’article avec filtrage en direct. -- Cartes d’articles: correctif de hauteur du premier card et meilleure lisibilité (métadonnées auteur/catégorie/date, état lu/non-lu). - -## Dépendances / build - -- Suppression de `wry` et `tao`, et de la feature `webview`. Plus de dépendances WebKitGTK nécessaires. - - Le README a été mis à jour en conséquence. - -## Points à vérifier côté utilisateur - -- La lecture complète d’un article s’effectue via le bouton « Ouvrir dans le navigateur » (navigateur par défaut du système). - -## Suivis/Idées futures - -- Améliorer l’aperçu texte optionnel (extraction plus robuste, limites configurables). -- Ajouter des favoris et un marquage « à lire plus tard ». -- Raccourcis clavier (navigation, marquer lu/non‑lu, rafraîchir). -- Export/Import OPML pour les flux. diff --git a/docs/recapitulatif_actions.md b/docs/recapitulatif_actions.md deleted file mode 100644 index 71ccc6f..0000000 --- a/docs/recapitulatif_actions.md +++ /dev/null @@ -1,47 +0,0 @@ -# Récapitulatif des actions réalisées - -Ce document résume, pas à pas et avec un ton pédagogique, tout ce qui a été mis en place dans le projet jusqu'à présent. - -## 1. Mise en place de l'environnement de travail -- Création du fichier `.github/copilot-instructions.md` pour suivre les étapes de production et garder la même ligne directrice tout au long du projet. -- Validation des besoins : projet Rust en groupe de 4, lecteur RSS avec service d'actualisation en arrière-plan et interface graphique dédiée. - -## 2. Structure du workspace Cargo -- Création d'un workspace `Cargo.toml` à la racine contenant deux membres : - - `rss-core` : bibliothèque où vit toute la logique métier (gestion des flux, poller, modèles partagés). - - `rss-gui` : application graphique qui consomme la bibliothèque `rss-core`. -- Ajout d'une section `[workspace.dependencies]` pour déclarer les dépendances communes (Tokio, Reqwest, RSS, Chrono, Serde, egui/eframe, tracing…). -- Ajout d'un `.gitignore` adapté aux projets Rust (répertoire `target/`, sauvegardes temporaires, dossiers éditeur). - -## 3. Contenu de la bibliothèque `rss-core` -- `src/lib.rs` : expose les modules et ré-exporte les types/fonctions utiles pour le front-end. -- `src/error.rs` : définit une énumération `PollError` avec le derive `thiserror::Error` pour une gestion des erreurs claire. -- `src/feed.rs` : - - Modèles `FeedDescriptor` et `FeedEntry` sérialisables (Serde) pour décrire un flux et ses articles. - - Fonctions utilitaires `add_feed`, `remove_feed`, `list_feeds` opérant sur un `Arc>>` (alias `SharedFeedList`). - - Conversion d'un `rss::Item` vers notre `FeedEntry` avec normalisation de la date de publication. -- `src/poller.rs` : - - Structure `PollConfig` (intervalle) et `PollerHandle` (contrôle du poller asynchrone). - - Fonction `spawn_poller` qui boucle avec `tokio::time::interval`, récupère chaque flux via `reqwest`, parse le contenu RSS, enrichit les entrées et pousse les nouveautés sur un canal `mpsc`. - -## 4. Contenu de l'application `rss-gui` -- `src/main.rs` : - - Initialisation du tracing. - - Création d'un runtime Tokio partagé (`Arc`), du magasin de flux (`shared_feed_list`), du poller et du canal d'updates. - - Lancement de l'application `eframe` en passant une instance de `RssApp`. -- `src/app.rs` : - - Structure `AppInit` (données préparées avant l'UI) et `RssApp` (état de l'application). - - Gestion de l'ajout/suppression de flux, rafraîchissement des nouvelles entrées et tri des articles. - - Interface `egui` avec panneau latéral pour les flux suivis et panneau central listant les articles. - - Arrêt propre du poller dans `Drop` pour éviter les tâches orphelines. - -## 5. Documentation projet (`README.md`) -- Introduction au projet en français, description du rôle de chaque crate. -- Pré-requis techniques et commandes courantes (formatage, linting, compilation, exécution). -- Guide de collaboration Git : initialisation locale, création du dépôt distant, workflow de branches et Pull Requests. - -## 6. Préparation aux prochaines étapes -- Instructions prêtes pour lancer `cargo check`, `cargo fmt`, `cargo clippy`, `cargo run -p rss-gui` (commandes à lancer directement dans le terminal standard, sans `flatpak-spawn`). -- Liste de pistes d'évolution (persistance locale, notifications, préférences utilisateur). - -Ce récapitulatif servira de base de référence avant l'initialisation Git et le partage avec les autres membres du groupe.