Integrato spunti ottenuti da una chat con Gemini

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.

---
Merge pull request 'Testato spunti ottenuti da una chat con Gemini' (#7) from mrk/Issue: MrkPi/A69WD#6 into master
Reviewed-on: http://mrkalm.ddns.net:34333/MrkPi/A69WD/pulls/7
Cmd: git push origin HEAD:refs/for/master -o topic="Issue: MrkPi/A69WD#6" -o title="Testato spunti ottenuti da una chat con Gemini"
This commit is contained in:
Mrk committed 2025-12-29 11:33:06 +01:00
commit b43dcbbb88
8 files changed
+619 -98

No files matched your search

+1
View File
@@ -62,6 +62,7 @@
<Compile Include="Data\cNota.cs" />
<Compile Include="Data\cRac.cs" />
<Compile Include="Data\DBTools.cs" />
<Compile Include="Data\Helper.cs" />
<Compile Include="Net\eLinkState.cs" />
<Compile Include="Net\cLink_A69.cs" />
<Compile Include="Service\Service_Crawler.cs" />
+212 -5
View File
@@ -867,7 +867,7 @@ namespace A69WD {
/// </summary>
/// <param name="IDrac">Identificativo del racconto desiderato</param>
/// <returns>Ritorna il racconto estratto dal DB</returns>
public cRac GetRacById(long IDrac) {
public cRac GetRacById_Orig(long IDrac) {
lock (objLock) {
cRac rac = null;
List<string> tmp = null;
@@ -1071,9 +1071,6 @@ namespace A69WD {
}
}
/// <summary>
/// Importa il racconto passato sul DB
/// </summary>
@@ -1728,8 +1725,218 @@ namespace A69WD {
// Metodi ricavati ottimizzando i miei tramite gemini
} // class DBTools
/// <summary>
/// Ricava il racconto con l'ID indicato leggendolo dal DB
/// </summary>
/// <param name="IDrac">Indice identificativo del racconto voluto</param>
/// <returns>oggetto cRac che rappresenta il racconto</returns>
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;
}
}//lock
}
/// <summary>
/// Salva il racconto nel DB
/// </summary>
/// <param name="racconto">Racconto da salvare sul DB</param>
/// <returns>Esito del salvataggio</returns>
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
}//namespace A69WD
+88
View File
@@ -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<string> GetList(SQLiteConnection conn, string sql, long IDrac) {
var list = new List<string>();
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
+230 -89
View File
@@ -1,103 +1,123 @@
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 {
/// <summary>
/// Rappresenta un singolo racconto all'interno del database ATDB.
/// Questa classe mappa la tabella [Racconti] e tutte le sue relazioni correlate.
/// </summary>
public class cRac {
#region Costruttori
#region Propietà: IDrac, Title, SubTitle, Creato, Updated, Trama, Voto, Autori, Tags, Origini, Notes
#region --- DATI BASE (Tabella [Racconti]) ---
/// <summary>
/// Costruttore
/// Identificativo univoco del racconto
/// Default: -1
/// Mappa [IDrac]
/// </summary>
public cRac() { isValid = false; }
public long IDrac { get; set; } = -1;
/// <summary>
/// Costruttore per un racconto nullo, utilizzato per mostrare eventuali errori o messaggi
/// <summary> Titolo del racconto
/// Default: 'Unknown'
/// Mappa [Title]
/// </summary>
/// <param name="title">Stringa che si vuole visualizzare come titolo del racconto</param>
/// <param name="trama">Stringa che si vuole visualizzare come testo del racconto</param>
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:
/// <summary>
/// Identificativo del racconto
/// </summary>
public long IDrac = -1;
/// <summary>
/// Titolo del racconto
/// </summary>
public string Title = null;
/// <summary>
/// <summary>
/// Sottotitolo del racconto
/// Default: ''
/// Mappa [SubTitle]
/// </summary>
public string SubTitle = null;
/// <summary>
/// Elenco dei tag (ex Props) associati al racconto
/// </summary>
public List<string> Tags = null;
/// <summary>
/// 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
/// </summary>
//public System.Collections.Generic.Dictionary<string, string> Notes = null;
public List<cNota> Notes = null;
/// <summary>
/// Elenco degli autori del racconto
/// </summary>
public List<string> Autori = null;
/// <summary>
/// Elenco delle origini del racconto
/// </summary>
public List<string> Origini = null;
/// <summary>
/// Voto assegnato al racconto
/// </summary>
public string Voto = null;
/// <summary>
/// Trama del racconto
/// </summary>
public string Trama = null;
public string SubTitle { get; set; } = string.Empty;
/// <summary>
/// Data di creazione/pubblicazione del racconto o in mancanza di essa, data di inserimento del racconto in ATDB
/// Default: DateTime.MinValue
/// Mappa [Creato]
/// </summary>
public DateTime Creato = DateTime.MinValue;
public DateTime Creato { get; set; } = DateTime.MinValue;
/// <summary>
/// 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]
/// </summary>
public DateTime Updated = DateTime.MinValue;
public DateTime Updated { get; set; } = DateTime.MinValue;
#endregion
#region --- CONTENUTO (Relazione 1-1 con Tabella [Trame]) ---
/// <summary>
/// Testo completo della trama del racconto
/// Default: ''
/// Mappa [Trama]
/// </summary>
public string Trama { get; set; } = string.Empty;
#endregion
#region --- CONTENUTO (Relazione 1-1 con Tabella [Voti]) ---
/// <summary>
/// Etichetta testuale del voto assegnato al racconto (es. 'Bello')
/// Default: 'Non Letto'
/// Mappa [Voto]
/// </summary>
public string Voto { get; set; } = "Non Letto";
#endregion
#region --- RELAZIONI MOLTI-A-MOLTI (Tramite tabelle Links_Rac_...) ---
/// <summary>
/// Elenco dei nomi degli autori associati al racconto.
/// Default: null
/// Mappa la relazione tramite [Links_Rac_Aut] con la tabella [Autori].
/// </summary>
public List<string> Autori { get; set; } = new List<string>();
/// <summary>
/// Elenco dei tag o proprietà associati (es. Genere, Mood).
/// Default: null
/// Mappa la relazione tramite [Links_Rac_Tag] con la tabella [Tags].
/// </summary>
public List<string> Tags { get; set; } = new List<string>();
/// <summary>
/// Elenco delle sorgenti o origini da cui proviene il racconto.
/// Default: null
/// Mappa la relazione tramite [Links_Rac_Ori] con la tabella [Origini].
/// </summary>
public List<string> Origini { get; set; } = new List<string>();
/// <summary>
/// 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].
/// </summary>
//public Dictionary<string, string> Note { get; set; } = new Dictionary<string, string>();
public List<cNota> Notes { get; set; } = new List<cNota>();
#endregion
#endregion
#region Metodi: strIDrac, strTags, fullTitle
#region Metodi: str..., fullTitle, IsValid, Validate, FileRacName
#region Conversioni a stringa dei campi non stringhe:
/// <summary>
/// ID del racconto espresso in formato testuale
@@ -164,6 +184,8 @@ namespace A69WD.Data {
}
}
#endregion
/// <summary>
/// Indica se il racconto è valido o no (nullo)
/// </summary>
@@ -222,9 +244,41 @@ namespace A69WD.Data {
return isValid;
}
/// <summary>
/// Crea un nome univoco per il file htm che rappresenta un racconto.
/// Rac00000.htm
/// </summary>
public static string FileRacName(long RacID) { return "Rac" + RacID.ToString("00000") + ".htm"; }
#endregion
#region Costruttori
/// <summary>
/// Costruttore
/// </summary>
public cRac() { isValid = false; }
/// <summary>
/// Costruttore per un racconto nullo, utilizzato per mostrare eventuali errori o messaggi
/// </summary>
/// <param name="title">Stringa che si vuole visualizzare come titolo del racconto</param>
/// <param name="trama">Stringa che si vuole visualizzare come testo del racconto</param>
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
/// <summary>
/// Crea il file html che rappresenta il racconto.
@@ -329,24 +383,6 @@ namespace A69WD.Data {
}
/// <summary>
/// Crea un nome univoco per il file htm che rappresenta un racconto.
/// Rac00000.htm
/// </summary>
public static string FileRacName(long RacID) { return "Rac" + RacID.ToString("00000") + ".htm"; }
/// <summary>
/// Stringa di separazione fra i vari campi quando esprimo un elenco tramite una singola stringa
/// </summary>
private const string SEPARATORETXT = " -|- ";
/// <summary>
/// Esprime la data indicata con una stringa opportunamente formattata
/// </summary>
/// <param name="dt">DateTime da esprimere in testo</param>
/// <returns>Il testo opportunamente formattato</returns>
private string DateFormat(DateTime dt) { return dt.ToString("yyyy/MM/dd"); }
/// <summary>
/// Override per esprimere in modo formale un racconto con una singola stringa.
/// </summary>
@@ -402,6 +438,19 @@ namespace A69WD.Data {
}
/// <summary>
/// Stringa di separazione fra i vari campi quando esprimo un elenco tramite una singola stringa
/// </summary>
private const string SEPARATORETXT = " -|- ";
/// <summary>
/// Esprime la data indicata con una stringa opportunamente formattata
/// </summary>
/// <param name="dt">DateTime da esprimere in testo</param>
/// <returns>Il testo opportunamente formattato</returns>
private string DateFormat(DateTime dt) { return dt.ToString("yyyy/MM/dd"); }
/// <summary>
/// Esprime il racconto in formato HTML formale.
/// </summary>
@@ -825,6 +874,98 @@ 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("<!DOCTYPE html>");
sb.AppendLine("<html lang='it'>");
sb.AppendLine("<head>");
sb.AppendLine(" <meta charset='UTF-8'>");
sb.AppendLine($" <title>{racconto.Title}</title>");
// CSS Incorporato per uno stile elegante (Bookish Style)
sb.AppendLine(" <style>");
sb.AppendLine(" body { font-family: 'Georgia', serif; line-height: 1.6; color: #333; max-width: 800px; margin: 40px auto; padding: 20px; background-color: #f4f1ea; }");
sb.AppendLine(" .container { background: white; padding: 50px; box-shadow: 0 0 10px rgba(0,0,0,0.1); border-radius: 5px; }");
sb.AppendLine(" h1 { color: #2c3e50; text-align: center; margin-bottom: 5px; font-variant: small-caps; }");
sb.AppendLine(" .subtitle { text-align: center; font-style: italic; color: #7f8c8d; margin-bottom: 30px; }");
sb.AppendLine(" .metadata { border-top: 1px solid #eee; border-bottom: 1px solid #eee; padding: 15px 0; margin-bottom: 30px; font-size: 0.9em; display: flex; flex-wrap: wrap; gap: 20px; }");
sb.AppendLine(" .meta-item { color: #555; }");
sb.AppendLine(" .meta-label { font-weight: bold; color: #2c3e50; }");
sb.AppendLine(" .trama { white-space: pre-wrap; text-align: justify; font-size: 1.1em; }");
sb.AppendLine(" .tags { margin-top: 40px; }");
sb.AppendLine(" .tag { background: #e0e0e0; padding: 3px 10px; border-radius: 15px; font-size: 0.8em; margin-right: 5px; display: inline-block; }");
sb.AppendLine(" .footer { margin-top: 50px; font-size: 0.8em; text-align: center; color: #bdc3c7; border-top: 1px solid #eee; padding-top: 10px; }");
sb.AppendLine(" @media print { body { background: white; } .container { box-shadow: none; } }");
sb.AppendLine(" </style>");
sb.AppendLine("</head>");
sb.AppendLine("<body>");
sb.AppendLine(" <div class='container'>");
// Intestazione
sb.AppendLine($" <h1>{racconto.Title}</h1>");
if (!string.IsNullOrEmpty(racconto.SubTitle))
sb.AppendLine($" <p class='subtitle'>{racconto.SubTitle}</p>");
// Metadata (Autori, Voto, Origini)
sb.AppendLine(" <div class='metadata'>");
sb.AppendLine($" <div class='meta-item'><span class='meta-label'>Autore:</span> {string.Join(", ", racconto.Autori)}</div>");
sb.AppendLine($" <div class='meta-item'><span class='meta-label'>Data:</span> {racconto.Creato}</div>");
sb.AppendLine($" <div class='meta-item'><span class='meta-label'>Voto:</span> {racconto.Voto})</div>");
if (racconto.Origini.Any())
sb.AppendLine($" <div class='meta-item'><span class='meta-label'>Sorgente:</span> {string.Join(", ", racconto.Origini)}</div>");
sb.AppendLine(" </div>");
// Corpo del racconto (Trama)
sb.AppendLine(" <div class='trama'>");
sb.AppendLine(racconto.Trama); // Preserva i ritorni a capo grazie a white-space: pre-wrap
sb.AppendLine(" </div>");
// Note (se presenti)
if (racconto.Notes.Any()) {
sb.AppendLine(" <div style='margin-top: 30px; padding: 15px; background: #f9f9f9; border-left: 5px solid #2c3e50;'>");
sb.AppendLine(" <h3 style='margin-top:0;'>Note</h3>");
foreach (var nota in racconto.Notes)
sb.AppendLine($" <p><strong>{nota.Key}:</strong> {nota.Description}</p>");
sb.AppendLine(" </div>");
}
// Tags
if (racconto.Tags.Any()) {
sb.AppendLine(" <div class='tags'>");
racconto.Tags.Sort();
//foreach (var tag in racconto.Tags)
// sb.AppendLine($" <span class='tag'>#{tag}</span>");
racconto.Tags.ForEach(t => sb.AppendLine($" <span class='tag'>#{t}</span>"));
sb.AppendLine(" </div>");
}
// Footer
sb.AppendLine(" <div class='footer'>");
sb.AppendLine($" Esportato da ATDB Collection - {DateTime.Now:dd/MM/yyyy HH:mm}");
sb.AppendLine(" </div>");
sb.AppendLine(" </div>"); // Fine container
sb.AppendLine("</body>");
sb.AppendLine("</html>");
// Scrittura su file
File.WriteAllText(pathCompleto, sb.ToString());
} catch (Exception ex) { Logger.Error("A69 Export - " + ex.Message); }
return pathCompleto;
}
} // class cRac
}//namespace A69WD.Data
+3 -2
View File
@@ -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; }
+2 -2
View File
@@ -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);
+13
View File
@@ -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;
}
}
+70
View File
@@ -183,6 +183,76 @@ namespace A69WD {
}
private Service_Importer serviceImporter = null;
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 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);
//
}
}//class frmDownloader