FileSystem

Valuta questo articolo 0 Voti

Autori: Lorenzo Maiorfi e Gianluca Ruta

Uno dei sottosistemi di gran lunga più utilizzato nell'ambito dello sviluppo di applicazioni embedded è senza dubbio quello  relativo all'immagazzinamento di dati non volatili. Ad esempio, in un'applicazione di data-logging è imperativo che i dati raccolti siano conservati anche a seguito di un riavvio del dispositivo o della scarica completa delle batterie. Anche se in linea di principio è possibile utilizzare un qualsiasi tipo di memoria di massa, da un hard disk ad una EEprom, la tendenza attuale spinge verso l'utilizzo di memorie flash, attraverso l'uso di schede SD, MMC, PenDrive USB, ecc.

A seconda del tipo di dispositivo utilizzato, il .NET Micro Framework rende disponibile l'accesso a memorie di massa (tipicamente memorie flash), attraverso i servizi di un vero e proprio ‘File System’. Gli standard supportati sono al momento solo FAT e FAT32, anche se nuovi ‘porting’ del Micro Framework (ovvero nuove implementazioni della runtime e delle librerie per una specifica architettura di microcontrollori) potrebbero consentire l'utilizzo di altri standard, quali NTFS o ext3, ad esempio. Come per la stragrande maggioranza dei File System, il modello di interazione con i contenuti della memoria di massa avviene tramite l'accesso ad una struttura gerarchica fatta di file e cartelle. La radice di tale gerarchia prende il nome di "\", ed è effettivamente presente come primo carattere di ogni stringa che esprime un "percorso" all'interno dell'albero del File System. Nel caso della scheda TahoeII della DeviceSolutions (nella figura seguente si veda il particolare della scheda con lo slot SD), il percorso che fa capo allo slot SD presente sulla scheda prende il nome di "\SD". Tale nome potrebbe essere diverso su dispositivi differenti.

Le principali classi del framework dedicate alla gestione del File System , contenute all'interno dell'assembly "System.IO" sono le seguenti:

  • Path: espone molti metodi statici per la gestione dei percorsi; consente ad esempio di estrarre le varie parti di un path gerarchico completo così come di comporre path complessi a partire dalle loro sottoparti
  • Directory: espone per lo più metodi statici per l'enumerazione di file e cartelle contenuti all'interno di un determinato percorso; non rappresenta un'istanza di directory vera e propria, per la quale è previsto l'uso della classe DirectoryInfo
  • File: espone metodi statici per la manipolazione di file (copia, ridenominazione, cancellazione, apertura, ecc.)
  • DirectoryInfo: rappresenta una specifica istanza di directory; consente operazioni relative alla creazione di sub-directory, cancellazione, ecc.
  • FileInfo: rappresenta una specifica istanza di file; consente operazioni di creazione ed eliminazione, oltre ad esporre proprietà importanti quali la lunghezza in byte

L'accesso a tutte le operazioni di lettura e scrittura di dati all'interno di un determinato file avviene tramite l'uso di un apposito Stream, denominato FileStream. Tramite le classi di supporto specializzate nella lettura e scrittura di Stream, rispettivamente StreamReader e StreamWriter, è possibile effettuare tutte le più comuni operazioni, come illustrato nel frammento di codice che segue, chiaramente ispirato ad un data-logger, in cui vengono scorse in maniera gerarchica tutte le cartelle e tutti i file presenti nel File System, alla ricerca di un ben preciso file ("datalog.txt", nel nostro esempio), al quale viene poi aggiunta una riga di testo composta da un progressivo di riga e dalla tensione misurata dal convertitore ADC all'ingresso "AUX" della scheda:

1: public class Program

2: {

3:     // stringa per indentazione

4:     const string INDENT="  ";

5:  

6:     public static void Main()

7:     {

8:         // chiama ricorsivamente l'enumerazione 

9:     

10:         enumerateDirectories(@"\");

11:     

12:         while (true)

13:         {

14:             Thread.Sleep(1000);

15:         }

16:     }

17:  

18:     static string _tabs = string.Empty;

19:  

20:     

21:     static void enumerateDirectories(string path)

22:     {

23:         // stampa il nome della directory

24:         Debug.Print(_tabs + "[" + path + "]");

25:  

26:         _tabs += INDENT;

27:         

28:         // Scorre i file...

29:         foreach(string file in Directory.GetFiles(path))

30:         {

31:             Debug.Print(_tabs+Path.GetFileName(file));

32:  

33:             // se viene trovato il file datalog.txt...

34:             if (Path.GetFileName(file).ToLower() == "datalog.txt")

35:             {

36:                 // aggiungo una riga con la tensione misurata all'ingresso aux

37:                 updateDataLog(file);

38:             }

39:         }

40:  

41:         // scorro le directory figlie

42:         foreach (string dir in Directory.GetDirectories(path))

43:         {

44:             // ricorsivamente

45:             enumerateDirectories(dir);

46:         }

47:  

48:         Debug.Print(string.Empty);

49:  

50:         if (_tabs.Length >= INDENT.Length) _tabs = _tabs.Substring(0, _tabs.Length - INDENT.Length);

51:     }

52:  

53:     private static void updateDataLog(string file)

54:     {

55:         int rownum = 0;

56:         string txt;

57:  

58:         // Apro il file in lettura e conto le righe

59:         using (StreamReader sr = new StreamReader(File.Open(file,FileMode.OpenOrCreate,FileAccess.Read)))

60:         {

61:             txt=sr.ReadToEnd();

62:         }

63:  

64:         rownum = txt.Split('\n').Length - 1;

65:  

66:         // riapro il file, stavolta in scrittura

67:         using (StreamWriter sw = new StreamWriter(File.Open(file,FileMode.Append,FileAccess.Write)))

68:         {

69:             // e aggiungo una riga con progressivo e aux

70:             txt = (rownum+1).ToString() + ";" + TahoeII.Tsc2046.ReadAux().ToString("G");

71:  

72:             sw.WriteLine(txt);

73:         }

74:  

75:     }

76:  

77: }

L'applicazione illustrata è costituita da due metodi principali: enumerateDirectories() e updateDataLog(). Il primo utilizza le chiamate a Directory.GetFiles() e Directory.GetDirectories() rispettivamente per scorrere tutti i file e le sotto cartelle della directory corrente. Il secondo metodo apre dapprima in sola lettura il file "datalog.txt", ne conta le righe utilizzando una stringa intermedia come variabile di appoggio e successivamente lo chiude e riapre in modalità di scrittura per aggiungere una riga di testo. E' opportuno notare che, a riga 59, viene utilizzata l'opzione FileMode.OpenOrCreate per fare in modo che, qualora il file non esista (caso che non si applica al nostro demo in quanto il metodo viene eseguito solamente se il file viene effettivamente rilevato) esso venga creato in occasione della prima apertura. Merita un'ultima osservazione inoltre quanto presente alla riga 10, in cui il percorso radice del File System viene indicato con @"\" anziché semplicemente con "\". Tale notazione non dipende dall'API relativa al file-system bensì dal linguaggio C#, che utilizza il carattere "\" come carattere speciale, utilizzato come prefisso di altri caratteri per esprimere in forma testuale caratteri non stampabili, come '\n' ad indicare il carattere "newline". Qualora si volesse indicare all'interno di una stringa espressa come costante nel codice sorgente lo stesso carattere "\", è possibile utilizzare la sequenza "\\" oppure, come indicato nell'esempio appena illustrato, utilizzare il carattere @ prima della stringa o del carattere costante il quale, interpretato solo dal preprocessore del compilatore, disabilita la gestione del carattere speciale "\" nella stringa che segue.