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
};