399 lines
21 KiB
C#
399 lines
21 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Globalization;
|
|
using System.IO;
|
|
using System.Linq;
|
|
using System.Text;
|
|
using System.Text.RegularExpressions;
|
|
using System.Threading;
|
|
using System.Threading.Tasks;
|
|
using System.Windows.Forms;
|
|
using A69WD.Data;
|
|
using A69WD.Net;
|
|
using HtmlAgilityPack;
|
|
|
|
|
|
|
|
namespace A69WD.Service {
|
|
|
|
|
|
public class Service_Import_Milu {
|
|
|
|
// Thread worker che consuma la coda
|
|
private Thread _workerThread;
|
|
private readonly StatusStrip _sb;
|
|
private string _path;
|
|
// Contatori
|
|
private int _totali = 0;
|
|
private int _completati = 0;
|
|
private int _duplicati = 0;
|
|
private int _errori = 0;
|
|
|
|
public Service_Import_Milu(StatusStrip sb) {
|
|
_sb = sb;
|
|
}
|
|
|
|
public void StartWorker(string path) {
|
|
_path = path;
|
|
_workerThread = new Thread(ProcessWorking) { IsBackground = true };
|
|
_workerThread.Start();
|
|
}
|
|
|
|
public void StopWorker() {
|
|
;
|
|
}
|
|
|
|
|
|
|
|
private async void ProcessWorking() {
|
|
// Seleziono 1000 file in modo casuale dalla directory indicata:
|
|
// - Ottieni tutti i file nel percorso
|
|
string[] files = System.IO.Directory.GetFiles(Cfg.Settings.PathImport, "*chapter=all");
|
|
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<string> (opzionale, ma utile)
|
|
|
|
// aggiorno contatori
|
|
_totali = fileCasuali.Count;
|
|
_completati = 0;
|
|
_duplicati = 0;
|
|
_errori = 0;
|
|
UpdateCounters();
|
|
|
|
// Verifico cartelle di destinazione
|
|
string pathExport = System.IO.Path.Combine(Cfg.Settings.PathApplicationRoot, "Export");
|
|
if (!Directory.Exists(pathExport)) Directory.CreateDirectory(pathExport);
|
|
if (!Directory.Exists(Cfg.Settings.PathSrcDone)) Directory.CreateDirectory(Cfg.Settings.PathSrcDone);
|
|
if (!Directory.Exists(Cfg.Settings.PathSrcErrOnExtract)) Directory.CreateDirectory(Cfg.Settings.PathSrcErrOnExtract);
|
|
if (!Directory.Exists(Cfg.Settings.PathSrcErrOnSave)) Directory.CreateDirectory(Cfg.Settings.PathSrcErrOnSave);
|
|
|
|
// Ciclo su ciascun file
|
|
foreach (string file in fileCasuali) {
|
|
string name = System.IO.Path.GetFileName(file);
|
|
Logger.Warning($"Import - Start estrazione racconto: {name}");
|
|
|
|
List<cRac> racs = await EstraiRacs(file);
|
|
if (racs != null) {
|
|
bool saveErr = false;
|
|
foreach (cRac rac in racs) {
|
|
if (rac.Validate()) {
|
|
// racconto valido:
|
|
// // - lo esporto come xml
|
|
// string nam = $"Milu_Rac_{rac.strIDrac}.xml";
|
|
// rac.SaveToXmlFile(System.IO.Path.Combine(pathExport, nam));
|
|
// - lo salvo sul DB
|
|
int res = App.DB.ImportRac(rac); // res=id del racconto nel DB
|
|
if (res < 1) {
|
|
// errore nel salvataggio
|
|
saveErr = true;
|
|
break;
|
|
} else {
|
|
// salvataggio ok
|
|
//saveErr = false;
|
|
}
|
|
} else {
|
|
Logger.Warning($"Import - estrazione errata: {name}");
|
|
saveErr = true;
|
|
break;
|
|
}
|
|
}
|
|
if (saveErr) {
|
|
// errore nel salvataggio dei racconti estratti dal file
|
|
Logger.Warning($"Import - errore nel salvataggio: {name}");
|
|
Interlocked.Increment(ref _errori);
|
|
// - Sposto il file
|
|
System.IO.File.Move(file, System.IO.Path.Combine(Cfg.Settings.PathSrcErrOnSave, System.IO.Path.GetFileName(file)));
|
|
} else {
|
|
// salvataggio ok
|
|
Logger.Warning($"Import - racconti salvati: {name}");
|
|
Interlocked.Increment(ref _completati);
|
|
// - Sposto il file
|
|
System.IO.File.Move(file, System.IO.Path.Combine(Cfg.Settings.PathSrcDone, System.IO.Path.GetFileName(file)));
|
|
}
|
|
} else {
|
|
Logger.Warning($"Import - estrazione errata: {name}");
|
|
Interlocked.Increment(ref _errori);
|
|
// - Sposto il file
|
|
System.IO.File.Move(file, System.IO.Path.Combine(Cfg.Settings.PathSrcErrOnExtract, System.IO.Path.GetFileName(file)));
|
|
}
|
|
UpdateCounters();
|
|
}
|
|
UpdateCounters();
|
|
}
|
|
|
|
private void UpdateCounters() {
|
|
if (_sb.InvokeRequired) {
|
|
_sb.Invoke(new Action(UpdateCounters));
|
|
return;
|
|
}
|
|
|
|
int daFare = _totali - _completati - _errori;
|
|
|
|
_sb.Items["sbStatusLabel"].Text = $"{_totali}|{_completati}|{_errori}";
|
|
_sb.Items["sbStatusLabel"].ToolTipText = "Totali | Completati | Error";
|
|
|
|
(_sb.Items["sbProgressBar"] as ToolStripProgressBar).Maximum = _totali > 0 ? _totali : 1;
|
|
(_sb.Items["sbProgressBar"] as ToolStripProgressBar).Value = _completati + _errori;
|
|
}
|
|
|
|
|
|
|
|
public async Task<List<cRac>> EstraiRacs(string FileFullPath) {
|
|
/*
|
|
* Analisi file dei racconti Milu con il vecchio formato:
|
|
*
|
|
* - I file con un racconto valido hanno la seguente struttura:
|
|
*
|
|
* {...}
|
|
* <div id="pagetitle">
|
|
* <a href="viewstory.php?sid={IDrac}">{Titolo}</a>
|
|
* by
|
|
* <a href="viewuser.php?uid={IDaut}">{Autor}</a>
|
|
* </div>
|
|
* <div class="infobox">
|
|
* <div class="content">
|
|
* <span class="label">Summary: </span>{Riassunto}<br />
|
|
* <span class="label">Categories:</span>
|
|
* <a href='browse.php?type=categories&catid={IDcat}'>{categoria}</a>
|
|
* <span class="label">Characters: </span> {Characters}<br />
|
|
* <span class='label'>Genres: </span> {Genres}<br />
|
|
* <span class='label'>Warnings: </span> {Warnings}<br />
|
|
* <span class="label">Challenges:</span> {Challenges}<br />
|
|
* <span class="label">Series:</span> {Series}<br />
|
|
* <span class="label">Chapters: </span> {Chapters}
|
|
* <span class="label">Completed:</span> {Completed}
|
|
* <span class="label">Word count:</span> {WordCount}
|
|
* <span class="label">Read:</span> {Read}
|
|
* <span class="label"> Published: </span>{Published}
|
|
* <span class="label">Updated:</span> }Updated}
|
|
* </div>
|
|
* </div>
|
|
* <p><b>1.</b> <a href='#1'>{Tit1}</a> by {Autor}</p>
|
|
* <p><b>2.</b> <a href='#2'>{Tit2}</a> by {Autor}</p>
|
|
* <p><b>3.</b> <a href='#3'>{Tit3}</a> by {Autor}</p>
|
|
* <div class='chaptertitle'><a name='1'></a>{Tit1} by {Autor}</div>
|
|
* <div class='chapter'>{Trama1}</div>
|
|
* <div class='toplink'><a href='#1'>Torna all'indice</a></div>
|
|
* <div class='chaptertitle'><a name='2'></a>{Tit2} by {Autor}</div>
|
|
* <div class='chapter'>{Trama2}</div>
|
|
* <div class='toplink'><a href='#1'>Torna all'indice</a></div>
|
|
* <div class='chaptertitle'><a name='3'></a>{Tit3} by {Autor}</div>
|
|
* <div class='chapter'>{Trama3}</div>
|
|
* <div class='toplink'><a href='#1'>Torna all'indice</a></div>
|
|
* {...}
|
|
* <div id="archivedat">Questo racconto è attualmente archiviato su <a href="{URLrac}">{URLrac}</a></div>
|
|
*
|
|
* Quindi per ricavare i dati del racconto:
|
|
* - Ricavo tag pagetitle
|
|
* - Se pagetitle vuoto o size<=2kb racconto nullo, inserisco log e proseguo
|
|
* - Se pagetitle non nullo allora ho un racconto valido e proseguo l'estrazione.
|
|
* - Da pagetitle ricavo il titolo del racconto.
|
|
* - dal testo del tag infobox.content ricavo filtrando i vari parametri del racconto
|
|
* - Dall'elenco dei tag chaptertitle ricavo i sottotitoli dei singoli racconti ATDB
|
|
* - Dall'elenco dei tag chapter ricavo le trame dei singoli racconti.
|
|
* - Occorre verificare per bene che l'ordine venga rispettato in modo da ordinarli correttamente.
|
|
* - Infine dal tag archivedat.a ricavo l'URL del racconto per verifica che sia come il link.
|
|
* - Ricavati tutti i dati inserisco sul DB tutti i singoli capitoli come singoli racconti ATDB assegnando le parti comuni a tutti i racconti.
|
|
*
|
|
*
|
|
*/
|
|
cRac rac = new cRac();
|
|
List<cRac> racs = new List<cRac>();
|
|
HtmlNodeCollection nodes;
|
|
try {
|
|
Console.WriteLine($"Old Milu - Importo: {FileFullPath}");
|
|
|
|
string filePathName = System.IO.Path.GetFileName(FileFullPath);
|
|
|
|
// Ricavo il nodo radice del racconto:
|
|
HtmlAgilityPack.HtmlDocument tdoc = new HtmlAgilityPack.HtmlDocument();
|
|
tdoc.Load(FileFullPath);
|
|
HtmlNode root = tdoc.DocumentNode;
|
|
if (root == null) {
|
|
// racconto non presente
|
|
return null;
|
|
}
|
|
|
|
|
|
List<string> origini = null;
|
|
List<string> tags = null;
|
|
List<cNota> notes = null;
|
|
string titolo = string.Empty;
|
|
string autore = string.Empty;
|
|
DateTime creato = DateTime.Now;
|
|
string infoCategories = string.Empty;
|
|
string infoPublished = string.Empty;
|
|
string infoCharacters = string.Empty;
|
|
string infoGenres = string.Empty;
|
|
string infoWarnings = string.Empty;
|
|
string infoChallenges = string.Empty;
|
|
string infoSeries = string.Empty;
|
|
string infoChapters = string.Empty;
|
|
string infoCompleted = string.Empty;
|
|
string infoWordCount = string.Empty;
|
|
string infoRead = string.Empty;
|
|
#region Info comuni a tutti i capitoli del racconto:
|
|
|
|
#region Origine
|
|
// Origine fissa:
|
|
origini = new List<string> { "Old Milu" };
|
|
#endregion
|
|
|
|
#region Titolo & Autore
|
|
// Titolo e autore si ricavano assieme: <div id="pagetitle"><a href="viewstory.php?sid={IDrac}">{Titolo}</a> by <a href="viewuser.php?uid={IDaut}">{Autor}</a></div>
|
|
try {
|
|
nodes = root.SelectNodes("//div[@id='pagetitle']//a[@href]");
|
|
foreach (HtmlNode nd in nodes) {
|
|
string href = nd.Attributes["href"].Value;
|
|
if (href.StartsWith("viewstory.php?sid=")) {
|
|
titolo = cTools.FormattaTesto(nd.InnerText.Trim(), false);
|
|
} else {
|
|
if (href.StartsWith("viewuser.php?uid=")) {
|
|
autore = cTools.FormattaTesto(nd.InnerText.Trim(), false);
|
|
}
|
|
}
|
|
}
|
|
if (string.IsNullOrEmpty(titolo)) {
|
|
Logger.Error($"Old Milu - Import - No Title - {filePathName}");
|
|
return null;
|
|
} else {
|
|
if (string.IsNullOrEmpty(autore)) {
|
|
Logger.Error($"Old Milu - Import - No Autor - {filePathName}");
|
|
return null;
|
|
}
|
|
}
|
|
} catch (Exception ex) { Logger.Error(string.Format("Fallita estrazione titolo/autore dal file {0}\r\n{1}", filePathName, ex.Message)); return null; }
|
|
#endregion
|
|
|
|
#region Tags: --> Testo dei nodi <div class="infobox">< div class="content"><a href='browse.php?type=categories&catid={IDcat}'>{categoria}</a>
|
|
try {
|
|
nodes = root.SelectNodes("//div[@class='infobox']//div[@class='content']//a");
|
|
List<string> items = new List<string>();
|
|
foreach (HtmlNode nd in nodes) {
|
|
string href = nd.Attributes["href"].Value;
|
|
if (href.StartsWith("browse.php?type=categories")) {
|
|
items.Add(cTools.FormattaTesto(nd.InnerText.Replace("genere", "").Trim(), false));
|
|
}
|
|
}
|
|
//items = items
|
|
// .OrderBy(s => s) // ordina alfabeticamente
|
|
// .Distinct() // elimina duplicati
|
|
// .ToList();
|
|
cTools.Insert_Format_Tags(ref tags, items);
|
|
if (tags.Count<1) {
|
|
Logger.Error($"Old Milu - Import - No Tags - {filePathName}");
|
|
return null;
|
|
}
|
|
} catch (Exception ex) { Logger.Warning(string.Format("Fallita estrazione Tags dal file {0}\r\n{1}", filePathName, ex.Message)); return null; }
|
|
#endregion
|
|
|
|
#region Creato & Notes
|
|
// Estraggo le informazioni generali
|
|
// Data estreatta dal testo del nodo <div class="infobox">< div class="content"> fra "Published{Published}Updated:
|
|
try {
|
|
HtmlNode nd = root.SelectSingleNode("//div[@class='infobox']//div[@class='content']");
|
|
string txt = nd.InnerText.Trim().Replace(" ", "");
|
|
infoPublished = TxtExtract(txt, "Published: ", "Updated: ");
|
|
infoCategories = TxtExtract(txt, "Categories:", "Characters:");
|
|
infoCharacters = TxtExtract(txt, "Characters:", "Genres:");
|
|
infoGenres = TxtExtract(txt, "Genres:", "Warnings:");
|
|
infoWarnings = TxtExtract(txt, "Warnings:", "Challenges:");
|
|
infoChallenges = TxtExtract(txt, "Challenges:", "Series:");
|
|
infoSeries = TxtExtract(txt, "Series:", "Chapters");
|
|
infoChapters = TxtExtract(txt, "Chapters:", "Completed");
|
|
infoCompleted = TxtExtract(txt, "Completed:", "Word count:");
|
|
infoWordCount = TxtExtract(txt, "Word count:", "Read:");
|
|
infoRead = TxtExtract(txt, "Read:", "Published:");
|
|
} catch (Exception ex) { Logger.Warning(string.Format("Informazioni generali non riconosciute nel file {0}\r\n{1}", filePathName, ex.Message)); return null; }
|
|
// Data del racconto
|
|
try {
|
|
CultureInfo culture = CultureInfo.CreateSpecificCulture("it-IT");
|
|
DateTimeStyles styles = DateTimeStyles.None;
|
|
if (!DateTime.TryParse(infoPublished.Trim(), culture, styles, out creato)) {
|
|
Logger.Error($"Old Milu - Import - No data - {filePathName}");
|
|
}
|
|
} catch (Exception ex) { Logger.Warning(string.Format("Data non riconosciuta nel file {0}\r\n{1}", filePathName, ex.Message)); return null; }
|
|
|
|
// Notes (comuni fra tutti i racconti):
|
|
try {
|
|
notes = new List<Data.cNota>();
|
|
notes.Add(new Data.cNota("SrcFile", System.IO.Path.GetFileName(filePathName)));
|
|
notes.Add(new Data.cNota("infoCategories", infoCategories));
|
|
notes.Add(new Data.cNota("infoCharacters", infoCharacters));
|
|
notes.Add(new Data.cNota("infoGenres", infoGenres));
|
|
notes.Add(new Data.cNota("infoWarnings", infoWarnings));
|
|
notes.Add(new Data.cNota("infoChallenges", infoChallenges));
|
|
notes.Add(new Data.cNota("infoSeries", infoSeries));
|
|
notes.Add(new Data.cNota("infoChapters", infoChapters));
|
|
notes.Add(new Data.cNota("infoCompleted", infoCompleted));
|
|
notes.Add(new Data.cNota("infoWordCount", infoWordCount));
|
|
notes.Add(new Data.cNota("infoRead", infoRead));
|
|
notes.Add(new Data.cNota("infoPublished", infoPublished));
|
|
} catch (Exception ex) { Logger.Warning(string.Format("Notes non riconosciute nel file {0}\r\n{1}", filePathName, ex.Message)); return null; }
|
|
#endregion
|
|
|
|
#endregion
|
|
|
|
|
|
// I vari capitoli dei racconti sono descritti dai nodi <div class='chaptertitle'> e <div class='chapter'> in successione.
|
|
try {
|
|
HtmlNodeCollection capTitles = root.SelectNodes("//div[@class='chaptertitle']");
|
|
HtmlNodeCollection capText = root.SelectNodes("//div[@class='chapter']");
|
|
if ((capTitles.Count > 0) && (capTitles.Count == capText.Count)) {
|
|
// Trovato capitoli congruenti
|
|
for (int i = 0; i < capTitles.Count; i++) {
|
|
// Creo i racconti:
|
|
rac = new cRac();
|
|
rac.Title = titolo;
|
|
rac.SubTitle = cTools.FormattaTesto(capTitles[i].InnerText.Trim().Replace($"by {autore}","").Trim(),true);
|
|
rac.Creato = creato;
|
|
rac.Updated = DateTime.Now;
|
|
//rac.Trama = cTools.FormattaTesto(capText[i].InnerHtml.Trim(), false);
|
|
rac.Trama = cTools.FormattaTesto(capText[i].InnerHtml.Trim(), true);
|
|
rac.Voto = "Non Letto";
|
|
rac.Autori = new List<string>();
|
|
rac.Autori.Add(autore);
|
|
rac.Tags = tags;
|
|
rac.Origini = origini;
|
|
rac.Notes = notes;
|
|
// Inserisco il racconto nella collezione
|
|
if (rac.Validate()) { racs.Add(rac); }
|
|
}
|
|
} else {
|
|
// Capitoli non trovati o non congruenti
|
|
Logger.Error($"Old Milu - Import - No Capitoli - {filePathName}");
|
|
return null;
|
|
}
|
|
} catch (Exception ex) { Logger.Warning(string.Format("Fallita estrazione capitolo {0}\r\n{1}", filePathName, ex.Message)); return null; }
|
|
|
|
Logger.Information($"Terminato estrazione racconti dal file: {filePathName}");
|
|
|
|
} catch (Exception ex) {
|
|
Logger.Error($"Old Milu - Import Error - {FileFullPath}: {ex.Message}");
|
|
Console.WriteLine($"Old Milu - Import Error - {FileFullPath}: {ex.Message}");
|
|
return null;
|
|
}
|
|
return racs;
|
|
}
|
|
|
|
|
|
protected string TxtExtract(string src, string start, string end) {
|
|
int nstart = src.LastIndexOf(start);
|
|
int nstop = src.LastIndexOf(end);
|
|
int offset = start.Length;
|
|
return src.Substring(nstart + offset, nstop - nstart - offset).Trim();
|
|
}
|
|
|
|
|
|
}//class Service_Import_Milu
|
|
|
|
}//namespace A69WD.Service
|