Premiere commit - Omika
This commit is contained in:
@@ -0,0 +1,213 @@
|
||||
using Omika.App.Local.Abstractions;
|
||||
using Omika.App.Local.Models;
|
||||
using Omika.Core.Contracts.Mobile.Languages;
|
||||
|
||||
namespace Omika.LanguesProbe;
|
||||
|
||||
/// <summary>
|
||||
/// Magasin de pack de langue adossé à un dossier du disque : c'est ce qui permet
|
||||
/// d'exécuter le <b>vrai</b> pipeline (<c>OnnxTranslationService</c>) sur les
|
||||
/// fichiers d'un développeur, hors téléphone.
|
||||
///
|
||||
/// <para>
|
||||
/// Le dossier passé en ligne de commande est traité comme
|
||||
/// « <c>langues/<code>/<sens></c> » : tous les fichiers
|
||||
/// <c>*.spm</c> et <c>*.onnx</c> qu'il contient sont déclarés pour le sens
|
||||
/// demandé, avec leur taille réelle. Aucune empreinte SHA-256 n'est inventée :
|
||||
/// un pack décrit depuis le disque n'a pas de référence serveur à laquelle se
|
||||
/// comparer, et une fausse empreinte rendrait la vérification mensongère.
|
||||
/// </para>
|
||||
///
|
||||
/// <para>
|
||||
/// <b>Lecture seule.</b> Les méthodes d'écriture de l'interface refusent d'agir
|
||||
/// plutôt que de faire semblant : un outil de diagnostic ne doit jamais
|
||||
/// modifier, ni supprimer, ce qu'un développeur lui demande d'examiner.
|
||||
/// </para>
|
||||
/// </summary>
|
||||
internal sealed class PackLangueDossier : ILanguagePackStore
|
||||
{
|
||||
private readonly string _dossier;
|
||||
private readonly string _code;
|
||||
private readonly string _sens;
|
||||
|
||||
public PackLangueDossier(string dossier, string code, string sens)
|
||||
{
|
||||
_dossier = dossier;
|
||||
_code = code;
|
||||
_sens = sens;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Dossier partagé entre les deux sens d'une même langue
|
||||
/// (« <code>\commun »), frère du dossier examiné. Un modèle familial
|
||||
/// (<c>opus-mt-tc-big-itc-itc</c>) y range ses poids une seule fois, et chaque
|
||||
/// sens ne porte que son <c>modele.json</c>. Absent pour un pack bilingue.
|
||||
/// </summary>
|
||||
private string DossierCommun
|
||||
{
|
||||
get
|
||||
{
|
||||
string? parent = Path.GetDirectoryName(_dossier);
|
||||
return string.IsNullOrEmpty(parent)
|
||||
? string.Empty
|
||||
: Path.Combine(parent, LanguageModelDirections.Commun);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Répertoire racine des packs sur l'appareil — ici, le dossier examiné.</summary>
|
||||
public string Racine => _dossier;
|
||||
|
||||
/// <summary>
|
||||
/// Aucun manifeste serveur n'est disponible hors application : le retour
|
||||
/// <see langword="null"/> est l'état « mémoire d'index absente », déjà géré
|
||||
/// par les appelants.
|
||||
/// </summary>
|
||||
public Task<LanguageManifestResponse?> GetIndexAsync(CancellationToken cancellationToken = default)
|
||||
=> Task.FromResult<LanguageManifestResponse?>(null);
|
||||
|
||||
public Task EnregistrerIndexAsync(LanguageManifestResponse manifeste, CancellationToken cancellationToken = default)
|
||||
=> throw LectureSeule();
|
||||
|
||||
public Task<PackLangueInstalle?> GetPackInstalleAsync(string code, CancellationToken cancellationToken = default)
|
||||
=> Task.FromResult<PackLangueInstalle?>(DecrirePack());
|
||||
|
||||
public Task<IReadOnlyList<PackLangueInstalle>> ListerPacksInstallesAsync(CancellationToken cancellationToken = default)
|
||||
{
|
||||
var packs = new List<PackLangueInstalle>();
|
||||
if (Directory.Exists(_dossier))
|
||||
{
|
||||
packs.Add(DecrirePack());
|
||||
}
|
||||
|
||||
return Task.FromResult<IReadOnlyList<PackLangueInstalle>>(packs);
|
||||
}
|
||||
|
||||
public Task EnregistrerPackAsync(PackLangueInstalle pack, CancellationToken cancellationToken = default)
|
||||
=> throw LectureSeule();
|
||||
|
||||
public Task SupprimerPackAsync(string code, CancellationToken cancellationToken = default)
|
||||
=> throw LectureSeule();
|
||||
|
||||
public string PreparerFichierPartiel(string code, string sens, string nomFichier)
|
||||
=> throw LectureSeule();
|
||||
|
||||
/// <summary>
|
||||
/// Décrit ce qui est <b>réellement présent</b> : c'est cette description qui
|
||||
/// décide des fichiers que le service ouvrira, donc ce que l'outil mesure.
|
||||
/// </summary>
|
||||
public PackLangueInstalle DecrirePack()
|
||||
{
|
||||
var fichiers = new List<FichierModeleInstalle>();
|
||||
|
||||
if (Directory.Exists(_dossier))
|
||||
{
|
||||
// Tri par nom : deux exécutions successives doivent produire exactement le même
|
||||
// inventaire, sans quoi une comparaison entre deux mesures n'aurait aucun sens.
|
||||
string[] chemins = Directory.GetFiles(_dossier);
|
||||
Array.Sort(chemins, StringComparer.Ordinal);
|
||||
|
||||
foreach (string chemin in chemins)
|
||||
{
|
||||
string nom = Path.GetFileName(chemin);
|
||||
if (!nom.EndsWith(".spm", StringComparison.OrdinalIgnoreCase)
|
||||
&& !nom.EndsWith(".onnx", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
fichiers.Add(new FichierModeleInstalle
|
||||
{
|
||||
Sens = _sens,
|
||||
NomFichier = nom,
|
||||
TailleOctets = new FileInfo(chemin).Length
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Poids partagés d'un modèle familial : le service les résout APRÈS le
|
||||
// dossier du sens (voir OnnxTranslationService.FichiersDuSens). Les
|
||||
// omettre ferait déclarer « pack incomplet » un pack familial valide.
|
||||
string commun = DossierCommun;
|
||||
if (commun.Length > 0 && Directory.Exists(commun))
|
||||
{
|
||||
string[] cheminsCommuns = Directory.GetFiles(commun);
|
||||
Array.Sort(cheminsCommuns, StringComparer.Ordinal);
|
||||
|
||||
foreach (string chemin in cheminsCommuns)
|
||||
{
|
||||
string nom = Path.GetFileName(chemin);
|
||||
if (!nom.EndsWith(".spm", StringComparison.OrdinalIgnoreCase)
|
||||
&& !nom.EndsWith(".onnx", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
fichiers.Add(new FichierModeleInstalle
|
||||
{
|
||||
Sens = LanguageModelDirections.Commun,
|
||||
NomFichier = nom,
|
||||
TailleOctets = new FileInfo(chemin).Length
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return new PackLangueInstalle
|
||||
{
|
||||
Code = _code,
|
||||
// Le libellé vient du manifeste serveur, absent ici ; il n'est lu par aucun appel du
|
||||
// pipeline de traduction.
|
||||
Libelle = _code,
|
||||
Version = string.Empty,
|
||||
InstalleLeUtc = DateTimeOffset.UtcNow,
|
||||
Fichiers = fichiers
|
||||
};
|
||||
}
|
||||
|
||||
public bool FichierPresent(string code, FichierModeleInstalle fichier)
|
||||
=> File.Exists(CheminFichier(code, fichier.Sens, fichier.NomFichier));
|
||||
|
||||
/// <summary>
|
||||
/// Vérifie la présence et la taille relevée à l'inventaire. L'empreinte
|
||||
/// SHA-256 n'est pas recalculée : le pack étant décrit depuis le disque
|
||||
/// lui-même, il n'existe aucune empreinte attendue à confronter.
|
||||
/// </summary>
|
||||
public Task<bool> VerifierFichierAsync(
|
||||
string code, FichierModeleInstalle fichier, CancellationToken cancellationToken = default)
|
||||
{
|
||||
var info = new FileInfo(CheminFichier(code, fichier.Sens, fichier.NomFichier));
|
||||
bool conforme = info.Exists && (fichier.TailleOctets <= 0 || info.Length == fichier.TailleOctets);
|
||||
return Task.FromResult(conforme);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Chemin réel d'un fichier du pack. Le nom est réduit à son nom simple,
|
||||
/// comme dans <c>LanguagePaths</c> : un nom de fichier ne doit jamais
|
||||
/// permettre de sortir du dossier examiné. Un fichier absent du dossier du
|
||||
/// sens est cherché dans son frère <c>commun</c> (poids partagé d'un modèle
|
||||
/// familial), en suivant exactement la priorité du service.
|
||||
/// </summary>
|
||||
public string CheminFichier(string code, string sens, string nomFichier)
|
||||
{
|
||||
string nom = Path.GetFileName(nomFichier);
|
||||
string chemin = Path.Combine(_dossier, nom);
|
||||
if (File.Exists(chemin))
|
||||
{
|
||||
return chemin;
|
||||
}
|
||||
|
||||
string commun = DossierCommun;
|
||||
if (commun.Length > 0)
|
||||
{
|
||||
string partage = Path.Combine(commun, nom);
|
||||
if (File.Exists(partage))
|
||||
{
|
||||
return partage;
|
||||
}
|
||||
}
|
||||
|
||||
return chemin;
|
||||
}
|
||||
|
||||
private static NotSupportedException LectureSeule()
|
||||
=> new("Cet outil de diagnostic ne modifie jamais le dossier examiné.");
|
||||
}
|
||||
Reference in New Issue
Block a user