From 1cc452829559f81ba64e1e5ff69d524b66204ccd Mon Sep 17 00:00:00 2001 From: CSiCoAlm Date: Sun, 28 Dec 2025 13:52:05 +0100 Subject: [PATCH 1/4] Testato esportazione in html Test degli spunti ottenuti da una chat con Gemini. - [ ] Test della classe cRac.cs - [ ] Test della funzione di salvataggio sul DB - [ ] Test della funzione di lettura dal DB - [ ] Test della funzione di ricerca nel DB - [ ] Test funzione di cancellazione nel DB - [ ] Test nella ricerca di duplicati - [X] Test della funzione di esportazione in html con stile Riepilogo della chat: - connesso a gemini con chrome e utente mirko - dato in pasto il file *ATDB Database Schema.sql* con la struttura del DB - richiesto la classe cRac per rappresentare un racconto. - ottenuto *cRac.cs* - richiesto come salvare un cRac sul DB con diagnostica errori ed evitando spurie sul DB - ottenuto *Salvataggio cRac su DB.cs* - richiesto come leggere un racconto - ottenuto *GetRaccontoById.cs* - richiesto: ragruppiamo le funzioni di helper su un file Helper.cs e quelle di lettura/scrittura sul DB sul file DB.cs - ottenuto *Helper.cs* e *DB.cs* - richiesto: facciamo le ricerche avanzate per titolo, tag, autore, data di creazione, sorgente - ottenuta la nuova versione di *Aggiornamento 1 di DB.cs* - richiesto come eliminare un racconto dal DB - ottenuta la nuova versione di *Aggiornamento 2 di DB.cs* - richiesto: come ricercare racconti duplicati - ottenuto spunti di ricerca salvati in *Ricerca duplicati.cs* - richiesto come fare il merge di racconti duplicati - ottenuto *merge.cs* - richiesto delle idee per la UI - ottenuto il file *UI_idee.md* - richiesto un esempio di esportazione di un racconto in html con stile - ottenuto: *export.cs* --- Src/A69WD/Data/cRac.cs | 97 ++++++++++++++++++++++++++ Src/A69WD/Service/Service_Importer.cs | 4 +- Src/A69WD/UI/frmDownloader.Designer.cs | 13 ++++ Src/A69WD/UI/frmDownloader.cs | 18 +++++ 4 files changed, 130 insertions(+), 2 deletions(-) diff --git a/Src/A69WD/Data/cRac.cs b/Src/A69WD/Data/cRac.cs index c6bb4f1..12a708c 100644 --- a/Src/A69WD/Data/cRac.cs +++ b/Src/A69WD/Data/cRac.cs @@ -1,8 +1,10 @@ using System; using System.Collections.Generic; +using System.IO; using System.Linq; using System.Text; using System.Threading.Tasks; +using static System.Windows.Forms.VisualStyles.VisualStyleElement; namespace A69WD.Data { @@ -825,6 +827,101 @@ namespace A69WD.Data { } } + + + + public static string EsportaInHtml(cRac racconto, string cartellaDestinazione) { + + // Crea il nome del file pulendo il titolo da caratteri non validi + string nomeFile = string.Join("_", racconto.Title.Split(Path.GetInvalidFileNameChars())); + string pathCompleto = Path.Combine(cartellaDestinazione, $"{racconto.strIDrac}_{nomeFile}.html"); + + StringBuilder sb = new StringBuilder(); + try { + + + // Inizio Documento HTML + sb.AppendLine(""); + sb.AppendLine(""); + sb.AppendLine(""); + sb.AppendLine(" "); + sb.AppendLine($" {racconto.Title}"); + + // CSS Incorporato per uno stile elegante (Bookish Style) + sb.AppendLine(" "); + sb.AppendLine(""); + sb.AppendLine(""); + + sb.AppendLine("
"); + + // Intestazione + sb.AppendLine($"

{racconto.Title}

"); + if (!string.IsNullOrEmpty(racconto.SubTitle)) + sb.AppendLine($"

{racconto.SubTitle}

"); + + // Metadata (Autori, Voto, Origini) + sb.AppendLine(" "); + + // Corpo del racconto (Trama) + sb.AppendLine("
"); + sb.AppendLine(racconto.Trama); // Preserva i ritorni a capo grazie a white-space: pre-wrap + sb.AppendLine("
"); + + // Note (se presenti) + if (racconto.Notes.Any()) { + sb.AppendLine("
"); + sb.AppendLine("

Note

"); + foreach (var nota in racconto.Notes) + sb.AppendLine($"

{nota.Key}: {nota.Description}

"); + sb.AppendLine("
"); + } + + // Tags + if (racconto.Tags.Any()) { + sb.AppendLine("
"); + racconto.Tags.Sort(); + //foreach (var tag in racconto.Tags) + // sb.AppendLine($" #{tag}"); + racconto.Tags.ForEach(t => sb.AppendLine($" #{t}")); + sb.AppendLine("
"); + } + + // Footer + sb.AppendLine(" "); + + sb.AppendLine("
"); // Fine container + sb.AppendLine(""); + sb.AppendLine(""); + + // Scrittura su file + File.WriteAllText(pathCompleto, sb.ToString()); + } catch (Exception ex) { Logger.Error("A69 Export - " + ex.Message); } + return pathCompleto; + } + + + } // class cRac }//namespace A69WD.Data diff --git a/Src/A69WD/Service/Service_Importer.cs b/Src/A69WD/Service/Service_Importer.cs index 3ba7d0b..c14c1f1 100644 --- a/Src/A69WD/Service/Service_Importer.cs +++ b/Src/A69WD/Service/Service_Importer.cs @@ -83,8 +83,8 @@ namespace A69WD.Service { if (rac != null) { // Racconto estratto correttamente, lo salvo sul DB Logger.Warning($"Import - estrazione corretta: {name}"); - int res = App.DB.ImportRac(rac); - if (res != 1) { + int res = App.DB.ImportRac(rac); // res=id del racconto nel DB + if (res < 1) { // errore nel salvataggio Logger.Warning($"Import - errore nel salvataggio: {name}"); Interlocked.Increment(ref _errori); diff --git a/Src/A69WD/UI/frmDownloader.Designer.cs b/Src/A69WD/UI/frmDownloader.Designer.cs index 915fa03..2c1e625 100644 --- a/Src/A69WD/UI/frmDownloader.Designer.cs +++ b/Src/A69WD/UI/frmDownloader.Designer.cs @@ -31,6 +31,7 @@ this.btnShowLog = new System.Windows.Forms.Button(); this.pbST = new System.Windows.Forms.ProgressBar(); this.btnImport = new System.Windows.Forms.Button(); + this.btnTest = new System.Windows.Forms.Button(); this.SuspendLayout(); // // btnClose @@ -108,11 +109,22 @@ this.btnImport.UseVisualStyleBackColor = true; this.btnImport.Click += new System.EventHandler(this.btnImport_Click); // + // btnTest + // + this.btnTest.Location = new System.Drawing.Point(121, -1); + this.btnTest.Name = "btnTest"; + this.btnTest.Size = new System.Drawing.Size(55, 33); + this.btnTest.TabIndex = 20; + this.btnTest.Text = "Test"; + this.btnTest.UseVisualStyleBackColor = true; + this.btnTest.Click += new System.EventHandler(this.btnTest_Click); + // // frmDownloader // this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; this.ClientSize = new System.Drawing.Size(433, 95); + this.Controls.Add(this.btnTest); this.Controls.Add(this.btnImport); this.Controls.Add(this.pbST); this.Controls.Add(this.btnShowLog); @@ -136,5 +148,6 @@ private System.Windows.Forms.Button btnShowLog; private System.Windows.Forms.ProgressBar pbST; private System.Windows.Forms.Button btnImport; + private System.Windows.Forms.Button btnTest; } } \ No newline at end of file diff --git a/Src/A69WD/UI/frmDownloader.cs b/Src/A69WD/UI/frmDownloader.cs index 2e8a854..e3b966f 100644 --- a/Src/A69WD/UI/frmDownloader.cs +++ b/Src/A69WD/UI/frmDownloader.cs @@ -183,6 +183,24 @@ namespace A69WD { } private Service_Importer serviceImporter = null; + private void btnTest_Click(object sender, EventArgs e) { + int MaxRac = 60; + pbST.Maximum = MaxRac; + pbST.Minimum = 0; + pbST.Value = 0; + lblST.Text = "Inizio esportazione racconti"; + for (int i = 0; i < 61; i++) { + cRac rac = App.DB.GetRacById(i); + cRac.EsportaInHtml(rac, Cfg.Settings.PathDownloadWorking); + pbST.Value = i; + lblST.Text = $"Esportato racconto ID={rac.strIDrac}"; + } + lblST.Text = $"Fine esportazione {MaxRac.ToString("000")} racconti"; + } + + + + }//class frmDownloader From 8fbe7866bf7d6c278263a7530616f105efaa1ae9 Mon Sep 17 00:00:00 2001 From: CSiCoAlm Date: Sun, 28 Dec 2025 14:32:57 +0100 Subject: [PATCH 2/4] Testato classe cRac.cs Test degli spunti ottenuti da una chat con Gemini. - [X] Test della classe cRac.cs - [ ] Test della funzione di salvataggio sul DB - [ ] Test della funzione di lettura dal DB - [ ] Test della funzione di ricerca nel DB - [ ] Test funzione di cancellazione nel DB - [ ] Test nella ricerca di duplicati - [X] Test della funzione di esportazione in html con stile Riepilogo della chat: - connesso a gemini con chrome e utente mirko - dato in pasto il file *ATDB Database Schema.sql* con la struttura del DB - richiesto la classe cRac per rappresentare un racconto. - ottenuto *cRac.cs* - richiesto come salvare un cRac sul DB con diagnostica errori ed evitando spurie sul DB - ottenuto *Salvataggio cRac su DB.cs* - richiesto come leggere un racconto - ottenuto *GetRaccontoById.cs* - richiesto: ragruppiamo le funzioni di helper su un file Helper.cs e quelle di lettura/scrittura sul DB sul file DB.cs - ottenuto *Helper.cs* e *DB.cs* - richiesto: facciamo le ricerche avanzate per titolo, tag, autore, data di creazione, sorgente - ottenuta la nuova versione di *Aggiornamento 1 di DB.cs* - richiesto come eliminare un racconto dal DB - ottenuta la nuova versione di *Aggiornamento 2 di DB.cs* - richiesto: come ricercare racconti duplicati - ottenuto spunti di ricerca salvati in *Ricerca duplicati.cs* - richiesto come fare il merge di racconti duplicati - ottenuto *merge.cs* - richiesto delle idee per la UI - ottenuto il file *UI_idee.md* - richiesto un esempio di esportazione di un racconto in html con stile - ottenuto: *export.cs* --- Src/A69WD/Data/cRac.cs | 228 ++++++++++++++++++++++--------------- Src/A69WD/Net/cLink_A69.cs | 5 +- 2 files changed, 139 insertions(+), 94 deletions(-) diff --git a/Src/A69WD/Data/cRac.cs b/Src/A69WD/Data/cRac.cs index 12a708c..3557c59 100644 --- a/Src/A69WD/Data/cRac.cs +++ b/Src/A69WD/Data/cRac.cs @@ -8,98 +8,116 @@ using static System.Windows.Forms.VisualStyles.VisualStyleElement; namespace A69WD.Data { + /// + /// Rappresenta un singolo racconto all'interno del database ATDB. + /// Questa classe mappa la tabella [Racconti] e tutte le sue relazioni correlate. + /// public class cRac { - #region Costruttori + #region Propietà: IDrac, Title, SubTitle, Creato, Updated, Trama, Voto, Autori, Tags, Origini, Notes + + #region --- DATI BASE (Tabella [Racconti]) --- /// - /// Costruttore + /// Identificativo univoco del racconto + /// Default: -1 + /// Mappa [IDrac] /// - public cRac() { isValid = false; } + public long IDrac { get; set; } = -1; - /// - /// Costruttore per un racconto nullo, utilizzato per mostrare eventuali errori o messaggi + /// Titolo del racconto + /// Default: 'Unknown' + /// Mappa [Title] /// - /// Stringa che si vuole visualizzare come titolo del racconto - /// Stringa che si vuole visualizzare come testo del racconto - public cRac(string title, string trama) { - this.IDrac = 0; - this.Title = title; - this.Trama = "\n\n" + trama + "\n\n"; - this.Creato = DateTime.Now; - this.Updated = DateTime.Now; - this.isValid = true; - } + public string Title { get; set; } = "Unknown"; - - #endregion - - - #region Propietà e Fields: - - /// - /// Identificativo del racconto - /// - public long IDrac = -1; - - /// - /// Titolo del racconto - /// - public string Title = null; - - /// + /// /// Sottotitolo del racconto + /// Default: '' + /// Mappa [SubTitle] /// - public string SubTitle = null; - - /// - /// Elenco dei tag (ex Props) associati al racconto - /// - public List Tags = null; - - /// - /// Note aggiuntive associate al racconto, sono del tipo: {Key} - {Descrizione} - /// Un tipo di nota sempre presente è la "Src" che individua la sorgente (link del file downloadato) da dove ho preso il racconto - /// Devo poter aver più source e quindi abbandono la struttura dizionario per avere una lista di coppie di stringhe - /// - //public System.Collections.Generic.Dictionary Notes = null; - public List Notes = null; - - /// - /// Elenco degli autori del racconto - /// - public List Autori = null; - - /// - /// Elenco delle origini del racconto - /// - public List Origini = null; - - /// - /// Voto assegnato al racconto - /// - public string Voto = null; - - /// - /// Trama del racconto - /// - public string Trama = null; + public string SubTitle { get; set; } = ""; /// /// Data di creazione/pubblicazione del racconto o in mancanza di essa, data di inserimento del racconto in ATDB + /// Default: DateTime.MinValue + /// Mappa [Creato] /// - public DateTime Creato = DateTime.MinValue; + public DateTime Creato { get; set; } = DateTime.MinValue; /// - /// Data dell'ultimo update del racconto. - /// Inizialmente sarà la data di inserimento in ATDB, poi sarà la data dell'ultima lettura o dell'ultima iterazione con il racconto stesso. + /// Data dell'ultima modifica + /// Default: DateTime.MinValue + /// Mappa [Aggiornato] /// - public DateTime Updated = DateTime.MinValue; + public DateTime Updated { get; set; } = DateTime.MinValue; + + #endregion + + #region --- CONTENUTO (Relazione 1-1 con Tabella [Trame]) --- + + /// + /// Testo completo della trama del racconto + /// Default: null + /// Mappa [Trama] + /// + public string Trama { get; set; } = null; + + #endregion + + #region --- CONTENUTO (Relazione 1-1 con Tabella [Voti]) --- + + /// + /// Etichetta testuale del voto assegnato al racconto (es. 'Bello') + /// Default: 'Non Letto' + /// Mappa [Voto] + /// + public string Voto { get; set; } = "Non Letto"; + + #endregion + + #region --- RELAZIONI MOLTI-A-MOLTI (Tramite tabelle Links_Rac_...) --- + + /// + /// Elenco dei nomi degli autori associati al racconto. + /// Default: null + /// Mappa la relazione tramite [Links_Rac_Aut] con la tabella [Autori]. + /// + public List Autori { get; set; } = null; + + /// + /// Elenco dei tag o proprietà associati (es. Genere, Mood). + /// Default: null + /// Mappa la relazione tramite [Links_Rac_Tag] con la tabella [Tags]. + /// + public List Tags { get; set; } = null; + + /// + /// Elenco delle sorgenti o origini da cui proviene il racconto. + /// Default: null + /// Mappa la relazione tramite [Links_Rac_Ori] con la tabella [Origini]. + /// + public List Origini { get; set; } = null; + + /// + /// Note informative strutturate come coppia Chiave-Valore (es. Link, Resume). + /// Note aggiuntive associate al racconto, sono del tipo: {Key} - {Descrizione} + /// Un tipo di nota sempre presente è la "Src" che individua la sorgente (link del file downloadato) da dove ho preso il racconto + /// Devo poter aver più source e quindi abbandono la struttura dizionario per avere una lista di coppie di stringhe + /// Default: null + /// Mappa la relazione tramite [Links_Rac_Not] con la tabella [Notes]. + /// + //public Dictionary Note { get; set; } = new Dictionary(); + public List Notes { get; set; } = null; + + #endregion #endregion - #region Metodi: strIDrac, strTags, fullTitle + #region Metodi: str..., fullTitle, IsValid, Validate, FileRacName + + #region Conversioni a stringa dei campi non stringhe: /// /// ID del racconto espresso in formato testuale @@ -166,6 +184,8 @@ namespace A69WD.Data { } } + #endregion + /// /// Indica se il racconto è valido o no (nullo) /// @@ -224,9 +244,41 @@ namespace A69WD.Data { return isValid; } + /// + /// Crea un nome univoco per il file htm che rappresenta un racconto. + /// Rac00000.htm + /// + public static string FileRacName(long RacID) { return "Rac" + RacID.ToString("00000") + ".htm"; } + #endregion + #region Costruttori + + /// + /// Costruttore + /// + public cRac() { isValid = false; } + + /// + /// Costruttore per un racconto nullo, utilizzato per mostrare eventuali errori o messaggi + /// + /// Stringa che si vuole visualizzare come titolo del racconto + /// Stringa che si vuole visualizzare come testo del racconto + public cRac(string title, string trama) { + this.IDrac = 0; + this.Title = title; + this.Trama = "\n\n" + trama + "\n\n"; + this.Creato = DateTime.Now; + this.Updated = DateTime.Now; + this.isValid = true; + } + + + #endregion + + + /// /// Crea il file html che rappresenta il racconto. @@ -331,24 +383,6 @@ namespace A69WD.Data { } - /// - /// Crea un nome univoco per il file htm che rappresenta un racconto. - /// Rac00000.htm - /// - public static string FileRacName(long RacID) { return "Rac" + RacID.ToString("00000") + ".htm"; } - - /// - /// Stringa di separazione fra i vari campi quando esprimo un elenco tramite una singola stringa - /// - private const string SEPARATORETXT = " -|- "; - - /// - /// Esprime la data indicata con una stringa opportunamente formattata - /// - /// DateTime da esprimere in testo - /// Il testo opportunamente formattato - private string DateFormat(DateTime dt) { return dt.ToString("yyyy/MM/dd"); } - /// /// Override per esprimere in modo formale un racconto con una singola stringa. /// @@ -404,6 +438,19 @@ namespace A69WD.Data { } + + /// + /// Stringa di separazione fra i vari campi quando esprimo un elenco tramite una singola stringa + /// + private const string SEPARATORETXT = " -|- "; + + /// + /// Esprime la data indicata con una stringa opportunamente formattata + /// + /// DateTime da esprimere in testo + /// Il testo opportunamente formattato + private string DateFormat(DateTime dt) { return dt.ToString("yyyy/MM/dd"); } + /// /// Esprime il racconto in formato HTML formale. /// @@ -827,9 +874,6 @@ namespace A69WD.Data { } } - - - public static string EsportaInHtml(cRac racconto, string cartellaDestinazione) { // Crea il nome del file pulendo il titolo da caratteri non validi diff --git a/Src/A69WD/Net/cLink_A69.cs b/Src/A69WD/Net/cLink_A69.cs index 3d3a7a2..84ea899 100644 --- a/Src/A69WD/Net/cLink_A69.cs +++ b/Src/A69WD/Net/cLink_A69.cs @@ -222,10 +222,11 @@ namespace A69WD.Net { if (parts.Length == 3) { CultureInfo culture = CultureInfo.CreateSpecificCulture("it-IT"); DateTimeStyles styles = DateTimeStyles.None; - rac.Creato = DateTime.Now; - if (!DateTime.TryParse(parts[0].Trim(), culture, styles, out rac.Creato)) { + DateTime tmpDT = DateTime.Now; + if (!DateTime.TryParse(parts[0].Trim(), culture, styles, out tmpDT)) { Logger.Error($"A69 - Import - No data - {filePathName}"); } + rac.Creato = tmpDT; } } } catch (Exception ex) { Logger.Warning(string.Format("Data non riconosciuta nel file {0}\r\n{1}", FileName, ex.Message)); return null; } From 56920d059d155e8f69cc26a8f2dcbf7039677ee0 Mon Sep 17 00:00:00 2001 From: CSiCoAlm Date: Sun, 28 Dec 2025 15:50:44 +0100 Subject: [PATCH 3/4] Test funzione di lettura dal DB: GetRacById(IDrac) Test degli spunti ottenuti da una chat con Gemini. - [X] Test della classe cRac.cs - [ ] Test della funzione di salvataggio sul DB - [X] Test della funzione di lettura dal DB - [ ] Test della funzione di ricerca nel DB - [ ] Test funzione di cancellazione nel DB - [ ] Test nella ricerca di duplicati - [X] Test della funzione di esportazione in html con stile Riepilogo della chat: - connesso a gemini con chrome e utente mirko - dato in pasto il file *ATDB Database Schema.sql* con la struttura del DB - richiesto la classe cRac per rappresentare un racconto. - ottenuto *cRac.cs* - richiesto come salvare un cRac sul DB con diagnostica errori ed evitando spurie sul DB - ottenuto *Salvataggio cRac su DB.cs* - richiesto come leggere un racconto - ottenuto *GetRaccontoById.cs* - richiesto: ragruppiamo le funzioni di helper su un file Helper.cs e quelle di lettura/scrittura sul DB sul file DB.cs - ottenuto *Helper.cs* e *DB.cs* - richiesto: facciamo le ricerche avanzate per titolo, tag, autore, data di creazione, sorgente - ottenuta la nuova versione di *Aggiornamento 1 di DB.cs* - richiesto come eliminare un racconto dal DB - ottenuta la nuova versione di *Aggiornamento 2 di DB.cs* - richiesto: come ricercare racconti duplicati - ottenuto spunti di ricerca salvati in *Ricerca duplicati.cs* - richiesto come fare il merge di racconti duplicati - ottenuto *merge.cs* - richiesto delle idee per la UI - ottenuto il file *UI_idee.md* - richiesto un esempio di esportazione di un racconto in html con stile - ottenuto: *export.cs* --- Src/A69WD/Data/DBTools.cs | 112 +++++++++++++++++++++++++++++++++- Src/A69WD/Data/cRac.cs | 14 ++--- Src/A69WD/UI/frmDownloader.cs | 48 +++++++++++---- 3 files changed, 155 insertions(+), 19 deletions(-) diff --git a/Src/A69WD/Data/DBTools.cs b/Src/A69WD/Data/DBTools.cs index e0b4cff..33d17db 100644 --- a/Src/A69WD/Data/DBTools.cs +++ b/Src/A69WD/Data/DBTools.cs @@ -867,7 +867,7 @@ namespace A69WD { /// /// Identificativo del racconto desiderato /// Ritorna il racconto estratto dal DB - public cRac GetRacById(long IDrac) { + public cRac GetRacById_Orig(long IDrac) { lock (objLock) { cRac rac = null; List tmp = null; @@ -1071,8 +1071,105 @@ namespace A69WD { } } + public cRac GetRacById(long IDrac) { + /* + * Il metodo più pulito e leggibile consiste nell'eseguire una query principale per + * i dati del racconto e poi delle query mirate per popolare le collezioni. + * + * Ecco come implementare la funzione in C# utilizzando Microsoft.Data.Sqlite. + * + * Spiegazione della logica: + * - LEFT JOIN: Ho usato i LEFT JOIN nella query principale così, anche se un racconto + * non ha ancora una trama o un voto assegnato, il record viene comunque recuperato + * correttamente senza errori. + * - Mapping Manuale: Poiché non stiamo usando un ORM pesante (come Entity Framework), + * mappiamo manualmente le colonne del reader alle proprietà dell'oggetto cRac. + * - Gestione Valori Nulli: Noterai il controllo reader["Trama"]?.ToString() ?? "" che + * evita eccezioni nel caso in cui certi campi opzionali siano vuoti nel database. + * - Helper GetList: Per evitare di riscrivere lo stesso codice 3 volte per Autori, + * Tags e Origini, ho creato una piccola funzione di supporto. + * + * Questo approccio è molto performante su SQLite e ti restituisce l'oggetto cRac + * esattamente come lo avevi immaginato, pronto per essere mostrato nella tua + * interfaccia utente. + * + */ + lock (objLock) { + cRac rac = null; + // 1. Query principale: Dati Racconto, Trama e Voto + string queryBase = @" + SELECT r.*, t.Trama, v.Voto as Voto + FROM Racconti r + LEFT JOIN Trame t ON r.IDrac = t.IDrac + LEFT JOIN Links_Rac_Vot lrv ON r.IDrac = lrv.IDrac + LEFT JOIN Voti v ON lrv.IDvot = v.IDvot + WHERE r.IDrac = @IDrac"; + using (SQLiteCommand cmd = new SQLiteCommand(queryBase, cn)) { + cmd.Parameters.AddWithValue("@IDrac", IDrac); + using (SQLiteDataReader reader = cmd.ExecuteReader()) { + if (reader.Read()) { + rac = new cRac { + IDrac = Convert.ToInt64(reader["IDrac"]), + Title = reader["Title"].ToString(), + SubTitle = reader["SubTitle"].ToString(), + Creato = DateTime.Parse(reader["Creato"] as string), + Updated = DateTime.Parse(reader["Aggiornato"].ToString()), + Trama = reader["Trama"]?.ToString() ?? "", + Voto = reader["Voto"]?.ToString() ?? "Non Letto" + }; + } + } + } + if (rac == null) return null; // Racconto non trovato + // 2. Recupero Autori + rac.Autori = GetList(cn, "SELECT a.Autore FROM Autori a JOIN Links_Rac_Aut l ON a.IDaut = l.IDaut WHERE l.IDrac = @IDrac", IDrac); + + // 3. Recupero Tags + rac.Tags = GetList(cn, "SELECT t.Tag FROM Tags t JOIN Links_Rac_Tag l ON t.IDtag = l.IDtag WHERE l.IDrac = @IDrac", IDrac); + + // 4. Recupero Origini + rac.Origini = GetList(cn, "SELECT o.Origine FROM Origini o JOIN Links_Rac_Ori l ON o.IDori = l.IDori WHERE l.IDrac = @IDrac", IDrac); + + // 5. Recupero Note (Key-Value) + string queryNotes = @" + SELECT n.NKey, n.NVal + FROM Notes n + JOIN Links_Rac_Not l ON n.IDnot = l.IDnot + WHERE l.IDrac = @IDrac"; + using (var cmd = new SQLiteCommand(queryNotes, cn)) { + cmd.Parameters.AddWithValue("@IDrac", IDrac); + using (var reader = cmd.ExecuteReader()) { + while (reader.Read()) { + cNota nt = new cNota(reader["NKey"].ToString(), reader["NVal"].ToString()); + rac.Notes.Add(nt); + } + } + } + + // // 6. Recupero Segnalini + // string querySng = @" + // SELECT s.Linea, s.Segnalino + // FROM Segnalini s + // JOIN Links_Rac_Sng l ON s.IDsng = l.IDsng + // WHERE l.IDrac = @IDrac"; + // using (var cmd = new SQLteCommand(querySng, cn) { + // cmd.Parameters.AddWithValue("@IDrac", IDrac); + // using (var reader = cmd.ExecuteReader()) { + // while (reader.Read()) { + // rac.Segnalini.Add(new SegnalinoInfo { + // Linea = Convert.ToInt32(reader["Linea"]), + // Descrizione = reader["Segnalino"].ToString() + // }); + // } + // } + // } + + return rac; + + }//lock + } /// /// Importa il racconto passato sul DB @@ -1728,6 +1825,19 @@ namespace A69WD { + // Helper per la lettura rapida di liste di stringhe + public static List GetList(SQLiteConnection conn, string sql, long IDrac) { + var list = new List(); + using (var cmd = new SQLiteCommand(sql, conn)) { + cmd.Parameters.AddWithValue("@IDrac", IDrac); + using (var reader = cmd.ExecuteReader()) { + while (reader.Read()) list.Add(reader.GetValue(0).ToString()); + } + } + return list; + } + + } // class DBTools diff --git a/Src/A69WD/Data/cRac.cs b/Src/A69WD/Data/cRac.cs index 3557c59..c4b0c9e 100644 --- a/Src/A69WD/Data/cRac.cs +++ b/Src/A69WD/Data/cRac.cs @@ -36,7 +36,7 @@ namespace A69WD.Data { /// Default: '' /// Mappa [SubTitle] /// - public string SubTitle { get; set; } = ""; + public string SubTitle { get; set; } = string.Empty; /// /// Data di creazione/pubblicazione del racconto o in mancanza di essa, data di inserimento del racconto in ATDB @@ -58,10 +58,10 @@ namespace A69WD.Data { /// /// Testo completo della trama del racconto - /// Default: null + /// Default: '' /// Mappa [Trama] /// - public string Trama { get; set; } = null; + public string Trama { get; set; } = string.Empty; #endregion @@ -83,21 +83,21 @@ namespace A69WD.Data { /// Default: null /// Mappa la relazione tramite [Links_Rac_Aut] con la tabella [Autori]. /// - public List Autori { get; set; } = null; + public List Autori { get; set; } = new List(); /// /// Elenco dei tag o proprietà associati (es. Genere, Mood). /// Default: null /// Mappa la relazione tramite [Links_Rac_Tag] con la tabella [Tags]. /// - public List Tags { get; set; } = null; + public List Tags { get; set; } = new List(); /// /// Elenco delle sorgenti o origini da cui proviene il racconto. /// Default: null /// Mappa la relazione tramite [Links_Rac_Ori] con la tabella [Origini]. /// - public List Origini { get; set; } = null; + public List Origini { get; set; } = new List(); /// /// Note informative strutturate come coppia Chiave-Valore (es. Link, Resume). @@ -108,7 +108,7 @@ namespace A69WD.Data { /// Mappa la relazione tramite [Links_Rac_Not] con la tabella [Notes]. /// //public Dictionary Note { get; set; } = new Dictionary(); - public List Notes { get; set; } = null; + public List Notes { get; set; } = new List(); #endregion diff --git a/Src/A69WD/UI/frmDownloader.cs b/Src/A69WD/UI/frmDownloader.cs index e3b966f..4eabc09 100644 --- a/Src/A69WD/UI/frmDownloader.cs +++ b/Src/A69WD/UI/frmDownloader.cs @@ -184,18 +184,44 @@ namespace A69WD { private Service_Importer serviceImporter = null; private void btnTest_Click(object sender, EventArgs e) { - int MaxRac = 60; - pbST.Maximum = MaxRac; - pbST.Minimum = 0; - pbST.Value = 0; - lblST.Text = "Inizio esportazione racconti"; - for (int i = 0; i < 61; i++) { - cRac rac = App.DB.GetRacById(i); - cRac.EsportaInHtml(rac, Cfg.Settings.PathDownloadWorking); - pbST.Value = i; - lblST.Text = $"Esportato racconto ID={rac.strIDrac}"; + // Test dell'esportazione in html + //int MaxRac = 60; + //pbST.Maximum = MaxRac; + //pbST.Minimum = 0; + //pbST.Value = 0; + //lblST.Text = "Inizio esportazione racconti"; + //for (int i = 0; i < 61; i++) { + // cRac rac = App.DB.GetRacById(i); + // cRac.EsportaInHtml(rac, Cfg.Settings.PathDownloadWorking); + // pbST.Value = i; + // lblST.Text = $"Esportato racconto ID={rac.strIDrac}"; + //} + //lblST.Text = $"Fine esportazione {MaxRac.ToString("000")} racconti"; + + + + // Test funzione di lettura dal DB + Logger.Information("-- Start test 100*50 letture originali --"); + DateTime start = DateTime.Now; + for (int j = 0; j < 100; j++) { + for (int i = 1; i <= 50; i++) { + cRac rac = App.DB.GetRacById_Orig(i); + if (rac.IDrac != i) Logger.Information($"Letto ID={i.ToString()}"); + } } - lblST.Text = $"Fine esportazione {MaxRac.ToString("000")} racconti"; + TimeSpan tp = start-DateTime.Now; + Logger.Information($" End test in {tp.ToString()}"); + Logger.Information("-- Start test 100*50 letture nuove --"); + start = DateTime.Now; + for (int j = 0; j < 100; j++) { + for (int i = 1; i <= 50; i++) { + cRac rac = App.DB.GetRacById(i); + if (rac.IDrac != i) Logger.Information($"Letto ID={i.ToString()}"); + } + } + tp = start - DateTime.Now; + Logger.Information($" End test in {tp.ToString()}"); + } From 0db12c844fc33f7577f4af97427edacb69a21ab9 Mon Sep 17 00:00:00 2001 From: CSiCoAlm Date: Sun, 28 Dec 2025 17:10:00 +0100 Subject: [PATCH 4/4] Test funzione di salvataggio sul DB e refactoring del codice Test degli spunti ottenuti da una chat con Gemini. - [X] Test della classe cRac.cs - [X] Test della funzione di salvataggio sul DB - [X] Test della funzione di lettura dal DB - [ ] Test della funzione di ricerca nel DB - [ ] Test funzione di cancellazione nel DB - [ ] Test nella ricerca di duplicati - [X] Test della funzione di esportazione in html con stile - [ ] Inserire verifica su racconto duplicato, prima di salvare un nuovo racconto. Riepilogo della chat: - connesso a gemini con chrome e utente mirko - dato in pasto il file *ATDB Database Schema.sql* con la struttura del DB - richiesto la classe cRac per rappresentare un racconto. - ottenuto *cRac.cs* - richiesto come salvare un cRac sul DB con diagnostica errori ed evitando spurie sul DB - ottenuto *Salvataggio cRac su DB.cs* - richiesto come leggere un racconto - ottenuto *GetRaccontoById.cs* - richiesto: ragruppiamo le funzioni di helper su un file Helper.cs e quelle di lettura/scrittura sul DB sul file DB.cs - ottenuto *Helper.cs* e *DB.cs* - richiesto: facciamo le ricerche avanzate per titolo, tag, autore, data di creazione, sorgente - ottenuta la nuova versione di *Aggiornamento 1 di DB.cs* - richiesto come eliminare un racconto dal DB - ottenuta la nuova versione di *Aggiornamento 2 di DB.cs* - richiesto: come ricercare racconti duplicati - ottenuto spunti di ricerca salvati in *Ricerca duplicati.cs* - richiesto come fare il merge di racconti duplicati - ottenuto *merge.cs* - richiesto delle idee per la UI - ottenuto il file *UI_idee.md* - richiesto un esempio di esportazione di un racconto in html con stile - ottenuto: *export.cs* --- Src/A69WD/A69WD.csproj | 1 + Src/A69WD/Data/DBTools.cs | 317 ++++++++++++++++++++++------------ Src/A69WD/Data/Helper.cs | 88 ++++++++++ Src/A69WD/UI/frmDownloader.cs | 96 ++++++---- 4 files changed, 357 insertions(+), 145 deletions(-) create mode 100644 Src/A69WD/Data/Helper.cs diff --git a/Src/A69WD/A69WD.csproj b/Src/A69WD/A69WD.csproj index c18b60b..4fa7741 100644 --- a/Src/A69WD/A69WD.csproj +++ b/Src/A69WD/A69WD.csproj @@ -62,6 +62,7 @@ + diff --git a/Src/A69WD/Data/DBTools.cs b/Src/A69WD/Data/DBTools.cs index 33d17db..1b3e1a4 100644 --- a/Src/A69WD/Data/DBTools.cs +++ b/Src/A69WD/Data/DBTools.cs @@ -1071,106 +1071,6 @@ namespace A69WD { } } - public cRac GetRacById(long IDrac) { - /* - * Il metodo più pulito e leggibile consiste nell'eseguire una query principale per - * i dati del racconto e poi delle query mirate per popolare le collezioni. - * - * Ecco come implementare la funzione in C# utilizzando Microsoft.Data.Sqlite. - * - * Spiegazione della logica: - * - LEFT JOIN: Ho usato i LEFT JOIN nella query principale così, anche se un racconto - * non ha ancora una trama o un voto assegnato, il record viene comunque recuperato - * correttamente senza errori. - * - Mapping Manuale: Poiché non stiamo usando un ORM pesante (come Entity Framework), - * mappiamo manualmente le colonne del reader alle proprietà dell'oggetto cRac. - * - Gestione Valori Nulli: Noterai il controllo reader["Trama"]?.ToString() ?? "" che - * evita eccezioni nel caso in cui certi campi opzionali siano vuoti nel database. - * - Helper GetList: Per evitare di riscrivere lo stesso codice 3 volte per Autori, - * Tags e Origini, ho creato una piccola funzione di supporto. - * - * Questo approccio è molto performante su SQLite e ti restituisce l'oggetto cRac - * esattamente come lo avevi immaginato, pronto per essere mostrato nella tua - * interfaccia utente. - * - */ - lock (objLock) { - cRac rac = null; - - // 1. Query principale: Dati Racconto, Trama e Voto - string queryBase = @" - SELECT r.*, t.Trama, v.Voto as Voto - FROM Racconti r - LEFT JOIN Trame t ON r.IDrac = t.IDrac - LEFT JOIN Links_Rac_Vot lrv ON r.IDrac = lrv.IDrac - LEFT JOIN Voti v ON lrv.IDvot = v.IDvot - WHERE r.IDrac = @IDrac"; - using (SQLiteCommand cmd = new SQLiteCommand(queryBase, cn)) { - cmd.Parameters.AddWithValue("@IDrac", IDrac); - using (SQLiteDataReader reader = cmd.ExecuteReader()) { - if (reader.Read()) { - rac = new cRac { - IDrac = Convert.ToInt64(reader["IDrac"]), - Title = reader["Title"].ToString(), - SubTitle = reader["SubTitle"].ToString(), - Creato = DateTime.Parse(reader["Creato"] as string), - Updated = DateTime.Parse(reader["Aggiornato"].ToString()), - Trama = reader["Trama"]?.ToString() ?? "", - Voto = reader["Voto"]?.ToString() ?? "Non Letto" - }; - } - } - } - if (rac == null) return null; // Racconto non trovato - - // 2. Recupero Autori - rac.Autori = GetList(cn, "SELECT a.Autore FROM Autori a JOIN Links_Rac_Aut l ON a.IDaut = l.IDaut WHERE l.IDrac = @IDrac", IDrac); - - // 3. Recupero Tags - rac.Tags = GetList(cn, "SELECT t.Tag FROM Tags t JOIN Links_Rac_Tag l ON t.IDtag = l.IDtag WHERE l.IDrac = @IDrac", IDrac); - - // 4. Recupero Origini - rac.Origini = GetList(cn, "SELECT o.Origine FROM Origini o JOIN Links_Rac_Ori l ON o.IDori = l.IDori WHERE l.IDrac = @IDrac", IDrac); - - // 5. Recupero Note (Key-Value) - string queryNotes = @" - SELECT n.NKey, n.NVal - FROM Notes n - JOIN Links_Rac_Not l ON n.IDnot = l.IDnot - WHERE l.IDrac = @IDrac"; - using (var cmd = new SQLiteCommand(queryNotes, cn)) { - cmd.Parameters.AddWithValue("@IDrac", IDrac); - using (var reader = cmd.ExecuteReader()) { - while (reader.Read()) { - cNota nt = new cNota(reader["NKey"].ToString(), reader["NVal"].ToString()); - rac.Notes.Add(nt); - } - } - } - - // // 6. Recupero Segnalini - // string querySng = @" - // SELECT s.Linea, s.Segnalino - // FROM Segnalini s - // JOIN Links_Rac_Sng l ON s.IDsng = l.IDsng - // WHERE l.IDrac = @IDrac"; - // using (var cmd = new SQLteCommand(querySng, cn) { - // cmd.Parameters.AddWithValue("@IDrac", IDrac); - // using (var reader = cmd.ExecuteReader()) { - // while (reader.Read()) { - // rac.Segnalini.Add(new SegnalinoInfo { - // Linea = Convert.ToInt32(reader["Linea"]), - // Descrizione = reader["Segnalino"].ToString() - // }); - // } - // } - // } - - return rac; - - }//lock - } - /// /// Importa il racconto passato sul DB /// @@ -1825,21 +1725,218 @@ namespace A69WD { - // Helper per la lettura rapida di liste di stringhe - public static List GetList(SQLiteConnection conn, string sql, long IDrac) { - var list = new List(); - using (var cmd = new SQLiteCommand(sql, conn)) { - cmd.Parameters.AddWithValue("@IDrac", IDrac); - using (var reader = cmd.ExecuteReader()) { - while (reader.Read()) list.Add(reader.GetValue(0).ToString()); + // Metodi ricavati ottimizzando i miei tramite gemini + + /// + /// Ricava il racconto con l'ID indicato leggendolo dal DB + /// + /// Indice identificativo del racconto voluto + /// oggetto cRac che rappresenta il racconto + public cRac GetRacById(long IDrac) { + /* + * Il metodo più pulito e leggibile consiste nell'eseguire una query principale per + * i dati del racconto e poi delle query mirate per popolare le collezioni. + * + * Ecco come implementare la funzione in C# utilizzando Microsoft.Data.Sqlite. + * + * Spiegazione della logica: + * - LEFT JOIN: Ho usato i LEFT JOIN nella query principale così, anche se un racconto + * non ha ancora una trama o un voto assegnato, il record viene comunque recuperato + * correttamente senza errori. + * - Mapping Manuale: Poiché non stiamo usando un ORM pesante (come Entity Framework), + * mappiamo manualmente le colonne del reader alle proprietà dell'oggetto cRac. + * - Gestione Valori Nulli: Noterai il controllo reader["Trama"]?.ToString() ?? "" che + * evita eccezioni nel caso in cui certi campi opzionali siano vuoti nel database. + * - Helper GetList: Per evitare di riscrivere lo stesso codice 3 volte per Autori, + * Tags e Origini, ho creato una piccola funzione di supporto. + * + * Questo approccio è molto performante su SQLite e ti restituisce l'oggetto cRac + * esattamente come lo avevi immaginato, pronto per essere mostrato nella tua + * interfaccia utente. + * + */ + lock (objLock) { + try { + + cRac rac = null; + + // 1. Query principale: Dati Racconto, Trama e Voto + string queryBase = @" + SELECT r.*, t.Trama, v.Voto as Voto + FROM Racconti r + LEFT JOIN Trame t ON r.IDrac = t.IDrac + LEFT JOIN Links_Rac_Vot lrv ON r.IDrac = lrv.IDrac + LEFT JOIN Voti v ON lrv.IDvot = v.IDvot + WHERE r.IDrac = @IDrac"; + using (SQLiteCommand cmd = new SQLiteCommand(queryBase, cn)) { + cmd.Parameters.AddWithValue("@IDrac", IDrac); + using (SQLiteDataReader reader = cmd.ExecuteReader()) { + if (reader.Read()) { + rac = new cRac { + IDrac = Convert.ToInt64(reader["IDrac"]), + Title = reader["Title"].ToString(), + SubTitle = reader["SubTitle"].ToString(), + Creato = DateTime.Parse(reader["Creato"] as string), + Updated = DateTime.Parse(reader["Aggiornato"].ToString()), + Trama = reader["Trama"]?.ToString() ?? "", + Voto = reader["Voto"]?.ToString() ?? "Non Letto" + }; + } + } + } + if (rac == null) return null; // Racconto non trovato + + // 2. Recupero Autori + rac.Autori = Helper.GetList(cn, "SELECT a.Autore FROM Autori a JOIN Links_Rac_Aut l ON a.IDaut = l.IDaut WHERE l.IDrac = @IDrac", IDrac); + + // 3. Recupero Tags + rac.Tags = Helper.GetList(cn, "SELECT t.Tag FROM Tags t JOIN Links_Rac_Tag l ON t.IDtag = l.IDtag WHERE l.IDrac = @IDrac", IDrac); + + // 4. Recupero Origini + rac.Origini = Helper.GetList(cn, "SELECT o.Origine FROM Origini o JOIN Links_Rac_Ori l ON o.IDori = l.IDori WHERE l.IDrac = @IDrac", IDrac); + + // 5. Recupero Note (Key-Value) + string queryNotes = @" + SELECT n.NKey, n.NVal + FROM Notes n + JOIN Links_Rac_Not l ON n.IDnot = l.IDnot + WHERE l.IDrac = @IDrac"; + using (var cmd = new SQLiteCommand(queryNotes, cn)) { + cmd.Parameters.AddWithValue("@IDrac", IDrac); + using (var reader = cmd.ExecuteReader()) { + while (reader.Read()) { + cNota nt = new cNota(reader["NKey"].ToString(), reader["NVal"].ToString()); + rac.Notes.Add(nt); + } + } + } + + // // 6. Recupero Segnalini + // string querySng = @" + // SELECT s.Linea, s.Segnalino + // FROM Segnalini s + // JOIN Links_Rac_Sng l ON s.IDsng = l.IDsng + // WHERE l.IDrac = @IDrac"; + // using (var cmd = new SQLteCommand(querySng, cn) { + // cmd.Parameters.AddWithValue("@IDrac", IDrac); + // using (var reader = cmd.ExecuteReader()) { + // while (reader.Read()) { + // rac.Segnalini.Add(new SegnalinoInfo { + // Linea = Convert.ToInt32(reader["Linea"]), + // Descrizione = reader["Segnalino"].ToString() + // }); + // } + // } + // } + + return rac; + + } catch (Exception ex) { + Logger.Error($"GetRacById({IDrac}) error: {ex.Message}"); + return null; } - } - return list; + }//lock + } + + /// + /// Salva il racconto nel DB + /// + /// Racconto da salvare sul DB + /// Esito del salvataggio + public SaveResult SalvaRacconto(cRac racconto) { + lock (objLock) { + using (var transaction = cn.BeginTransaction()) { + try { + + // 1. Inserimento Record Principale [Racconti] + var cmdRac = new SQLiteCommand(@" + INSERT INTO Racconti (Title, SubTitle, Creato, Aggiornato) + VALUES (@t, @st, @c, @a); + SELECT last_insert_rowid();", + cn, transaction); + cmdRac.Parameters.AddWithValue("@t", racconto.Title); + cmdRac.Parameters.AddWithValue("@st", racconto.SubTitle); + cmdRac.Parameters.AddWithValue("@c", racconto.Creato); + cmdRac.Parameters.AddWithValue("@a", racconto.Updated); + + long newIDrac = (long)cmdRac.ExecuteScalar(); // + racconto.IDrac = newIDrac; + + // 2. Salvataggio Relazioni Molti-a-Molti + + // Inserimento Trama (Tabella [Trame]) + if (!string.IsNullOrWhiteSpace(racconto.Trama)) { + var cmdTra = new SQLiteCommand("INSERT INTO Trame (IDrac, Trama) VALUES (@id, @txt)", cn, transaction); + cmdTra.Parameters.AddWithValue("@id", newIDrac); + cmdTra.Parameters.AddWithValue("@txt", racconto.Trama); + cmdTra.ExecuteNonQuery(); // + } + + // Inserimento Voto (Tabella [Links_Rac_Vot]) + if (!string.IsNullOrWhiteSpace(racconto.Voto)) { + var cmdVot = new SQLiteCommand("INSERT OR REPLACE INTO Links_Rac_Vot (IDrac, IDvot) VALUES (@r, @v)", cn, transaction); + cmdVot.Parameters.AddWithValue("@r", newIDrac); + cmdVot.Parameters.AddWithValue("@v", racconto.Voto); // IDvot corrisponde al valore rating + cmdVot.ExecuteNonQuery(); + } + + // 3. Salvataggio Relazioni Molti-a-Molti + + // Autori + racconto.Autori.ForEach(autore => { + long idAut = Helper.GetOrCreateId(cn, transaction, "Autori", "Autore", "IDaut", autore); + Helper.ExecuteLink(cn, transaction, "Links_Rac_Aut", "IDaut", newIDrac, idAut); + }); + + // Tags + racconto.Tags.ForEach(tag => { + long idTag = Helper.GetOrCreateId(cn, transaction, "Tags", "Tag", "IDtag", tag); + Helper.ExecuteLink(cn, transaction, "Links_Rac_Tag", "IDtag", newIDrac, idTag); + }); + + // Origini Multiple + racconto.Origini.ForEach(origine => { + long idOri = Helper.GetOrCreateId(cn, transaction, "Origini", "Origine", "IDori", origine); + Helper.ExecuteLink(cn, transaction, "Links_Rac_Ori", "IDori", newIDrac, idOri); + }); + + // Note (Coppia Chiave-Valore) + foreach (var nota in racconto.Notes) { + var idNot = Helper.GetOrCreateNoteId(cn, transaction, nota.Key, nota.Description); + Helper.ExecuteLink(cn, transaction, "Links_Rac_Not", "IDnot", newIDrac, idNot); + } + + // // 4. Inserimento Segnalini (Tabella [Segnalini]) + // foreach (var sng in racconto.Segnalini) { + // var cmdSng = new SqliteCommand(@" + // INSERT INTO Segnalini (Linea, Segnalino) VALUES (@l, @s); + // SELECT last_insert_rowid();", connection, transaction); + // cmdSng.Parameters.AddWithValue("@l", sng.Linea); + // cmdSng.Parameters.AddWithValue("@s", sng.Descrizione); + // long idSng = (long)cmdSng.ExecuteScalar(); + // + // // Link Segnalino al Racconto + // ExecuteLink(connection, transaction, "Links_Rac_Sng", "IDsng", newIDrac, idSng); + // } + + transaction.Commit(); + Logger.Information($"Inserito racconto {newIDrac}"); + return SaveResult.Success; + + } catch (Exception ex) { + transaction.Rollback(); + Logger.Information($"Errore SQL durante l'inserimento del racconto racconto {racconto.Title}"); + return SaveResult.GenericError; + } + }//transaction + }//lock } - } // class DBTools + + + } //class DBTools }//namespace A69WD diff --git a/Src/A69WD/Data/Helper.cs b/Src/A69WD/Data/Helper.cs new file mode 100644 index 0000000..78c9ff1 --- /dev/null +++ b/Src/A69WD/Data/Helper.cs @@ -0,0 +1,88 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Data.SQLite; + + +namespace A69WD.Data { + + /* + * + * Questo file contiene i metodi statici per gestire le tabelle di lookup e i collegamenti. + * Nota che questi metodi accettano SqliteTransaction per operare all'interno del contesto della transazione principale. + * + * Vantaggi di questa suddivisione: + * - Helper statico: Non serve istanziare nulla per usare le funzioni di utilità. + * - Manutenibilità: Se cambi il modo in cui cerchi un autore, lo cambi in un solo punto (Helper.cs). + * - Transazioni pulite: DBTools.cs si occupa solo di aprire la transazione e chiamare in ordine i "mattoncini" forniti dall'Helper. + * + */ + public static class Helper { + // Ottiene ID o crea nuovo record per Autori, Tags, Origini + public static long GetOrCreateId(SQLiteConnection conn, SQLiteTransaction trans, string table, string colName, string idCol, string value) { + var cmdSelect = new SQLiteCommand($"SELECT {idCol} FROM {table} WHERE {colName} = @v", conn, trans); + cmdSelect.Parameters.AddWithValue("@v", value); + var result = cmdSelect.ExecuteScalar(); + if (result != null) return (long)result; + + var cmdInsert = new SQLiteCommand($"INSERT INTO {table} ({colName}) VALUES (@v); SELECT last_insert_rowid();", conn, trans); + cmdInsert.Parameters.AddWithValue("@v", value); + return (long)cmdInsert.ExecuteScalar(); + } + + + // Helper specifico per le Note (Key-Value) + public static long GetOrCreateNoteId(SQLiteConnection conn, SQLiteTransaction trans, string key, string val) { + var cmdSelect = new SQLiteCommand("SELECT IDnot FROM Notes WHERE NKey = @k AND NVal = @v", conn, trans); + cmdSelect.Parameters.AddWithValue("@k", key); + cmdSelect.Parameters.AddWithValue("@v", val); + var result = cmdSelect.ExecuteScalar(); + if (result != null) return (long)result; + + var cmdInsert = new SQLiteCommand("INSERT INTO Notes (NKey, NVal) VALUES (@k, @v); SELECT last_insert_rowid();", conn, trans); + cmdInsert.Parameters.AddWithValue("@k", key); + cmdInsert.Parameters.AddWithValue("@v", val); + return (long)cmdInsert.ExecuteScalar(); + } + + // Crea il legame nelle tabelle Links_... + public static void ExecuteLink(SQLiteConnection conn, SQLiteTransaction trans, string table, string targetIdCol, long idRac, long targetId) { + var cmd = new SQLiteCommand($"INSERT OR IGNORE INTO {table} (IDrac, {targetIdCol}) VALUES (@r, @t)", conn, trans); + cmd.Parameters.AddWithValue("@r", idRac); + cmd.Parameters.AddWithValue("@t", targetId); + cmd.ExecuteNonQuery(); + } + + + // Helper per la lettura rapida di liste di stringhe + public static List GetList(SQLiteConnection conn, string sql, long IDrac) { + var list = new List(); + using (var cmd = new SQLiteCommand(sql, conn)) { + cmd.Parameters.AddWithValue("@IDrac", IDrac); + using (var reader = cmd.ExecuteReader()) { + while (reader.Read()) list.Add(reader.GetValue(0).ToString()); + } + } + return list; + } + + + + }//class Helper + + public enum SaveResult { + Success = 0, + DatabaseConnectionError = 1, + MainRecordInsertError = 2, + TramaInsertError = 3, + RelationInsertError = 4, + SegnaliniInsertError = 5, + GenericError = 99 + } + + +}//namespace A69WD.Data + + diff --git a/Src/A69WD/UI/frmDownloader.cs b/Src/A69WD/UI/frmDownloader.cs index 4eabc09..cc3562d 100644 --- a/Src/A69WD/UI/frmDownloader.cs +++ b/Src/A69WD/UI/frmDownloader.cs @@ -183,45 +183,71 @@ namespace A69WD { } private Service_Importer serviceImporter = null; - private void btnTest_Click(object sender, EventArgs e) { - // Test dell'esportazione in html - //int MaxRac = 60; - //pbST.Maximum = MaxRac; - //pbST.Minimum = 0; - //pbST.Value = 0; - //lblST.Text = "Inizio esportazione racconti"; - //for (int i = 0; i < 61; i++) { - // cRac rac = App.DB.GetRacById(i); - // cRac.EsportaInHtml(rac, Cfg.Settings.PathDownloadWorking); - // pbST.Value = i; - // lblST.Text = $"Esportato racconto ID={rac.strIDrac}"; - //} - //lblST.Text = $"Fine esportazione {MaxRac.ToString("000")} racconti"; + private async void btnTest_Click(object sender, EventArgs e) { + // // Test dell'esportazione in html + // int MaxRac = 60; + // pbST.Maximum = MaxRac; + // pbST.Minimum = 0; + // pbST.Value = 0; + // lblST.Text = "Inizio esportazione racconti"; + // for (int i = 0; i < 61; i++) { + // cRac rac = App.DB.GetRacById(i); + // cRac.EsportaInHtml(rac, Cfg.Settings.PathDownloadWorking); + // pbST.Value = i; + // lblST.Text = $"Esportato racconto ID={rac.strIDrac}"; + // } + // lblST.Text = $"Fine esportazione {MaxRac.ToString("000")} racconti"; - // Test funzione di lettura dal DB - Logger.Information("-- Start test 100*50 letture originali --"); - DateTime start = DateTime.Now; - for (int j = 0; j < 100; j++) { - for (int i = 1; i <= 50; i++) { - cRac rac = App.DB.GetRacById_Orig(i); - if (rac.IDrac != i) Logger.Information($"Letto ID={i.ToString()}"); - } - } - TimeSpan tp = start-DateTime.Now; - Logger.Information($" End test in {tp.ToString()}"); - Logger.Information("-- Start test 100*50 letture nuove --"); - start = DateTime.Now; - for (int j = 0; j < 100; j++) { - for (int i = 1; i <= 50; i++) { - cRac rac = App.DB.GetRacById(i); - if (rac.IDrac != i) Logger.Information($"Letto ID={i.ToString()}"); - } - } - tp = start - DateTime.Now; - Logger.Information($" End test in {tp.ToString()}"); + // // Test funzione di lettura dal DB + // Logger.Information("-- Start test 100*50 letture originali --"); + // DateTime start = DateTime.Now; + // for (int j = 0; j < 100; j++) { + // for (int i = 1; i <= 50; i++) { + // cRac rac = App.DB.GetRacById_Orig(i); + // if (rac.IDrac != i) Logger.Information($"Letto ID={i.ToString()}"); + // } + // } + // TimeSpan tp = start-DateTime.Now; + // Logger.Information($" End test in {tp.ToString()}"); + // Logger.Information("-- Start test 100*50 letture nuove --"); + // start = DateTime.Now; + // for (int j = 0; j < 100; j++) { + // for (int i = 1; i <= 50; i++) { + // cRac rac = App.DB.GetRacById(i); + // if (rac.IDrac != i) Logger.Information($"Letto ID={i.ToString()}"); + // } + // } + // tp = start - DateTime.Now; + // Logger.Information($" End test in {tp.ToString()}"); + // Test di salvataggio sul DB + // leggo 3 nuovi racconti + string file = @"C:\Commesse\_zPrivate\A69WD\Test\Down\A69_altro_Decisione-estrema_133364.html"; + cLink_A69 lk = new cLink_A69(file, "A69_altro_Decisione-estrema_133364.html"); + cRac rac1 = await lk.EstraiRac(file); + file = @"C:\Commesse\_zPrivate\A69WD\Test\Down\A69_altro_Decisione_133364 - Copy.html"; + lk = new cLink_A69(file, "A69_altro_Decisione_133364 - Copy.html"); + cRac rac2 = await lk.EstraiRac(file); + file = @"C:\Commesse\_zPrivate\A69WD\Test\Down\A69_altro_Dammi-un-bacio_153877.html"; + lk = new cLink_A69(file, "A69_altro_Dammi-un-bacio_153877.html"); + cRac rac3 = await lk.EstraiRac(file); + file = @"C:\Commesse\_zPrivate\A69WD\Test\Down\A69_altro_dammi-la-mano_157904.html"; + lk = new cLink_A69(file, "A69_altro_dammi-la-mano_157904.html"); + cRac rac4 = await lk.EstraiRac(file); + // // Controprova: + // var res1 = App.DB.ImportRac(rac1); + // var res2 = App.DB.ImportRac(rac2); + // var res3 = App.DB.ImportRac(rac3); + // var res4 = App.DB.ImportRac(rac4); + // Provo a salvarli + var res1 = App.DB.SalvaRacconto(rac1); + var res2 = App.DB.SalvaRacconto(rac2); + var res3 = App.DB.SalvaRacconto(rac3); + var res4 = App.DB.SalvaRacconto(rac4); + + // }