Come generare nomi di file univoci in C #


131

Ho implementato un algoritmo che genererà nomi univoci per i file che verranno salvati sul disco rigido. Sto aggiungendo DateTime: ore, minuti, secondi e millisecondi, ma genera comunque un nome duplicato di file perché sto caricando più file alla volta.

Qual è la soluzione migliore per generare nomi univoci per i file da archiviare sul disco rigido in modo che non ci siano 2 file uguali?


Dipende da altri requisiti; questa [vecchia] domanda era / è troppo vaga.
user2864740

Risposte:


240

Se la leggibilità non ha importanza, utilizzare i GUID .

Per esempio:

var myUniqueFileName = string.Format(@"{0}.txt", Guid.NewGuid());

o più corto :

var myUniqueFileName = $@"{Guid.NewGuid()}.txt";

Nei miei programmi, a volte provo, ad esempio, 10 volte a generare un nome leggibile ("Image1.png" ... "Image10.png") e se fallisce (perché il file esiste già), torno ai GUID.

Aggiornare:

Di recente, ho usato anche al DateTime.Now.Ticksposto dei GUID:

var myUniqueFileName = string.Format(@"{0}.txt", DateTime.Now.Ticks);

o

var myUniqueFileName = $@"{DateTime.Now.Ticks}.txt";

Il vantaggio per me è che questo genera un nome file più breve e "più bello", rispetto ai GUID.

Si noti che in alcuni casi (ad esempio quando si generano molti nomi casuali in un tempo molto breve), ciò potrebbe accadere creare valori non univoci.

Attenersi ai GUID se si desidera assicurarsi davvero che i nomi dei file siano univoci, anche durante il trasferimento su altri computer.


7
Mi piace usare le zecche come GUID è davvero brutto. Puoi anche ottenere un hash dei tick che riduce la lunghezza dei caratteri del nome file. DateTime.Now.Ticks.GetHashCode().ToString("x").ToUpper()
WillMcKill

4
I "tick" sono prevedibili e non sicuri per i thread (poiché gli stessi "tick" possono essere ottenuti da più thread / processi). Ciò non lo rende adatto alla generazione di nomi di file temporanei. La generazione di X..1..N può essere adatta per attività rivolte all'utente (ad es. Copia in Explorer), ma è dubbia per il lavoro del server.
user2864740

90

Uso

Path.GetTempFileName()

o usa il nuovo GUID ().

Path.GetTempFilename () su MSDN .


Ecco il link al documento MSDN: msdn.microsoft.com/en-us/library/...
epotter

3
Nota, ciò GetTempFileName()potrebbe generare un'eccezione se crei molti di questi file senza eliminarli.
Joey,

21
"Il metodo GetTempFileName genererà una IOException se viene utilizzato per creare più di 65535 file senza eliminare i file temporanei precedenti." dice l'articolo MSDN.
Çağdaş Tekin,

1
ATTENZIONE: GetTempFileNamesarà creare un file. Questo significa anche che sceglie la posizione del percorso temporaneo . D'altra parte, GetRandomFileNameè adatto per generare un nome di file 8.3 che può essere utilizzato con un percorso diverso. (Ho visto un codice orribile che utilizza GetTempFileName con un File.Delete solo per usare il nome del file altrove ..)
user2864740


54

Se la leggibilità del nome del file non è importante, lo farà il GUID, come suggerito da molti. Tuttavia, trovo che guardare in una directory con 1000 nomi di file GUID sia molto scoraggiante da ordinare. Quindi di solito uso una combinazione di una stringa statica che fornisce al nome del file alcune informazioni di contesto, un timestamp e GUID.

Per esempio:

public string GenerateFileName(string context)
{
    return context + "_" + DateTime.Now.ToString("yyyyMMddHHmmssfff") + "_" + Guid.NewGuid().ToString("N");
}

filename1 = GenerateFileName("MeasurementData");
filename2 = GenerateFileName("Image");

In questo modo, quando ordino per nome file, raggrupperà automaticamente i file in base alla stringa di contesto e li ordinerà in base al timestamp.

Si noti che il limite del nome file in Windows è di 255 caratteri.


1
+1 Per il suggerimento di includere informazioni utili combinate con un GUID. - Un accantonamento da prendere con un granello di sale: includere la data e l'ora in un nome di file è in qualche modo ridondante quando puoi Right Click > Sort By > Date.
Timothy Shields,

1
Il tempo diventa utile se si memorizzano un mucchio di file con contesti diversi nella stessa directory. Naturalmente, la generazione del nome del file dovrebbe essere regolata in base alle tue esigenze specifiche.
Mas

Dovrebbe essere Guid.NewGuid().ToString();. Parentesi mancante. +1 altrimenti
Laurent W.

Questo è molto elegante. Data e ora e guida. +1
JoshYates1980,

Mi piace questa soluzione +1, ho aggiunto una seconda estensione della stringa di parametri che aggiungo al fileName, questo supporta ulteriormente l'idea di contesto e consente di aprire facilmente il file con l'applicazione predefinita con un doppio clic, se necessario
shelbypereira

23

Ecco un algoritmo che restituisce un nome file leggibile univoco basato sull'originale fornito. Se il file originale esiste, tenta in modo incrementale di aggiungere un indice al nome file fino a quando non ne trova uno che non esiste. Legge i nomi di file esistenti in un HashSet per verificare le collisioni, quindi è abbastanza veloce (alcune centinaia di nomi di file al secondo sulla mia macchina), è anche sicuro per i thread e non soffre delle condizioni di gara.

Ad esempio, se lo passi test.txt, tenterà di creare file in questo ordine:

test.txt
test (2).txt
test (3).txt

ecc. È possibile specificare i tentativi massimi o lasciarlo semplicemente sul valore predefinito.

Ecco un esempio completo:

class Program
{
    static FileStream CreateFileWithUniqueName(string folder, string fileName, 
        int maxAttempts = 1024)
    {
        // get filename base and extension
        var fileBase = Path.GetFileNameWithoutExtension(fileName);
        var ext = Path.GetExtension(fileName);
        // build hash set of filenames for performance
        var files = new HashSet<string>(Directory.GetFiles(folder));

        for (var index = 0; index < maxAttempts; index++)
        {
            // first try with the original filename, else try incrementally adding an index
            var name = (index == 0)
                ? fileName
                : String.Format("{0} ({1}){2}", fileBase, index, ext);

            // check if exists
            var fullPath = Path.Combine(folder, name);
            if(files.Contains(fullPath))
                continue;

            // try to create the file
            try
            {
                return new FileStream(fullPath, FileMode.CreateNew, FileAccess.Write);
            }
            catch (DirectoryNotFoundException) { throw; }
            catch (DriveNotFoundException) { throw; }
            catch (IOException) 
            {
                // Will occur if another thread created a file with this 
                // name since we created the HashSet. Ignore this and just
                // try with the next filename.
            } 
        }

        throw new Exception("Could not create unique filename in " + maxAttempts + " attempts");
    }

    static void Main(string[] args)
    {
        for (var i = 0; i < 500; i++)
        {
            using (var stream = CreateFileWithUniqueName(@"c:\temp\", "test.txt"))
            {
                Console.WriteLine("Created \"" + stream.Name + "\"");
            }
        }

        Console.ReadKey();
    }
}

thread-safe ? Nonstatic readonly variabililock?
Kiquenet,

Il metodo stesso è statico, quindi non condivide nulla, quindi credo che più thread possano tranquillamente inserire questo metodo contemporaneamente. Forse thread-safe non è proprio il termine corretto: sto cercando di comunicare che se un altro thread / processo crea un file con un nome in conflitto durante l'esecuzione, questo metodo recupera e prova il prossimo nome disponibile. Sentiti libero di modificare se pensi che possa essere migliorato.
Mike Chamberlain,

Forse "non soffrire di una condizione di razza" è un modo migliore per dirlo.
Mike Chamberlain,

10

Uso GetRandomFileName :

Il metodo GetRandomFileName restituisce una stringa casuale crittograficamente potente che può essere utilizzata come nome di cartella o nome di file. A differenza di GetTempFileName, GetRandomFileName non crea un file. Quando la sicurezza del file system è fondamentale, questo metodo dovrebbe essere usato al posto di GetTempFileName.

Esempio:

public static string GenerateFileName(string extension="")
{
    return string.Concat(Path.GetRandomFileName().Replace(".", ""),
        (!string.IsNullOrEmpty(extension)) ? (extension.StartsWith(".") ? extension : string.Concat(".", extension)) : "");
}

Il metodo GetRandomFileName () genera sempre il nome file univoco ogni volta simile a GUID ()?
Ashish Shukla,

1
@AshishShukla in realtà non ne ho idea. msdn dice che viene generata "una stringa casuale crittograficamente forte". Finora non ho avuto problemi. Se l'unicità è fondamentale, un controllo extra potrebbe essere una buona idea.
Koray,

3
  1. Crea il tuo nome file timestamp seguendo il tuo normale processo
  2. Controlla se esiste il nome file
  3. False: salva il file
  4. Vero - Aggiungi carattere aggiuntivo al file, forse un contatore
  5. Vai al passaggio 2

10
Questo algoritmo è votato alla concorrenza
Jader Dias,

3

Puoi avere un nome file univoco generato automaticamente per te senza alcun metodo personalizzato. Utilizzare quanto segue con la classe StorageFolder o la classe StorageFile . La chiave qui è: CreationCollisionOption.GenerateUniqueNameeNameCollisionOption.GenerateUniqueName

Per creare un nuovo file con un nome file univoco:

var myFile = await ApplicationData.Current.LocalFolder.CreateFileAsync("myfile.txt", NameCollisionOption.GenerateUniqueName);

Per copiare un file in una posizione con un nome file univoco:

var myFile2 = await myFile1.CopyAsync(ApplicationData.Current.LocalFolder, myFile1.Name, NameCollisionOption.GenerateUniqueName);

Per spostare un file con un nome file univoco nella posizione di destinazione:

await myFile.MoveAsync(ApplicationData.Current.LocalFolder, myFile.Name, NameCollisionOption.GenerateUniqueName);

Per rinominare un file con un nome file univoco nella posizione di destinazione:

await myFile.RenameAsync(myFile.Name, NameCollisionOption.GenerateUniqueName);

2

Ho usato il seguente codice e funziona bene. Spero che questo possa aiutarti.

Comincio con un nome file univoco usando un timestamp -

"context_" + DateTime.Now.ToString ("yyyyMMddHHmmssffff")

Codice C # -

public static string CreateUniqueFile(string logFilePath, string logFileName, string fileExt)
    {
        try
        {
            int fileNumber = 1;

            //prefix with . if not already provided
            fileExt = (!fileExt.StartsWith(".")) ? "." + fileExt : fileExt;

            //Generate new name
            while (File.Exists(Path.Combine(logFilePath, logFileName + "-" + fileNumber.ToString() + fileExt)))
                fileNumber++;

            //Create empty file, retry until one is created
            while (!CreateNewLogfile(logFilePath, logFileName + "-" + fileNumber.ToString() + fileExt))
                fileNumber++;

            return logFileName + "-" + fileNumber.ToString() + fileExt;
        }
        catch (Exception)
        {
            throw;
        }
    }

    private static bool CreateNewLogfile(string logFilePath, string logFile)
    {
        try
        {
            FileStream fs = new FileStream(Path.Combine(logFilePath, logFile), FileMode.CreateNew);
            fs.Close();
            return true;
        }
        catch (IOException)   //File exists, can not create new
        {
            return false;
        }
        catch (Exception)     //Exception occured
        {
            throw;
        }
    }

1

Ti serve la data e l'ora nel nome del file?

È possibile rendere il nome file un GUID.


@downvoter Qual è il motivo del voto negativo? Un GUID per un nome file sembra essere una risposta popolare a questa domanda.
Larry Hipp,

È una risposta ripetuta e non ho abbastanza reputazione da sottovalutare
Jader Dias l'

@XMLforDummies la mia risposta è stata una delle prime. Potrebbe non sembrare adesso perché mostra solo le ore ormai. È una risposta ripetuta perché probabilmente è la risposta corretta.
Larry Hipp,


1

Che ne dite di usare Guid.NewGuid()per creare un GUID e usarlo come nome del file (o parte del nome del file insieme al timestamp, se lo desideri).


1

Ho scritto una semplice funzione ricorsiva che genera nomi di file come fa Windows, aggiungendo un numero di sequenza prima dell'estensione del file.

Dato un percorso di file desiderato C:\MyDir\MyFile.txte il file esiste già, restituisce un percorso di file finale diC:\MyDir\MyFile_1.txt .

Si chiama così:

var desiredPath = @"C:\MyDir\MyFile.txt";
var finalPath = UniqueFileName(desiredPath);

private static string UniqueFileName(string path, int count = 0)
{
    if (count == 0)
    {
        if (!File.Exists(path))
        {
            return path;
        }
    }
    else
    {
        var candidatePath = string.Format(
            @"{0}\{1}_{2}{3}",
            Path.GetDirectoryName(path),
            Path.GetFileNameWithoutExtension(path),
            count,
            Path.GetExtension(path));

        if (!File.Exists(candidatePath))
        {
            return candidatePath;
        }
    }

    count++;
    return UniqueFileName(path, count);
}

Questo non è thread-safe o process-safe. Esiste una condizione di competizione con il controllo File.Exists e qualsiasi creazione (in seguito supposta) del file. In pratica, quando viene chiamato due volte di seguito senza creare un file, verrà restituito lo stesso risultato.
user2864740

1

Perché non possiamo creare un ID unico come di seguito.

Possiamo usare DateTime.Now.Ticks e Guid.NewGuid (). ToString () per combinare insieme e creare un ID unico.

Con l'aggiunta di DateTime.Now.Ticks, possiamo scoprire la data e l'ora in secondi in cui viene creato l'id univoco.

Si prega di consultare il codice.

var ticks = DateTime.Now.Ticks;
var guid = Guid.NewGuid().ToString();
var uniqueSessionId = ticks.ToString() +'-'+ guid; //guid created by combining ticks and guid

var datetime = new DateTime(ticks);//for checking purpose
var datetimenow = DateTime.Now;    //both these date times are different.

Possiamo anche prendere la parte delle zecche in un ID unico e controllare la data e l'ora in seguito per riferimento futuro.

È possibile allegare l'id univoco creato al nome file o può essere utilizzato per creare un ID sessione univoco per il logout degli utenti alla nostra applicazione o al nostro sito Web.


Perché preoccuparsi di "tick" se esiste un GUID?
user2864740

In qualsiasi scenario, se è necessario verificare quando viene generato uniqueSessionId, si otterrà l'ora esatta. E anche a quel segno di spunta specifico si verificherà solo una volta nella vita.
Jineesh Uvantavida,

In sostanza, l'assunto sulle zecche non è valido: 1) più osservatori possono vedere lo stesso "tick" (pensa a discussioni / processi) e 2) lo stesso "tick" può essere osservato più volte dallo stesso osservatore, se interrogato abbastanza velocemente.
user2864740

Tuttavia, da solo utilizzando Guid.NewGuid (e ignorando questo fatto non è "crittograficamente casuale", che potrebbe essere di interesse in alcuni casi), possiamo affermare che, con una probabilità abbastanza alta che non si preoccupano altrimenti, un ID univoco sarà essere generato - questa è una garanzia molto, molto più alta delle "zecche" . Pertanto, i 'segni di spunta' non hanno alcun valore / uso qui si aspettano come dati "secondari" inseriti nel nome del file.
user2864740

(FWIW: ho appena corretto un po 'di codice con un'affermazione sopra menzionata rotta su' tempo unico '..)
user2864740

0

Se desideri avere il datetime, le ore, i minuti ecc., Puoi utilizzare una variabile statica. Aggiungi il valore di questa variabile al nome del file. È possibile avviare il contatore con 0 e incrementare dopo aver creato un file. In questo modo il nome del file sarà sicuramente unico dato che hai anche secondi nel file.


0

Di solito faccio qualcosa del genere:

  • inizia con un nome file stem ( work.dat1ad esempio)
  • prova a crearlo con CreateNew
  • se funziona, hai il file, altrimenti ...
  • mescola la data / ora corrente nel nome del file ( work.2011-01-15T112357.datad esempio)
  • prova a creare il file
  • se ha funzionato, hai il file, altrimenti ...
  • Mescola un contatore monotonico nel nome del file (work.2011-01-15T112357.0001.dat ad esempio. (Non mi piacciono i GUID. Preferisco l'ordine / prevedibilità).
  • prova a creare il file. Continua a spuntare il contatore e riprova fino a quando un file non viene creato per te.

Ecco una lezione di esempio:

static class DirectoryInfoHelpers
{
    public static FileStream CreateFileWithUniqueName( this DirectoryInfo dir , string rootName )
    {
        FileStream fs = dir.TryCreateFile( rootName ) ; // try the simple name first

        // if that didn't work, try mixing in the date/time
        if ( fs == null )
        {
            string date = DateTime.Now.ToString( "yyyy-MM-ddTHHmmss" ) ;
            string stem = Path.GetFileNameWithoutExtension(rootName) ;
            string ext  = Path.GetExtension(rootName) ?? ".dat" ;

            ext = ext.Substring(1);

            string fn = string.Format( "{0}.{1}.{2}" , stem , date , ext ) ;
            fs = dir.TryCreateFile( fn ) ;

            // if mixing in the date/time didn't work, try a sequential search
            if ( fs == null )
            {
                int seq = 0 ;
                do
                {
                    fn = string.Format( "{0}.{1}.{2:0000}.{3}" , stem , date , ++seq , ext ) ;
                    fs = dir.TryCreateFile( fn ) ;
                } while ( fs == null ) ;
            }

        }

        return fs ;
    }

    private static FileStream TryCreateFile(this DirectoryInfo dir , string fileName )
    {
        FileStream fs = null ;
        try
        {
            string fqn = Path.Combine( dir.FullName , fileName ) ;

            fs = new FileStream( fqn , FileMode.CreateNew , FileAccess.ReadWrite , FileShare.None ) ;
        }
        catch ( Exception )
        {
            fs = null ;
        }
        return fs ;
    }

}

Potresti voler modificare l'algoritmo (ad esempio, utilizzare sempre tutti i componenti possibili nel nome del file). Dipende dal contesto: se, ad esempio, stavo creando file di registro che potrei voler uscire dall'esistenza, vorresti che tutti condividessero lo stesso modello con il nome.

Il codice non è perfetto (nessun controllo sui dati passati per esempio). E l'algoritmo non è perfetto (se si riempiono il disco rigido o si incontrano autorizzazioni, errori I / O effettivi o altri errori del file system, ad esempio, questo si bloccherà, così com'è, in un ciclo infinito).


0

Finisco per concatenare il GUID con la stringa del giorno del mese e del secondo millisecondo e penso che questa soluzione sia abbastanza buona nel mio scenario



0

Ho scritto un corso specifico per farlo. Viene inizializzato con una parte "base" (il valore predefinito è un timestamp preciso al minuto) e successivamente aggiunge lettere per creare nomi univoci. Quindi, se il primo timbro generato è 1907101215a, il secondo sarebbe 1907101215b, quindi 1907101215c, eccetera.

Se ho bisogno di più di 25 francobolli unici, allora uso le "z" unarie per contare i 25. Quindi, va 1907101215y, 1907101215za, 1907101215zb, ... 1907101215zy, 1907101215zza, 1907101215zzb e così via. Ciò garantisce che i timbri vengano sempre ordinati in ordine alfanumerico nell'ordine in cui sono stati generati (purché il carattere successivo dopo il timbro non sia una lettera).

Non è thread-safe, non aggiorna automaticamente il tempo e si gonfia rapidamente se hai bisogno di centinaia di timbri, ma lo trovo sufficiente per le mie esigenze.

/// <summary>
/// Class for generating unique stamps (for filenames, etc.)
/// </summary>
/// <remarks>
/// Each time ToString() is called, a unique stamp is generated.
/// Stamps are guaranteed to sort alphanumerically in order of generation.
/// </remarks>
public class StampGenerator
{
  /// <summary>
  /// All the characters which could be the last character in the stamp.
  /// </summary>
  private static readonly char[] _trailingChars =
  {
    'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j',
    'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't',
    'u', 'v', 'w', 'x', 'y'
  };

  /// <summary>
  /// How many valid trailing characters there are.
  /// </summary>
  /// <remarks>Should always equal _trailingChars.Length</remarks>
  public const int TRAILING_RANGE = 25;

  /// <summary>
  /// Maximum length of the stamp. Hard-coded for laziness.
  /// </summary>
  public const int MAX_LENGTH_STAMP = 28;

  /// <summary>
  /// Base portion of the stamp. Will be constant between calls.
  /// </summary>
  /// <remarks>
  /// This is intended to uniquely distinguish between instances.
  /// Default behavior is to generate a minute-accurate timestamp.
  /// </remarks>
  public string StampBase { get; }

  /// <summary>
  /// Number of times this instance has been called.
  /// </summary>
  public int CalledTimes { get; private set; }

  /// <summary>
  /// Maximum number of stamps that can be generated with a given base.
  /// </summary>
  public int MaxCalls { get; }

  /// <summary>
  /// Number of stamps remaining for this instance.
  /// </summary>
  public int RemainingCalls { get { return MaxCalls - CalledTimes; } }

  /// <summary>
  /// Instantiate a StampGenerator with a specific base.
  /// </summary>
  /// <param name="stampBase">Base of stamp.</param>
  /// <param name="calledTimes">
  /// Number of times this base has already been used.
  /// </param>
  public StampGenerator(string stampBase, int calledTimes = 0)
  {
    if (stampBase == null)
    {
      throw new ArgumentNullException("stampBase");
    }
    else if (Regex.IsMatch(stampBase, "[^a-zA-Z_0-9 \\-]"))
    {
      throw new ArgumentException("Invalid characters in Stamp Base.",
                                  "stampBase");
    }
    else if (stampBase.Length >= MAX_LENGTH_STAMP - 1)
    {
      throw new ArgumentException(
        string.Format("Stamp Base too long. (Length {0} out of {1})",
                      stampBase.Length, MAX_LENGTH_STAMP - 1), "stampBase");
    }
    else if (calledTimes < 0)
    {
      throw new ArgumentOutOfRangeException(
        "calledTimes", calledTimes, "calledTimes cannot be negative.");
    }
    else
    {
      int maxCalls = TRAILING_RANGE * (MAX_LENGTH_STAMP - stampBase.Length);
      if (calledTimes >= maxCalls)
      {
        throw new ArgumentOutOfRangeException(
          "calledTimes", calledTimes, string.Format(
            "Called Times too large; max for stem of length {0} is {1}.",
            stampBase.Length, maxCalls));
      }
      else
      {
        StampBase = stampBase;
        CalledTimes = calledTimes;
        MaxCalls = maxCalls;
      }
    }
  }

  /// <summary>
  /// Instantiate a StampGenerator with default base string based on time.
  /// </summary>
  public StampGenerator() : this(DateTime.Now.ToString("yMMddHHmm")) { }

  /// <summary>
  /// Generate a unique stamp.
  /// </summary>
  /// <remarks>
  /// Stamp values are orered like this:
  /// a, b, ... x, y, za, zb, ... zx, zy, zza, zzb, ...
  /// </remarks>
  /// <returns>A unique stamp.</returns>
  public override string ToString()
  {
    int zCount = CalledTimes / TRAILING_RANGE;
    int trailing = CalledTimes % TRAILING_RANGE;
    int length = StampBase.Length + zCount + 1;

    if (length > MAX_LENGTH_STAMP)
    {
      throw new InvalidOperationException(
        "Stamp length overflown! Cannot generate new stamps.");
    }
    else
    {
      CalledTimes = CalledTimes + 1;
      var builder = new StringBuilder(StampBase, length);
      builder.Append('z', zCount);
      builder.Append(_trailingChars[trailing]);
      return builder.ToString();
    }
  }
}

-1

DateTime.Now.Ticksnon è sicuro, Guid.NewGuid()è troppo brutto, se hai bisogno di qualcosa di pulito e quasi sicuro ( non è sicuro al 100% ad esempio se lo chiami 1.000.000 di volte in 1ms ), prova:

Math.Abs(Guid.NewGuid().GetHashCode())

Per sicuro intendo sicuro di essere unico quando lo chiami così tante volte in brevissimo tempo pochi ms di tempo.


C'è qualche problema con la mia soluzione downvoter? Per favore mi faccia sapere.
Mehdi Dehghani,

Il GetHashCodemetodo restituisce an int, che ha un intervallo di 32 bit, mentre a GUIDha un intervallo di 128 bit e quindi è molto più probabile che sia univoco. Se non ti piace il formato di un GUIDvalore, chiamalo semplicemente ToString("N")per rimuovere i trattini.
4thex
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.