Prima di iniziare il trattamento delle Web API vere e proprie, è necessario approfondire la gestione del file system nei vari contesti di utilizzo. Il file system, infatti, comprende le funzioni necessarie all’accesso locale o remoto alle risorse, quindi per utilizzare Web API o implementarne di proprie, sono proprio i metodi del file system che entrano in gioco.
Strutture del file system #
Il framework di Instant Developer Cloud contiene un’interfaccia file system unificata rispetto ai tre contesti operativi di utilizzo delle applicazioni:
- Il contesto server, in cui l’applicazione è in funzione nell’ambiente Node.js e può utilizzare il file system del sistema operativo del server.
- Il contesto browser offline (PWA), in cui l’applicazione è in funzione nel browser, e può utilizzare le funzioni di gestione file che il browser mette a disposizione.
- Il contesto device, in cui l’applicazione è in funzione in una shell Cordova e quindi può utilizzare il file system del device tramite i plugin nativi.
In tutti e tre questi contesti, l’interfaccia unificata di Instant Developer consente al medesimo codice applicativo di ottenere un funzionamento corrispondente.
Ogni applicazione installata ha una propria home directory nella quale memorizza i file che essa gestisce. La struttura della home directory è mostrata nel disegno seguente:
Nella cartella resources vengono memorizzati i file inseriti come risorse di progetto. Le risorse considerate per un’applicazione sono quelle inserite direttamente nell’applicazione stessa e quelle presenti nelle librerie condivise del progetto. Ogni risorsa viene memorizzata con un nome del file costituito dal suo id in formato guid 36, più l’estensione che corrisponde al nome del file. La cartella resources viene ricostruita ad ogni installazione, quindi deve essere considerata in sola lettura. I file contenuti nella cartella resources cambiano solo quando cambia anche il loro nome, quindi come impostazione predefinita vengono mantenuti nella cache dei browser per un anno intero.
La cartella files è la vera e propria home directory del file system dell’applicazione. Essa contiene le cartelle predefinite private, temp e uploaded che hanno un significato particolare:
- private: contiene file non visibili da internet, quindi senza URL pubblica.
- temp: contiene file temporanei visibili da internet. Questi file non vengono automaticamente cancellati dai server, ma possono essere rimossi dal browser o dai dispositivi in alcune circostanze.
- uploaded: contiene file che rappresentano risorse caricate dai dispositivi. I file in questa cartella vengono mantenuti nella cache dei browser per un anno intero, in quanto si suppone che essi non cambino mai il loro contenuto e, quando questo avviene, cambino anche il nome.
Ogni cartella può contenere a sua volta ulteriori sottocartelle personalizzate che mantengono le caratteristiche di quella base.
L’oggetto File System #
L’oggetto che rappresenta il file system all’interno dell’applicazione viene creato dal framework all’avvio di una sessione e può essere utilizzato dal codice applicativo tramite app.fs. Sono disponibili tre semplici metodi per creare ed inizializzare oggetti di tipo File, Directory e Url.
Per istanziare un nuovo oggetto File è sufficiente utilizzare il seguente codice:
let f = app.fs.file(path, type);
Dove path è il percorso del file completo di nome ed estensione relativo alla cartella base definita in funzione del parametro type. I valori possibili di type sono contenuti nella lista valori App.Fs.internalType e sono i seguenti:
- permanent: è il valore di default; la cartella base sarà files.
- temp: rappresenta un file temporaneo; la cartella base sarà files/temp.
- resource: rappresenta un file di risorsa in sola lettura; la cartella base sarà resources.
- private: rappresenta un file privato; la cartella base sarà files/private.
Per istanziare un nuovo oggetto Directory è possibile utilizzare il seguente codice:
let d = app.fs.directory(path, type);
Dove path è il percorso della directory completa di nome relativa alla cartella base definita in funzione del parametro type.
Infine, per istanziare un nuovo oggetto URL è possibile utilizzare il seguente codice:
let u = app.fs.url(URL);
Dove il parametro URL è in formato canonico: protocollo://<nomehost><:porta></percorso><?querystring>
Scrivere file #
Per creare un nuovo file nel file system e scrivere una riga di testo, è possibile usare il seguente codice:
let f = app.fs.file("test.txt");
yield f.create();
yield f.write("hello world\n");
yield f.close();
Il file viene creato nella home directory per i file pubblici, ovvero nella cartella files. Se si desidera vedere il contenuto del file in un browser, si possono aggiungere le seguenti righe:
let url = yield f.getPublicUrl();
app.open(url);
Se si desidera aggiungere righe ad un file invece di crearlo vuoto, è possibile usare il metodo append al posto di create.
Per creare un file in una directory personalizzata, tale directory deve essere già presente nel file system, come mostrato nel seguente esempio:
let d = app.fs.directory("test-files");
yield d.create();
let f = app.fs.file("test-files/test.txt");
yield f.create();
yield f.write("hello world\n");
yield f.close();
Leggere file #
Per leggere un file contenuto nel file system sono disponibili due modalità: lettura binaria o lettura di file di testo in forma completa o riga per riga. Vediamo un esempio di lettura completa di un file di testo in memoria.
let f = app.fs.file("test-files/test.txt");
let fcontent = yield f.readAll();
Il metodo readAll non richiede né l’apertura né la chiusura del file, visto che il file viene letto completamente in una sola operazione in memoria. Non è consigliabile quindi usarlo per file di grandi dimensioni, cioè dell’ordine del megabyte o superiori. In questi casi è preferibile utilizzare il metodo readLine che esegue la lettura del file riga per riga.
let f = app.fs.file("test-files/test.txt");
yield f.open();
f.encoding = "utf-8";
yield f.readLine(function (s) {
console.log(s);
f.break();
});
yield f.close();
Il metodo readLine richiede come primo parametro una funzione che riceve in input le righe del file, lette una alla volta. All’interno della funzione è possibile interrompere la lettura chiamando il metodo file.break. Nell’esempio, verrà letta solo la prima riga e poi la chiamata a readLine sarà completa e il codice proseguirà con la chiusura del file.
La lettura in formato binario può essere eseguita tramite il metodo read, che restituisce un oggetto JavaScript di tipo ArrayBuffer.
Leggere le informazioni di un file o una directory #
Gli oggetti File e Directory possiedono proprietà e numerosi metodi nati per recuperare informazioni. Fra i quali:
- file.encoding: codifica del file, di default “utf-8”. Deve essere impostata prima di utilizzare il metodo di lettura readLine.
- file.publicUrl: contiene la URL con cui accedere al file da remoto. Nota bene: per recuperare questa informazione in modo coerente in tutti i contesti di utilizzo, è necessario chiamare il metodo file.getPublicUrl().
- file.originalName: se il file viene restituito da un’operazione di upload, contiene il nome originale del file. Il file fisico viene rinominato per ragioni di privacy e per evitare conflitti con i file esistenti.
- file.exists(), directory.exists(): restituiscono true se il file o la directory sono già esistenti nel file system.
- file.name(), file.extension(), file.length(): restituiscono il nome, l’estensione e la lunghezza del file in byte.
- directory.list(): restituisce un array di File o Directory contenuti nella directory attuale. È possibile richiedere la ricerca a più livelli.
Se si desidera conoscere la lunghezza di un file in byte è possibile, ad esempio, utilizzare il seguente codice:
let l = null;
let f = app.fs.file("test-files/test.txt");
if (yield f.exists()) {
l = yield f.length();
}
console.log(l);
Operazioni su file e directory #
Gli oggetti File e Directory possiedono diversi metodi per la propria gestione. I principali sono:
- file.create: crea il file; la directory di destinazione deve già esistere.
- directory.create: crea una directory, creando anche quelle intermedie.
- file.remove, directory.remove: cancella il file o la directory (e il suo contenuto).
- file.rename, directory.rename: cambia nome e sposta il file o la directory in un’altra directory.
- file.zip, directory.zip: comprime il file o la directory.
- file.unzip: decomprime l’archivio zip ricreando il contenuto compresso.
Funzioni crittografiche #
Gli oggetti File possiedono i seguenti metodi per la gestione del contenuto criptato:
- file.encrypt: sostituisce il file con la versione criptata, utilizzando una chiave simmetrica.
- file.decrypt: sostituisce un file criptato con la versione in chiaro, utilizzando una chiave simmetrica.
Per la gestione dei contenuti criptati, si consiglia di utilizzare i metodi della libreria App.Crypt, che permette di gestire crittografia simmetrica, asimmetrica, hashing, e generazione di chiavi con un’interfaccia consistente nei vari contesti di utilizzo: server, browser e dispositivi.
Controllare il contenuto del file system #
Per controllare il contenuto del file system di un’applicazione su un server IDE o di produzione è possibile utilizzare la pagina file browser della console.
Per accedere al file system dell’applicazione sul server IDE, entrare nel sottomenu APP E DATI e poi cliccare il pulsante Sfoglia.
Per accedere al file system di un’installazione dell’applicazione su un server di produzione, entrare nella pagina delle installazioni e poi cliccare il pulsante Sfoglia.
Per controllare il contenuto del file system di un’applicazione installata in un browser o in un dispositivo, è necessario utilizzare gli inspector del browser o del dispositivo. Si consiglia di fare riferimento alla documentazione del browser o del dispositivo per maggiori informazioni.
Accedere alle risorse #
I file di risorsa inseriti nel progetto vengono copiati in una parte del file system dell’applicazione in sola lettura. Per accedere a questi file è possibile utilizzare il metodo app.getResourceFile che restituisce un oggetto File dato il nome della risorsa o il relativo riferimento, come mostrato negli esempi seguenti:
let f = app.getResourceFile($panca);
console.log(f.readAll());
let f = app.getResourceFile(“panca”);
console.log(f.readAll());
Si ricorda che i file di risorsa sono pubblici, cioè visibili da internet. Tuttavia essendo i loro nomi sempre in formato guid 36, occorre conoscerne il nome per poter accedere.
File system remoti #
Nel manuale Struttura del database è stato introdotto il componente Cloud Connector, in grado di far interagire i server nel cloud con servizi presenti on-premise come ad esempio database relazionali o parti di file system.
Operare sui file system on-premise condivisi tramite Cloud Connector è molto semplice: basta creare l’oggetto che rappresenta il file system remoto con questa riga di codice:
let rfs = new App.RemoteFS(app, "<nome-cc>://<nome-fs>", “api-key”);
Dove <nome-cc> è il nome del Cloud Connector che contiene il file system su cui operare, <nome-fs> è il nome del file system come configurato nelle opzioni del Cloud Connector ed infine <api-key> è la chiave di connessione del Cloud Connector.
L’oggetto RemoteFS estende l’oggetto File System base, cioè quello ottenuto tramite app.fs, quindi tutti metodi e le proprietà visti finora sono disponibili anche nel file system remoto.
L’unica operazione da aggiungere è lo spostamento di un oggetto File tra un file system remoto e quello locale. Per ottenere questo risultato è disponibile il metodo file.put che sposta il file nell’oggetto corrispondente in un diverso file system. Esempio di codice:
let rfs = new App.RemoteFS(app, "<nome-cc>://<nome-fs>", “api-key”);
let rf = rfs.file(“panca.txt”);
let lf = app.fs.file(“panca.txt”);
rf.put(lf); // sposta il file dal cloud connector al server
lf.put(rf); // sposta il file dal server al cloud connector
Si ricorda che l’uso dei file system remoti è possibile solo quando l’applicazione è in funzione nel server e non nei browser o nei dispositivi. Inoltre gli spostamenti tramite put sono consentiti solo tra un file system remoto e quello del server, non direttamente fra due istanze di file system remoto.
L’oggetto URL #
Concludiamo la panoramica delle funzionalità del file system illustrando l’oggetto url, che rappresenta una URL su cui eseguire operazioni come get, post, eccetera. Per definire un oggetto url è sufficiente usare il metodo corrispondente del file system:
let u = app.fs.url(“http://www.bing.com/HPImageArchive.aspx?format=js&n=8”)
A questo punto è possibile chiamare i metodi: get, post, put, delete, head, patch che restituiscono la risposta completa del server, che nell’esempio è il servizio immagini di Bing:
let result = yield uu.get();
console.log(result);
let p = JSON.parse(result.body);
console.log(p);
Tutti i metodi di chiamata ammettono un parametro options che permette di specificare le seguenti opzioni:
- params: è un oggetto che contiene la query string della chiamata. I nomi dei parametri e i valori vengono automaticamente codificati.
- headers: è un oggetto che contiene gli header della chiamata.
- timeOut: timeout della richiesta, in millisecondi.
- authentication: è un oggetto che permette di specificare le proprietà username e password, per il livello di autenticazione basic.
- responseType: indica il tipo di risposta desiderata: “arraybuffer” o “text”.
- body: se la chiamata è di tipo post o patch, rappresenta il body della chiamata.
- bodyType: specifica il content-type della richiesta, in caso di post, patch o put.
La possibilità di utilizzare chiamate URL è disponibile in ogni contesto di utilizzo. Tuttavia, se l’applicazione è in esecuzione nel contesto browser, le chiamate devono soddisfare i criteri CORS. Si consiglia di verificare che il server consenta la condivisione delle risorse cross-origin, altrimenti la chiamata genererà un’eccezione.
Trasferimento di file tramite URL #
L’oggetto url contiene anche i metodi download e upload che permettono di scaricare un file da un server o di caricare un file verso un server remoto. Questi metodi richiedono come primo parametro un oggetto File che rappresenta il contenuto da caricare o il posto dove scaricare la risorsa.
È possibile monitorare il progresso dell’operazione di upload o download tramite gli eventi onUploadProgress e onDownloadProgress che possono essere intercettati definendo la corrispondente funzione sull’istanza di url, come mostrato nell’esempio seguente:
var u = app.fs.url(“https://server.com/my-file.jpg”);
yield u.download();
u.onDownloadProgress = function (byteTransferred, total) {
if (view.cancelOperation)
return false; // to cancel download, return false
};