Traduzione a cura di Fabrizio Angius [qtsolutions -A-T- gmx.net]
English TOC.

8. File, cartelle e flussi

Questo capitolo riguarda i file e le azioni associate ai file. Verranno discusse quelle (importanti) parti di Qt che riguardano la gestione dei file e del file system in maniera indipendente dalla piattaforma. Dopo aver introdotto le classi si usera un po' di codice per mostrare ulteriormente l'uso delle stesse.

La classe QFile

Qt gestisce i file come degli oggetti. Per esempio, un file e' rappresentato da un'istanza della classe QFile. Il file non deve necessariamente esistere o essere aperto. Il metodo exists() ne verifica l'esistenza. Se il file esiste si puo' usare il metodo remove() per cancellarlo. Per operazioni di lettura e/o scrittura, il file va aperto. Questo viene fatto utilizzando il metodo open(). Un file puo' essere aperto in diversi modi, questi sono i piu' comuni:

Se state lavorando con dei file di log (aggiungendo cioe' delle informazioni alla fine di un file) e' necessario aprire il file con i flag IO_WriteOnly | IO_Append. Il metodo open() ritorna TRUE se il file e' stato aperto correttamente e FALSE altrimenti. Aprendo il file in scrittura (IO_WriteOnly o IO_ReadWrite), questo viene creato se non esisteva gia'.

Quando si lavora con dei file, potrebbe tornare utile sapere se questi sono aperti. A questo proposito basta utilizzare il metodo isOpen(). Quando un file non serve piu' va chiuso con il metodo close(). In questo modo i dati eventualmente ancora nel buffer di scrittura vengono scritti su disco. Se si dovesse rendere necessario svuotare il buffer senza chiudere il file, basterebbe utilizzare il metodo flush().

L'esempio 8-1 mostra un possibile utilizzo della classe QFile. L'esempio 8-2 mostra una sessione Bash utilizzando l'applicazione dell'esempio 8-1. Il commando Unix touch crea il file f.txt, mentre chmod -r rimuove i diritti di accesso in lettura allo stesso.

#include <qapplication.h>
#include <qfile.h>

#include <iostream>

int main( int argc, char **argv )
{
  QApplication a( argc, argv );

  QFile f( "f.txt" );

  if( !f.exists() )
  {
    // Il file non esiste
    std::cout << "The file does not exist." << std::endl;

    return 0;
  }

  // Esiste, quindi aprilo
  if( !f.open( IO_ReadOnly ) )
  {
    // Non puo' essere aperto
    std::cout << "Failed to open." << std::endl;

    return 0;
  }

  // Aperto. Ora dobbiamo chiuderlo.
  std::cout << "It worked." << std::endl;

  f.close();

  return 0;
}

Esempio 8-1

$ ./qfile
The file does not exist.
$ touch f.txt
$ ./qfile
It worked.
$ chmod -r f.txt
$ ./qfile
Failed to open.

Esempio 8-2

Flussi

Un file di per se' e' poco utile. Dispone di alcuni metodi primitivi per la lettura e scrittura, ma l'utilizzo di una classe per gestire i flussi rende il tutto piu' semplice e flessibile.

Per l'utilizzo con file di testo c'e' la classe QTextStream. Per i dati binari c'e' la classe QDataStream, ma in questo capitolo ci concentreremo sulla gestione dei file di testo.

Il lettore curioso avra' gia' notato la relazione che sia le classi per i flussi che la classe QFile hanno con la classe IODevice. Questo significa che qualsiasi classe utilizzata per leggere o scrivere un flusso di dati puo' lavorare per esempio anche su socket, database, memoria, ecc. In questo modo diventa molto facile riutilizzare pezzi di codice.

La classe QTextStream fornisce gli operatori << e >> per i tipi piu' comuni. Nel secondo tutorial della Trolltech, nel capitolo Data Elements, sezione "Reading and Writing Data Elements", si dimostra come implementare questi operatori in una qualsiasi classe o struttura.

E' possibile controllare la formattazione del file prodotto utilizzando i flags delle proprieta' dell'oggetto text stream. Per esempio, e' possibile impostare la base utilizzata per rapresentare i numeri scritti nel flusso (binaria, ottale, decimale, esadecimale).

Il metodo atEnd() ritorna infine true se non ci sono piu' dati da leggere nel flusso in questione.

L'esempio 8-3 mostra l'utilizzo di un text stream con un file. L'esempio 8-4 mostra una sessione Bash in cui si utilizza il programma. Il commando unix wc -l conta il numero di righe in un dato file.

#include <qapplication.h>
#include <qfile.h>
#include <qtextstream.h>

#include <iostream>

int main( int argc, char **argv )
{
  QApplication a( argc, argv );

  QFile f( "f.txt" );

  if( !f.open( IO_WriteOnly | IO_Append ) )
  {
    std::cout << "Failed to open file." << std::endl;
    return 0;
  }

  QTextStream ts( &f );

  ts << "This is a test-run." << endl;

  f.close();

  return 0;
}

Esempio 8-3

$ wc -l f.txt
wc: f.txt: No such file or directory
$ ./stream
$ wc -l f.txt
      1 f.txt
$ ./stream
$ wc -l f.txt
      2 f.txt

Esempio 8-4

Percorsi dei file

Le diverse piattaforme utilizzano modi diversi per specificare i percorsi dei file. Windows utilizza una lettera per ogni unita' e un percorso relativo ad essa, come c:\foo\bar, mentre Unix e MacOS X (che deriva da Unix), utilizzano un'unica radice per i percorsi, come /foo/bar. Cambia anche il carattere utilizzato per separare le cartelle. Per poter rappresentare i percorsi in maniera indipendente dalla piattaforma, Qt utilizza la classe QDir.

Quando si esplora un file system, sarebbe bello avere anche un punto di partenza. Per questo motivo, la classe QDir ha una serie di membri statici. Per iniziare, il metodo current() ritorna semplicemente un oggetto QDir rappresentante la directory corrente dell'applicazione. Il metodo root() ritorna la directory radice relativamente al drive corrente [sui sistemi Unix c'e' una sola root ovviamente - ndt]. Per le piattaforme che gestiscono piu' drive [come appunto Windows - ndt], il metodo drives() ritorna una serie di istanze a QFileInfo (non cercate di cancellare i puntatori restituiti, sono gestiti da Qt). Il metodo home() punta infine alla home directory dell'utente corrente.

Quando dovete spostarvi tra le directory potete partire da uno dei metodi indicati sopra o da un punto specifico indicato con una stringa. Tale stringa puo' essere 'ripulita' utilizzando il metodo cleanDirPath(). L'esistenza di un percorso puo' essere verificata utilizzando il metodo exists(). Un percorso relativo puo' essere convertito in assoluto con il metodo convertToAbs(). Per spostarsi di cartella si possono usare i metodi cd() e cdUp(). Funzionano esattamente come i rispettivi commandi Bash/Cmd/DOS cd e cd ... Quando ci si trova nella directory di root (ossia quando isRoot() ritorna TRUE) non ha alcun senso chiamare cdUp().

Sebbene potrebbe essere utile spostarsi semplicemente di cartella, sara' ancora piu' utile fare effettivamente qualcosa. Il metodo mkdir() tenta di creare una nuova cartella e ritorna TRUE se ha avuto successo e FALSE altrimenti. I metodi rmdir() e rename() ritornano sempre TRUE in caso di successo, ma servono rispettivamente e cancellare e a rinominare una cartella. Un metodo chiamato rename() viene utilizzato anche per rinominare file, me per cancellarli c'e' il metodo remove().

Mentre ci sposta per il file system eseguendovi sopra varie operazioni, e' utile conoscere le opzioni che ci vengono messe a disposizione. Prima di continuare discutendo sul come iterare sui contenuti di una cartella, e' necessario spiegare due metodi.

Per prima cosa c'e' il metodo setSorting. Esso ci permette di specificare l'ordine con cui le voci delle cartelle vengono scandite. Il tutto viene fatto impostando alcuni flag (da mettere eventualmente in OR tra di loro). Ci sono quattro flag esclusivi che non possono essere combinati: QDir::Name, QDir::Time, QDir::Size e Unsorted. Oltre a questi ci sono alcuni modificatori per ignorare maiuscole e minuscole, per elencare per prime le cartelle, ecc. Vengono descritti nei dettagli qui.

Infine c'e' il metodo setFilter. Questo controlla quali voci di directory appariranno effettivamente quando si itera sui contenuti di una cartella. Le varie opzioni possono essere combinate mettendo i relativi flag in OR. Dato che sono moltissimi, vi consiglio di leggere la documentazione ufficiale.

L'esempio 8-5 mostra un'esempio di utilizzo della class QDir. Itera semplicemente su tutte le sottocartelle della cartella corrente e ne stampa il nome su console.

#include <qapplication.h>
#include <qdir.h>

#include <iostream>

int main( int argc, char **argv )
{
  QApplication a( argc, argv );

  QDir currentDir = QDir::current();

  currentDir.setFilter( QDir::Dirs );
  QStringList entries = currentDir.entryList();
  for( QStringList::ConstIterator entry=entries.begin(); entry!=entries.end(); ++entry )
    std::cout << *entry << std::endl;

  return 0;
}

Esempio 8-5

Notate che viene restituita una QStringList. Per accedere ai membri della lista abbiamo usato un ConstIterator. Il tutto e' molto simile agli equivalenti STL, list<string> e list<string>::const_iterator, per cui non verranno discussi ulteriormente.

Ulteriori informazioni su file e directory

Il metodo setFilter potrebbe tornare utile per recuperare le voci di directory che ci interessano, ma per ognuna di queste voci ci sono numerose informazioni associate. Invece di utilizzare entryList() si potrebbe utilizzare entryInfoList. Questo metodo ritorna una lista di istanze di QFileInfo contenenti le informazioni associate ad ogni voce. Una cosa che vale la pena notare e' che la lista restituita da entryInfoList appartiene all'oggetto QDir associato, per cui non va cancellata. Dato che la lista cambiera alle successive invocazioni dello stesso metodo, potrebbe essere necessario farne una copia per lavorarci sopra.

Quali informazioni contiene quindi un oggetto QFileInfo? Per prima cosa, possiamo sapere di che tipo di voce di directory si tratta utilizzando i metodi isDir, isFile e isSymLink. Questi metodi ritornano TRUE se l'elemento in questione e' del tipo indicato dal nome del metodo stesso.

Sarebbe anche interessante conoscere il nome dell'elemento. Per fare cio' ci sono moltissimi metodi. Per iniziare, possiamo reperire la path con il metodo filePath(). La path assoluta puo' essere ottenuta invocando absFilePath(). Il nome del file (o della cartella) con l'eventuale estensione puo' essere ottenuto dal metodo fileName() mentre per avere il nome senza estensione c'e' il metodo baseName(); l'estensione puo' essere recuperata separatamente mediante il metodo extension(). Questi ultimi due metodi accettano un parametro booleano per specificare se devono ritornare l'intera estensione nel caso di estensioni multiple (p.es. il file test.tar.gz ha come estensione completa tar.gz e come estensione di default gz). Utilizzando il metodo dir() viene restituito un oggetto QDir rappresentante la path del file o la directory in esame.

E' anche possibile sapere quali attributi ha un determinato elemento. Per questo ci sono i metodi isReadable(), isWriteable, isExecutable e isHidden.

Infine si puo' accedere alle informazioni temporali relative all'elemento. I metodi in questione sono created(), lastModified() e lastRead(). Tutti questi metodi ritornano un oggetto di tipo QDateTime. Questi oggetti possono essere convertiti in oggetti QString con il metodo toString().

L'esempio 8-6 dimostra come si possono ottenere degli oggetti QFileInfo da un oggetto QDir e come si puo' iterare sulle voci di una cartella.

#include <qapplication.h>
#include <qdir.h>

#include <iostream>

int main( int argc, char **argv )
{
  QApplication a( argc, argv );

  QDir currentDir = QDir::current();
  currentDir.setFilter( QDir::Files );

  const QFileInfoList *infos = currentDir.entryInfoList();
  const QFileInfo *info;

  QFileInfoListIterator infoIt( *infos );
  while( (info=infoIt.current()) != 0 )
  {
    std::cout << "Base name: " << info->baseName( TRUE ) << std::endl;
    std::cout << "Extension: " << info->extension( TRUE ) << std::endl;
    std::cout << "Created: " << info->created().toString() << std::endl;

    ++infoIt;
  }
}

Esempio 8-6

Riassunto

Non c'e' nessun codice sorgente da scaricare per questo capitolo.

This is a part of digitalfanatics.org and is valid XHTML.