diff --git a/Src/A69WD/A69WD.csproj b/Src/A69WD/A69WD.csproj index 9a72457..7aad2c7 100644 --- a/Src/A69WD/A69WD.csproj +++ b/Src/A69WD/A69WD.csproj @@ -62,6 +62,7 @@ + Form diff --git a/Src/A69WD/Net/cLink_A69.cs b/Src/A69WD/Net/cLink_A69.cs index 8b24934..3cfa612 100644 --- a/Src/A69WD/Net/cLink_A69.cs +++ b/Src/A69WD/Net/cLink_A69.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; using System.Runtime.Remoting.Messaging; @@ -42,6 +43,35 @@ namespace A69WD.Net { } + + public static List GetStartLink() { + List links = new List(); + for (int i = 0; i < 10; i++) { + foreach (string cat in categorie) { + string url = LinkIdxBase.Replace("{Categoria}", cat).Replace("{ID}", i.ToString()); + string name = string.Format("_A69_{0}_{1}.html", cat, i.ToString()); + links.Add(new cLink_A69(url, name)); + } + } + return links; + } + + public static bool InsertStartLink(BlockingCollection _urlQueue) { + int i = 1; + try { + foreach (string cat in categorie) { + string url = LinkIdxBase.Replace("{Categoria}", cat).Replace("{ID}", i.ToString()); + string name = string.Format("_A69_{0}_{1}.html", cat, i.ToString("0000")); + _urlQueue.Add(new cLink_A69(url, name)); + } + return true; + } catch (Exception ex) { + Logger.Error("Errore inserendo A69 Start Link - " + ex.Message); + return false; + } + } + + }//class cLink_A69 }//namespace A69WD.Net diff --git a/Src/A69WD/Service/Service_Crawler.cs b/Src/A69WD/Service/Service_Crawler.cs new file mode 100644 index 0000000..103aad3 --- /dev/null +++ b/Src/A69WD/Service/Service_Crawler.cs @@ -0,0 +1,143 @@ +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Linq; +using System.Net.Http; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using System.Windows.Forms; + + + +namespace A69WD.Service { + public class Service_Crawler { + + // Thread worker che consuma la coda + private Thread _workerThread; + + // Coda thread-safe di URL da scaricare + private readonly BlockingCollection _urlQueue = new BlockingCollection(new ConcurrentQueue()); + + // Semaforo per limitare il numero massimo di download simultanei + private readonly SemaphoreSlim _semaphore = new SemaphoreSlim(1); + + // Contatori + private int _inCorso = 0; + private int _completati = 0; + private int _errori = 0; + + // HttpClient autenticato (fornito da AuthService) + private HttpClient _client; + + + public Service_Crawler(Label lblInCorso) { + _lblInCorso = lblInCorso; + } + + + + + + + // Riferimenti ai controlli UI per aggiornare i contatori e la progress bar + private readonly Label _lblInCorso; + private readonly Label _lblCompletati; + private readonly Label _lblDaFare; + private readonly Label _lblErrori; + private readonly ProgressBar _progressBar; + + + + + + /// + /// Avvia il thread worker che consuma la coda di URL. + /// + public void StartWorker(HttpClient client) { + _client = client; + _workerThread = new Thread(ProcessQueue) { IsBackground = true }; + _workerThread.Start(); + } + + /// + /// Ferma il worker completando la coda. + /// + public void StopWorker() { + _urlQueue.CompleteAdding(); + } + + /// + /// Aggiunge un URL alla coda FIFO. + /// + public void EnqueueUrl(string url) { + _urlQueue.Add(url); + UpdateCounters(); + } + + /// + /// Thread worker: consuma la coda ed avvia task di download. + /// + private void ProcessQueue() { + foreach (var url in _urlQueue.GetConsumingEnumerable()) { + Task.Run(async () => { + Interlocked.Increment(ref _inCorso); + UpdateCounters(); + + await _semaphore.WaitAsync(); + try { + // Scarica contenuto della pagina + var html = await _client.GetStringAsync(url); + + Interlocked.Increment(ref _completati); + + // Salvo il file downloadato + System.IO.StreamWriter sw = new System.IO.StreamWriter("A69_Idx_altro_" + _completati.ToString("000") + ".html", false, Encoding.UTF8); + sw.Write(html); + + Logger.Information($"Scaricato {url} ({html.Length} bytes)"); + } catch (Exception ex) { + Interlocked.Increment(ref _errori); + Logger.Error($"Errore su {url}: {ex.Message}"); + } finally { + Interlocked.Decrement(ref _inCorso); + _semaphore.Release(); + UpdateCounters(); + } + }); + } + } + + + + /// + /// Aggiorna i contatori e la progress bar sulla UI. + /// + private void UpdateCounters() { + if (_lblInCorso.InvokeRequired) { + _lblInCorso.Invoke(new Action(UpdateCounters)); + return; + } + + int daFare = _urlQueue.Count; + + _lblInCorso.Text = $"In corso: {_inCorso} - Completati: {_completati} - Errori: {_errori} - Da fare: {daFare}"; + + //int totale = _inCorso + _completati + _errori + daFare; + //int fatti = _completati + _errori; + // + //_progressBar.Maximum = totale > 0 ? totale : 1; + //_progressBar.Value = fatti; + } + + + + + + + + + + } + +} diff --git a/Src/A69WD/Site/cSite_A69.cs b/Src/A69WD/Site/cSite_A69.cs index 2927f96..2077f8f 100644 --- a/Src/A69WD/Site/cSite_A69.cs +++ b/Src/A69WD/Site/cSite_A69.cs @@ -87,8 +87,18 @@ namespace A69WD.Site { return logged; } - public async Task> CrawlPageAsync(iLink lk) { - var links = new List(); + /// + /// 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(iLink lk) { + var newlinks = new List(); try { Console.WriteLine($"A69 - Scarico: {lk.URL}"); HtmlDocument tdoc = null; @@ -125,7 +135,7 @@ namespace A69WD.Site { Console.WriteLine($"A69 - Errore HTTP {(int)resp.StatusCode} {resp.ReasonPhrase} per {lk.URL}"); lk.Status = eLinkState.Error; //App.DB.LinkLog(lk); - return links; + return newlinks; } // Scarico la pagina var thtml = await client.GetStringAsync(lk.URL); @@ -141,24 +151,94 @@ namespace A69WD.Site { #endregion #region ToAnalize - // if (lk.Status == eLinkState.ToAnalize) { - // // Estraggo i link alle altre pagine: - // var tnodes = tdoc.DocumentNode.SelectNodes("//div[@id='pagination']//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=")) - // links.Add(cLink_A69.PageBase + href); - // } - // } - // // Rimuove duplicati e ordina alfabeticamente - // links = links - // .Distinct() // elimina duplicati - // .OrderBy(s => s) // ordina alfabeticamente - // .ToList(); - // } - // lk.Status = eLinkState.End; - // App.DB.LinkLog(lk); + { + if (lk.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 " + lk.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 " + lk.FileName + " - " + ex.Message); } + } + #endregion + + lk.Status = eLinkState.End; + App.DB.LinkLog(lk); + } + } #endregion #region End @@ -181,14 +261,14 @@ namespace A69WD.Site { } #endregion - Console.WriteLine("Link.Count=" + links.Count.ToString("00")); + Console.WriteLine("Link.Count=" + newlinks.Count.ToString("00")); } catch (Exception ex) { Console.WriteLine($"Errore con {lk.URL}: {ex.Message}"); lk.Status = eLinkState.Error; //App.DB.LinkLog(lk); } - return links; + return newlinks; } diff --git a/Src/A69WD/Site/iSiteType.cs b/Src/A69WD/Site/iSiteType.cs index 4d4eec7..737f06d 100644 --- a/Src/A69WD/Site/iSiteType.cs +++ b/Src/A69WD/Site/iSiteType.cs @@ -47,7 +47,7 @@ namespace A69WD.Site { /// /// /// - Task> CrawlPageAsync(iLink lk); + Task> CrawlPageAsync(iLink lk); }//interface iSiteType diff --git a/Src/A69WD/UI/frmDownloader.cs b/Src/A69WD/UI/frmDownloader.cs index 704402f..cb8d4a6 100644 --- a/Src/A69WD/UI/frmDownloader.cs +++ b/Src/A69WD/UI/frmDownloader.cs @@ -14,11 +14,21 @@ using static System.Windows.Forms.LinkLabel; using static System.Windows.Forms.VisualStyles.VisualStyleElement; using A69WD.Site; using A69WD.Net; +using System.Collections.Concurrent; +using System.Xml.Linq; +using A69WD.Service; namespace A69WD { public partial class frmDownloader : Form { + // Coda thread-safe di URL da scaricare + private readonly BlockingCollection _urlQueue = new BlockingCollection(new ConcurrentQueue()); + + // Semaforo per limitare il numero massimo di download simultanei + private readonly SemaphoreSlim _semaphore = new SemaphoreSlim(1); + + public frmDownloader() { InitializeComponent(); @@ -52,20 +62,45 @@ namespace A69WD { A69WD.Site.iSiteType siteA69 = new cSite_A69(); // - Eseguo l'autenticazione bool res = await siteA69.Authenticate(); - lblST.Text = DateTime.Now.ToString() + " - sito loggato: " + res.ToString(); - // - Verifico directory pe ri download - if (!System.IO.Directory.Exists(Cfg.Settings.PathDownloadWorking)) { System.IO.Directory.CreateDirectory(Cfg.Settings.PathDownloadWorking); } - // - Test di download di un link di indice - iLink lk = new cLink_A69("https://www.annunci69.it/racconti-erotici/altro.html", "Idx_altro_01.html"); - await siteA69.CrawlPageAsync(lk); - // - Test di download di un link di racconto - lk = new cLink_A69("https://www.annunci69.it/racconti-erotici/altro/Oltre-il-desiderio_166769.html", "Rac_0001.html"); - await siteA69.CrawlPageAsync(lk); - // - Test di download di un link protetto - lk = new cLink_A69("https://www.annunci69.it/racconti-erotici/racconti_lista.php?genere=altro&pagina=11", "Idx_altro_11.html"); - await siteA69.CrawlPageAsync(lk); + if (res) { + lblST.Text = DateTime.Now.ToString() + " - sito loggato: " + res.ToString(); + Logger.Information("Sito correttamente loggato."); + } else { + Logger.Information("Errore di autenticazione al sito."); + return; + } + // - Verifico path per il download + if (!System.IO.Directory.Exists(Cfg.Settings.PathDownloadWorking)) System.IO.Directory.CreateDirectory(Cfg.Settings.PathDownloadWorking); + // // Test downloadando tutti i 10 primi link per categoria + // List links = cLink_A69.GetStartLink(); + // int i = 1; + // foreach (iLink link in links) { + // lblST.Text = "Start url " + link.FileName; + // Logger.Information("Start url " + link.FileName); + // await siteA69.CrawlPageAsync(link); + // Logger.Information(" end url " + link.FileName); + // i++; + // } + + // Inserisco i link iniziali sulla coda dei download: + if (!cLink_A69.InsertStartLink(_urlQueue)) { + lblST.Text = "Inserimento A69 start link fault."; + return; + } + lblST.Text = "In coda vi sono: " + _urlQueue.Count.ToString() + " url."; + + // Test estrazione link dal primo indice: + iLink link = new cLink_A69("https://www.annunci69.it/racconti-erotici/racconti_lista.php?genere=altro&pagina=1", "Idx_altro_1.html"); + Logger.Information("Start url " + link.FileName); + List urls = await siteA69.CrawlPageAsync(link); + Logger.Information(" end url " + link.FileName + " - URL trovati: " + urls.Count.ToString("000")); + foreach (iLink url in urls) { _urlQueue.Add(url); } + lblST.Text = "In coda vi sono: " + _urlQueue.Count.ToString() + " url."; + } + private Service_Crawler service = null; + private void btnShowLog_Click(object sender, EventArgs e) { cTools.ShowOnNotepad(Cfg.Settings.PathFileLogs);