From 63e769810f54ad4358185ec63713cf4a713b8e36 Mon Sep 17 00:00:00 2001 From: CSiCoAlm Date: Thu, 1 Jan 2026 14:02:44 +0100 Subject: [PATCH] Refactoring e migliorata importazione --- Src/A69WD/General/Cfg.cs | 12 + Src/A69WD/Net/cLink_A69.cs | 315 ++++++++++++++++++++++++++ Src/A69WD/Properties/AssemblyInfo.cs | 22 +- Src/A69WD/Service/Service_Importer.cs | 55 ++++- Src/A69WD/Settings.cfg | 6 + Src/A69WD/UI/frmReader.Designer.cs | 13 ++ Src/A69WD/UI/frmReader.cs | 42 +++- 7 files changed, 450 insertions(+), 15 deletions(-) diff --git a/Src/A69WD/General/Cfg.cs b/Src/A69WD/General/Cfg.cs index 9109ac0..61b3126 100644 --- a/Src/A69WD/General/Cfg.cs +++ b/Src/A69WD/General/Cfg.cs @@ -119,6 +119,12 @@ namespace A69WD { public virtual string PathApplicationRoot { get { return applicationRootPath; } set { applicationRootPath = value; } } private string applicationRootPath; + /// + /// Path della cartella con i file da importare. + /// + public virtual string PathImport { get { return pathImport; } set { pathImport = value; } } + private string pathImport; + /// /// Nome del database usato dall'applicazione [DEPRECATE]. /// @@ -150,6 +156,12 @@ namespace A69WD { public virtual int nThread { get { return nthread; } set { nthread = value; } } private int nthread; + /// + /// Numero di file massimo da gestire + /// + public virtual int MaxNumFile { get { return maxNumFile; } set { maxNumFile = value; } } + private int maxNumFile; + #endregion diff --git a/Src/A69WD/Net/cLink_A69.cs b/Src/A69WD/Net/cLink_A69.cs index ce3cc69..cceba20 100644 --- a/Src/A69WD/Net/cLink_A69.cs +++ b/Src/A69WD/Net/cLink_A69.cs @@ -3,6 +3,8 @@ using System.Collections.Concurrent; using System.Collections.Generic; using System.Globalization; using System.Linq; +using System.Net; +using System.Net.Http; using System.Runtime.Remoting.Messaging; using System.Security.Policy; using System.Text; @@ -733,6 +735,319 @@ namespace A69WD.Net { + #region Gestione Sito A69 + + private static HttpClient client = null; + private static readonly string nickname = @"Toky78"; + private static readonly string password = @"p!fw05Q9Hc6j5"; + private static bool autenticated = false; + + /// + /// Indica se siamo autenticati al sito + /// + public bool Autenticated => autenticated; + + + /// + /// Esegue l'autenticazione al sito: + /// - Scarica la pagina di autenticazione + /// - analizza quanto scaricato per determinare i campi dinamici 'ts' e 'token' + /// - prepara la risposta al form di identificazione + /// - invia il POST di autenticazione + /// - analizza la risposta per verificare il corretto login + /// + public static async Task Authenticate() { + if (autenticated) { + // Autenticazione già fatta, posso proseguire senza altro fare + Logger.Warning("A69 - Autenticazione già effettuata."); + } + try { + + #region 1) Prepare HTML Client + + HttpClientHandler _handler = new HttpClientHandler { + CookieContainer = new CookieContainer(), + UseCookies = true, + AllowAutoRedirect = true, + AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate + }; + + client = new HttpClient(_handler); + client.Timeout = TimeSpan.FromSeconds(60); + client.DefaultRequestHeaders.TryAddWithoutValidation("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64)"); + client.DefaultRequestHeaders.Referrer = new Uri(cLink_A69.LoginPageUrl); + + #endregion + + #region 2) GET pagina di login + + Console.WriteLine("A69 - Richiedo la pagina di login..."); + Logger.Information("A69 - Richiedo la pagina di login..."); + string loginPageHtml = await client.GetStringAsync(cLink_A69.LoginPageUrl); + + #endregion + + #region 3) Parsing HTML per estrarre ts e token + + var doc = new HtmlAgilityPack.HtmlDocument(); + doc.LoadHtml(loginPageHtml); + + string ts = doc.DocumentNode + .SelectSingleNode("//input[@name='ts']") + ?.GetAttributeValue("value", ""); + string token = doc.DocumentNode + .SelectSingleNode("//input[@name='token']") + ?.GetAttributeValue("value", ""); + + if (string.IsNullOrWhiteSpace(ts) || string.IsNullOrWhiteSpace(token)) { + Console.WriteLine("A69 - Impossibile estrarre 'ts' o 'token' dalla pagina di login."); + Logger.Information("A69 - Impossibile estrarre 'ts' o 'token' dalla pagina di login."); + return autenticated; + } + Console.WriteLine($"A69 - Parametri dinamici trovati: ts={ts}, token={token}"); + Logger.Information($"A69 - Parametri dinamici trovati: ts={ts}, token={token}"); + + #endregion + + #region 4) POST login con valori dinamici + + var loginData = new FormUrlEncodedContent(new[] { + new KeyValuePair("nick", nickname), + new KeyValuePair("pwd", password), + new KeyValuePair("ricordami","1"), + new KeyValuePair("login", "Entra"), + new KeyValuePair("ts", ts), + new KeyValuePair("token", token) + }); + + Console.WriteLine("Invio credenziali di login..."); + var loginResponse = await client.PostAsync(cLink_A69.LoginPostUrl, loginData); + + if (!loginResponse.IsSuccessStatusCode) { + Console.WriteLine($"A69 - Login fallito. HTTP {(int)loginResponse.StatusCode} {loginResponse.ReasonPhrase}"); + Logger.Information($"A69 - Login fallito. HTTP {(int)loginResponse.StatusCode} {loginResponse.ReasonPhrase}"); + return autenticated; + } + + #endregion + + #region 5) Verifica autenticazione correttamente avvenuta + + // Verifica semplice: sulla pagina di login effettuato, ricerco il nome dell'utente: + string loginResult = await loginResponse.Content.ReadAsStringAsync(); + if (loginResult.Contains(nickname)) { + Console.WriteLine("A69 - Login effettuato con successo! Sessione autenticata attiva."); + Logger.Information("A69 - Login effettuato con successo! Sessione autenticata attiva."); + autenticated = true; + } else { + Console.WriteLine("A69 - Login non riuscito: il server ha restituito un messaggio di errore."); + Logger.Information("A69 - Login non riuscito: il server ha restituito un messaggio di errore."); + autenticated = false; + } + + #endregion + + } catch (Exception ex) { + Console.WriteLine($"Errore generale: {ex.Message}"); + Logger.Information($"Errore generale: {ex.Message}"); + } + return autenticated; + } + + + /// + /// Codice da eseguire per ciascun A69 link: + /// - scarico l'url indicato + /// - se richiesto lo salvo come file di testo + /// - analizzo il documento html ed estraggo tutti gli url ad altre pagine di link + /// - analizzo il documento html ed estraggo tutti gli url a racconti + /// - analizzo il documento html ed estraggo il testo del racconto + /// + /// + /// + public async Task> CrawlPageAsync() { + var newlinks = new List(); + if (!autenticated) { + // Sito non autenticato, non posso fare nulla + Logger.Warning("A69 - Sito non autenticato. Download non permessi."); + return newlinks; + } + try { + Console.WriteLine($"A69 - Scarico: {URL}"); + HtmlDocument tdoc = null; + + #region ToConfirm + if (Status == eLinkState.ToConfirm) { + // Eseguo la verifica se link da downloadare + Status = eLinkState.ToDownload; + } + #endregion + + #region ToDownload + if (Status == eLinkState.ToDownload) { + Status = eLinkState.OnDownload; + } + #endregion + + #region OnDownload + if (Status == eLinkState.OnDownload) { + // Scarico il file + var resp = await client.GetAsync(URL); + if (!resp.IsSuccessStatusCode) { + Console.WriteLine($"A69 - Errore HTTP {(int)resp.StatusCode} {resp.ReasonPhrase} per {URL}"); + Status = eLinkState.Error; + //App.DB.LinkLog(lk); + return newlinks; + } + // Scarico la pagina + var thtml = await client.GetStringAsync(URL); + tdoc = new HtmlDocument(); + tdoc.LoadHtml(thtml); + // Salvo il file downloadato + //System.IO.StreamWriter tsw = new System.IO.StreamWriter(lk.DstFilePath, false, Encoding.UTF8); + System.IO.StreamWriter tsw = new System.IO.StreamWriter(System.IO.Path.Combine(FilePath, FileName), false, Encoding.UTF8); + tsw.Write(thtml); + + Status = eLinkState.ToAnalize; + } + #endregion + + #region ToAnalize + { + if (Status == eLinkState.ToAnalize) { + List tmpLk = new List(); + #region Estraggo gli url ad indici: + { + /* + * Gli url delle pagine di indice sono del tipo: + * + * + * + */ + // Estraggo i link alle altre pagine: + var tnodes = tdoc.DocumentNode.SelectNodes("//div[@id='pagination']//ul//li//a[@href]"); + if (tnodes != null) { + foreach (var link in tnodes) { + var href = link.GetAttributeValue("href", ""); + if (!string.IsNullOrEmpty(href) && href.StartsWith("/racconti-erotici/racconti_lista.php?genere=")) + tmpLk.Add(href); //tmpLk.Add(cLink_A69.PageBase + href); + } + } + // Rimuove duplicati e ordina alfabeticamente + tmpLk = tmpLk + .OrderBy(s => s) // ordina alfabeticamente + .Distinct() // elimina duplicati + .ToList(); + // Inserisco i link trovati + try { + foreach (string ilk in tmpLk) { + string[] separator = new string[] { "&pagina=" }; + string[] txt = ilk.Split(separator, StringSplitOptions.RemoveEmptyEntries); + string url = cLink_A69.PageBase + ilk; + string name = txt[0].Replace("/racconti-erotici/racconti_lista.php?genere=", "_A69_") + "_" + int.Parse(txt[1]).ToString("0000") + ".html"; + newlinks.Add(new cLink_A69(url, name)); + } + } catch (Exception ex) { Logger.Error("Errore analisi Idx link dal " + FileName + " - " + ex.Message); } + } + #endregion + + #region Estraggo gli url a racconti: + { + /* + * + * I link ai racconti sono dei pulsanti così formati: + * + */ + // Estraggo i link ai racconti: + tmpLk = new List(); + // Estraggo i link alle altre pagine: + var tnodes = tdoc.DocumentNode.SelectNodes("//div[@class='continua']//a[@href]"); + if (tnodes != null) { + foreach (var link in tnodes) { + var href = link.GetAttributeValue("href", ""); + if (!string.IsNullOrEmpty(href) && href.StartsWith("/racconti-erotici/") && (href.EndsWith(".html") || href.EndsWith(".htm"))) + tmpLk.Add(href); //tmpLk.Add(cLink_A69.PageBase + href); + } + } + // Rimuove duplicati e ordina alfabeticamente + tmpLk = tmpLk + .OrderBy(s => s) // ordina alfabeticamente + .Distinct() // elimina duplicati + .ToList(); + // Inserisco i link trovati + try { + foreach (string ilk in tmpLk) { + string url = cLink_A69.PageBase + ilk; + string name = ilk.Replace("/racconti-erotici/", "A69_").Replace(@"/", "_"); + newlinks.Add(new cLink_A69(url, name)); + } + } catch (Exception ex) { Logger.Error("Errore analisi Idx link dal " + FileName + " - " + ex.Message); } + } + #endregion + + #region Estraggo il racconto dal file e lo inserisco nel DB + if (FileName.StartsWith("A69_")) { + // Link ad un racconto + cRac rac = EstraiRac(tdoc); + if (rac != null) { + // Racconto estratto correttamente, lo salvo sul DB + App.DB.ImportRac(rac); + } + } + #endregion + + Status = eLinkState.End; + App.DB.LinkLog(this); + } + } + #endregion + + #region End + if (Status == eLinkState.End) { + } + #endregion + + #region Error + if (Status == eLinkState.Error) { + } + #endregion + + #region Abort + if (Status == eLinkState.Abort) { + } + #endregion + + #region Cancel + if (Status == eLinkState.Cancel) { + } + #endregion + + Console.WriteLine("Link.Count=" + newlinks.Count.ToString("00")); + + } catch (Exception ex) { + Console.WriteLine($"Errore con {URL}: {ex.Message}"); + Status = eLinkState.Error; + //App.DB.LinkLog(lk); + } + return newlinks; + } + + #endregion + + + }//class cLink_A69 }//namespace A69WD.Net diff --git a/Src/A69WD/Properties/AssemblyInfo.cs b/Src/A69WD/Properties/AssemblyInfo.cs index d42f70e..afdef1b 100644 --- a/Src/A69WD/Properties/AssemblyInfo.cs +++ b/Src/A69WD/Properties/AssemblyInfo.cs @@ -32,8 +32,26 @@ using System.Runtime.InteropServices; // È possibile specificare tutti i valori oppure impostare valori predefiniti per i numeri relativi alla revisione e alla build // usando l'asterisco '*' come illustrato di seguito: // [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("0.5.0.0")] -[assembly: AssemblyFileVersion("0.5.0.0")] +[assembly: AssemblyVersion("0.5.1.0")] +[assembly: AssemblyFileVersion("0.5.1.0")] + + +/* + * + * Vrs 0.5.1.0 + * + * Compilata da CSiCoAlm il 2026/01/01 + * Refactoring del downloader per poterlo utilizzare in ATDB + * Upgradato l'importazione, selezionando casualmente i file da importare, + * gestendo il numero massimo di file e la cartella da importare, sul file di configurazione. + * + * + * ToDo: + * - Aggiungere gestione parallela della FIFO secondo vecchi modello per aumentare il feedback alla UI + * - Inserire anche l'estrazione dei racconti + * - Inserire funzionalità avanzate sul lettore + * + */ /* diff --git a/Src/A69WD/Service/Service_Importer.cs b/Src/A69WD/Service/Service_Importer.cs index 6fc9e38..0cdfe9c 100644 --- a/Src/A69WD/Service/Service_Importer.cs +++ b/Src/A69WD/Service/Service_Importer.cs @@ -24,6 +24,7 @@ namespace A69WD.Service { // Contatori private int _totali = 0; private int _completati = 0; + private int _duplicati = 0; private int _errori = 0; public Service_Importer(StatusStrip sb) { @@ -43,12 +44,11 @@ namespace A69WD.Service { private async void ProcessWorking() { - string[] files = System.IO.Directory.GetFiles(_path, "A69_*.html"); - _totali = files.Length; - _completati = 0; - _errori = 0; - UpdateCounters(); - + //string[] files = System.IO.Directory.GetFiles(_path, "A69_*.html"); + //_totali = files.Length; + //_completati = 0; + //_errori = 0; + //UpdateCounters(); // foreach (string file in files) { // Task.Run(async () => { // Logger.Warning($"Start estrazione racconto: {file}"); @@ -72,7 +72,37 @@ namespace A69WD.Service { // }); // } - foreach (string file in files) { + // Seleziono 1000 file in modo casuale dalla directory indicata: + // - Ottieni tutti i file nel percorso + string[] files = System.IO.Directory.GetFiles(Cfg.Settings.PathImport, "A69_*.html"); + int MaxNFile = files.Length < Cfg.Settings.MaxNumFile ? files.Length : Cfg.Settings.MaxNumFile; + // - Mescola l'elenco dei file in modo casuale e seleziona i primi MaxNFile + // Usiamo LINQ per mescolare (Shuffle) l'array e prendere i primi MaxNFile + // Si noti che mescolare casualmente l'intero array può essere costoso per molte migliaia di file. + // Alternativamente, si può usare un algoritmo di campionamento casuale (come Fisher-Yates) se l'efficienza è cruciale, + // ma per 10 elementi da un elenco ragionevole, questo è semplice ed efficace. + Random rng = new Random(); + var fileCasuali = files + .OrderBy(f => rng.Next()) // Ordina in base a un numero casuale + .Take(MaxNFile) // Prendi i primi MaxNFile + .ToList(); // Converti in List (opzionale, ma utile) + + // aggiorno contatori + _totali = fileCasuali.Count; + _completati = 0; + _duplicati = 0; + _errori = 0; + UpdateCounters(); + + // Verifico cartelle di destinazione + string pathErrOnExtract = System.IO.Path.Combine(Cfg.Settings.PathDownloadWorking, "ErrOnExtract"); + if (!Directory.Exists(pathErrOnExtract)) Directory.CreateDirectory(pathErrOnExtract); + string pathErrOnSave = System.IO.Path.Combine(Cfg.Settings.PathDownloadWorking, "ErrOnSave"); + if (!Directory.Exists(pathErrOnSave)) Directory.CreateDirectory(pathErrOnSave); + string pathDone = System.IO.Path.Combine(Cfg.Settings.PathDownloadWorking, "Done"); + if (!Directory.Exists(pathDone)) Directory.CreateDirectory(pathDone); + // Ciclo su ciascun file + foreach (string file in fileCasuali) { string name = System.IO.Path.GetFileName(file); Logger.Warning($"Import - Start estrazione racconto: {name}"); @@ -80,27 +110,32 @@ namespace A69WD.Service { cRac rac = await lk.EstraiRac(file); if (rac != null) { // Racconto estratto correttamente, lo salvo sul DB - Logger.Warning($"Import - estrazione corretta: {name}"); + Logger.Warning($"Import - estrazione corretta: {name}"); 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); + // - Sposto il file + System.IO.File.Move(file, System.IO.Path.Combine(pathErrOnSave, System.IO.Path.GetFileName(file))); } else { // salvataggio ok Logger.Warning($"Import - racconto salvato: {name}"); Interlocked.Increment(ref _completati); + // - Sposto il file + System.IO.File.Move(file, System.IO.Path.Combine(pathDone, System.IO.Path.GetFileName(file))); } } else { - Logger.Warning($"Import - estrazione errata: {name}"); + Logger.Warning($"Import - estrazione errata: {name}"); Interlocked.Increment(ref _errori); + // - Sposto il file + System.IO.File.Move(file, System.IO.Path.Combine(pathErrOnExtract, System.IO.Path.GetFileName(file))); } UpdateCounters(); } UpdateCounters(); } - private void UpdateCounters() { if (_sb.InvokeRequired) { _sb.Invoke(new Action(UpdateCounters)); diff --git a/Src/A69WD/Settings.cfg b/Src/A69WD/Settings.cfg index 2b28f54..ee6f83d 100644 --- a/Src/A69WD/Settings.cfg +++ b/Src/A69WD/Settings.cfg @@ -6,6 +6,12 @@ C:\Commesse\_zPrivate\A69WD\Test + + C:\Commesse\_zPrivate\A69WD\Test\Import + + + 1000 + A69DB.db diff --git a/Src/A69WD/UI/frmReader.Designer.cs b/Src/A69WD/UI/frmReader.Designer.cs index 0e4399b..f73280f 100644 --- a/Src/A69WD/UI/frmReader.Designer.cs +++ b/Src/A69WD/UI/frmReader.Designer.cs @@ -30,6 +30,7 @@ this.sbStatusLabel = new System.Windows.Forms.ToolStripStatusLabel(); this.btnTest = new System.Windows.Forms.Button(); this.btnTest2 = new System.Windows.Forms.Button(); + this.btnTest3 = new System.Windows.Forms.Button(); this.sb.SuspendLayout(); this.SuspendLayout(); // @@ -114,12 +115,23 @@ this.btnTest2.UseVisualStyleBackColor = true; this.btnTest2.Click += new System.EventHandler(this.btnTest2_Click); // + // btnTest3 + // + this.btnTest3.Location = new System.Drawing.Point(140, 4); + this.btnTest3.Name = "btnTest3"; + this.btnTest3.Size = new System.Drawing.Size(62, 48); + this.btnTest3.TabIndex = 6; + this.btnTest3.Text = "Test 3"; + this.btnTest3.UseVisualStyleBackColor = true; + this.btnTest3.Click += new System.EventHandler(this.btnTest3_Click); + // // frmReader // this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; this.ClientSize = new System.Drawing.Size(700, 444); this.ControlBox = false; + this.Controls.Add(this.btnTest3); this.Controls.Add(this.btnTest2); this.Controls.Add(this.btnTest); this.Controls.Add(this.rtb); @@ -143,5 +155,6 @@ private System.Windows.Forms.ToolStripStatusLabel sbStatusLabel; private System.Windows.Forms.Button btnTest; private System.Windows.Forms.Button btnTest2; + private System.Windows.Forms.Button btnTest3; } } \ No newline at end of file diff --git a/Src/A69WD/UI/frmReader.cs b/Src/A69WD/UI/frmReader.cs index 8c5a185..00782d8 100644 --- a/Src/A69WD/UI/frmReader.cs +++ b/Src/A69WD/UI/frmReader.cs @@ -683,7 +683,7 @@ namespace A69WD.UI { // - Eseguo l'autenticazione bool res = await siteA69.Authenticate(); if (res) { - sbStatusLabel.Text = "A69 logged"; + sbStatusLabel.Text = "A69 autenticated"; Logger.Information("Sito A69 loggato: " + res.ToString()); } else { Logger.Information("Login Fault"); @@ -756,7 +756,7 @@ namespace A69WD.UI { // - Eseguo l'autenticazione bool res = await site.Authenticate(); if (res) { - sbStatusLabel.Text = "A69 logged"; + sbStatusLabel.Text = "A69 autenticated"; Logger.Information("Sito A69 loggato: " + res.ToString()); } else { Logger.Information("Login Fault"); @@ -792,7 +792,7 @@ namespace A69WD.UI { // - Eseguo l'autenticazione bool res = await site.Authenticate(); if (res) { - sbStatusLabel.Text = "A69 logged"; + sbStatusLabel.Text = "A69 autenticated"; Logger.Information("Sito A69 loggato: " + res.ToString()); } else { Logger.Information("Login Fault"); @@ -816,5 +816,41 @@ namespace A69WD.UI { sbStatusLabel.Text = (start - end).ToString(); } + + private async void btnTest3_Click(object sender, EventArgs e) { + + // Test download multipli con client diversi ma che condividono solo la sessione e nuovo refactoring + + // - Eseguo l'autenticazione + bool res = await cLink_A69.Authenticate(); + if (res) { + sbStatusLabel.Text = "A69 autenticated"; + Logger.Information("Sito A69 loggato: " + res.ToString()); + } else { + sbStatusLabel.Text = "Fault Login"; + Logger.Information("Login Fault"); + return; + } + + // - Verifico path per il download + if (!System.IO.Directory.Exists(Cfg.Settings.PathDownloadWorking)) System.IO.Directory.CreateDirectory(Cfg.Settings.PathDownloadWorking); + cLink_A69[] lks = new cLink_A69[26]; + for (int i = 1; i < lks.Length; i++) { + lks[i] = new cLink_A69($"https://www.annunci69.it/racconti-erotici/racconti_lista.php?genere=altro&pagina={i}", $"_A69_altro_{i.ToString("0000")}.html"); + } + + DateTime start = DateTime.Now; + // - Download dei link come finora fato (steso client per condividere la sessione): + for (int i = 1; i < lks.Length; i++) { + List urls = await lks[i].CrawlPageAsync(); + } + DateTime end = DateTime.Now; + + sbStatusLabel.Text = (start - end).ToString(); + + } + + + } }