MaCocoa 032

Capitolo 032 - Barra a Dritta

Questo capitolo riprende il discorso interrotto con una pagina leggera per ricominciare senza troppi traumi.

Sorgenti: Documentazione Apple.

Primo inserimento: 5 giugno 2003

Nuova documentazione

Parto dall'aggiornamento dei Developer Tools del Dicembre 2002. Più che le funzioni aggiunte, le possibilità esotiche di compilazione e quant'altro, mi interessa verificare la documentazione in linea. In effetti vedo che ci sono molte informazioni aggiuntive (magari erano anche nei rilasci precedenti, ma le viste solo ora...). Comincio quindi da lì, per scoprire finalmente che, tra le informazioni associate ad un file, compare anche la data di creazione (e non solo quella di ultima modifica). È giocoforza aggiungere anche questa informazione a tutto il mio castello di oggetti ed interfaccia.

Sicuramente dimentico qualche modifica: aggiungo una variabile d'istanza all'oggetto LSFileInfo; di conseguenza, i metodi accessor, qualche istruzione per salvare il dato su file e recuperarlo (ancora una volta, il formato del file catalogo cambia, ma è storia già vista); ci sono da cambiare alcuni file nib che contengono l'interfaccia, sicuramente la finestra della Preferenze (c'è appunto da aggiungere la visualizzazione o meno della colonna con la data di creazione); ma allora c'è da cambiare anche la gestione delle preferenze, della visualizzazione nella tabella; c'è infine (forse...) la finestra per le ricerche, dove adesso c'è da aggiungere nell'interfaccia una voce di menu pop-up, e poi anche la gestione delle ricerche.

figura 01

figura 01

Insomma, molte piccole modifiche.

Ne ho allora approfittato anche per eliminare codice obsoleto, ovvero ho eliminato dal file DjZeroUtils.h le define di compilazione (ne avevo parlato in precedenza), con la conseguente rimozione di tutto il codice interessato.

Volumi

Sempre frugando nella documentazione, trovo che l'oggetto NSFileManager possiede un metodo per recuperare alcune informazioni relative ai volumi nel loro complesso.

Ora, in Unix e Mac OS X, un volume (disco) è considerato come un file. In effetti, ho sfruttato tale fatto in modo naturale; con l'apposita finestra, scelgo un volume da catalogare. Una volta ottenuto il path, lo considero come un file, ne leggo le informazioni relative, e da qui, essendo a tutti gli effetti una directory, procedo alla costruzione dell'albero dei file contenuti nel volume (di più: all'inizio dei tempi, questo programma ha cominciato catalogando file e directory piuttosto che volumi).

Ci sono però alcune informazioni relative ad un volume che non sono disponibili (né pensabili) se si considera il volume come un file. Ad esempio (cosa che mi rattristava particolarmente), un volume ha una dimensione fisica (per un cd, ad 700 Mega) ben diversa dallo spazio occupato (la dimensione ricorsivamente calcolata sommando lo spazio occupato da tutti i suoi file).

Ecco quindi che è disponibile un metodo solo per i file che sono volumi:

attributi = [[NSFileManager defaultManager]fileSystemAttributesAtPath: volume];

che in piena similitudine coll'ormai noto

attributi = [[NSFileManager defaultManager]fileAttributesAtPath: filename ];

produce una serie di informazioni utili: la dimensione del volume, che si spera essere in byte (data l'ampia varietà di formati di volumi letti da Mac Os X, c'è da sperarsi, ma non è garantito) come l'attributo NSFileSystemSize; all'opposto, lo spazio libero sul volume NSFileSystemFreeSize, nella stesse unità di misura. Sottraendo i due numeri, dovrei avere lo spazio occupato sul volume, che dovrebbe coincidere con quello calcolato sommando le dimensioni di tutti i file (ma c'è il problema della resource fork, che non è considerata...). Poi ci sono due numeri, che dicono quanti nodi sono disponibili sul volume, e quanti di questi sono disponibili (NSFileSystemNodes e NSFileSystemFreeNodes). Cosa siano con precisione i nodi, non lo so, e lascio volentieri indietro ogni spiegazione. Infine, il numero NSFileSystemNumber dovrebbe coincidere col sempre misterioso campo fsNum delle informazioni relative ad un file (un codice identificativo del volume, suppongo, ma ignoro qualsiasi criterio di produzione di tale numero).

Una nuova classe

Per mantenere le informazioni relative ai volumi, decido di costruire una nuova classe. Dal momento che un volume è un file (LSFileInfo) che è una directory (FileStruct) con qualcosa di speciale, la cosa da fare è una sottoclasse di FileStruct. Battezzata subito la classe VolInfo, ecco l'interfaccia:

@interface VolInfo : FileStruct {
    // dimensione del volume, forse in byte
    unsigned long long    volSize ;
    // dimensione dello spazio libero, forse in byte
    unsigned long long    volFreeSize ;
    // numero di nodi presenti, qualunque cosa siano
    unsigned long long    volSystemNodes ;
    // numero di nodi liberi, qualunque cosa siano
    unsigned long long    volSystemFreeNodes ;
    // numero del Fyle system
    unsigned long long    volFileSystemNumber ;
}

completata dalla solita pletora di metodi accessor (più facili del solito, dal momento che si tratta di grandezze scalari e non di oggetti).

- (unsigned long long)volSize
    { return volSize; }
- (void)setVolSize :(unsigned long long)xx
    { volSize = xx ; }

Al momento, la raccolta delle informazioni è l'unico scopo dell'oggetto. È tuttavia interessante l'uso che se ne fa, nella parziale riscrittura del metodo performAddFilesModal:. Il codice definitivo è diverso da quello seguente, ma per aspetti che spiegherò poi:

- (void)
performAddFilesModal: ( NSArray *)volList
{
    NSModalSession     session ;
    NSEnumerator     * enumerator = [volList objectEnumerator];
    NSString    * volPath ;
    NSDictionary *fsattrs ;
    unsigned long long    totalSpace, freeSpace, usedspace ;

    volPath = [enumerator nextObject] ;
    // mostro la finestra di attesa con apposita stringa
    [ WaitPanCtrl showHide: TRUE ];
    [ WaitPanCtrl setText: [NSString stringWithFormat: @"Cataloging %@",
        [[ NSFileManager defaultManager] displayNameAtPath: volPath] ]];
    // faccio partire l'animazione della barra del barbiere
    [ WaitPanCtrl animate: self ];
    // comincio una sessione modale per l'intera applicazione
    session = [NSApp beginModalSessionForWindow: [ [ WaitPanCtrl sharedProgress] window ] ];
    // eseguo la sessione
    [NSApp runModalSession:session] ;
    while ( volPath )
    {
        VolInfo     * fInfo ;
        fsattrs = [[NSFileManager defaultManager] fileSystemAttributesAtPath: volPath];
        totalSpace = [[fsattrs objectForKey:NSFileSystemSize] unsignedLongLongValue] ;
        freeSpace = [[fsattrs objectForKey:NSFileSystemFreeSize] unsignedLongLongValue] ;
        usedspace = totalSpace - freeSpace ;
        // costruisco l'alberatura dei file a partire dalla scelta
        fInfo = [[ VolInfo alloc ] initTreeFromPath: volPath ];
        // metto a posto le altre informazioni
        [ fInfo setFileSize: totalSpace ];
        [ fInfo setVolSize: totalSpace ];
        [ fInfo setVolFreeSize: freeSpace ];
        [ fInfo setVolSystemNodes: [[fsattrs objectForKey:NSFileSystemNodes] unsignedLongLongValue] ];
        [ fInfo setVolSystemFreeNodes: [[fsattrs objectForKey:NSFileSystemFreeNodes] unsignedLongLongValue] ];
        [ fInfo setVolFileSystemNumber: [[fsattrs objectForKey:NSFileSystemNumber] unsignedLongLongValue] ];
        // aggiungo la cosa al catalogo
        [ dataSource addFileEntry: fInfo ];
        volPath = [enumerator nextObject] ;
        if ( volPath == nil ) break ;
        // mostro la finestra di attesa con apposita stringa
        [ WaitPanCtrl setText: [NSString stringWithFormat: @"Cataloging %@",
            [[ NSFileManager defaultManager] displayNameAtPath: volPath] ]];
        // faccio partire l'animazione della barra del barbiere
        [ WaitPanCtrl animate: self ];
    }
    // finisco la sessione
    [NSApp endModalSession: session];
    // nascondo la finestra di attesa
    [ WaitPanCtrl showHide: FALSE ];
}

Il metodo esamina tutti i volumi contenuti nel vettore passato come parametro; di ciascuno preleva gli attributi notevoli, e li conserva nelle giuste variabili. È da notare come la costruzione dell'alberatura della directory avviene come in precedenza. Questa volta il metodo inizializzatore initTreeFromPath: invece che essere applicato ad un oggetto FileStruct lo è ad un oggetto VolInfo, ma funziona lo stesso.

In più dico che la dimensione del volume non è quella calcolata sommando le dimensioni di tutti i file, ma proprio la dimensione fisica del volume (mi pare una cosa più sensata).

Attese piu' misurate

Uno dei motivi di cruccio per cui ho cercato spasmodicamente la dimensione complessiva di un volume prima di esaminare tutti i file è legata alla finestra di attesa WaitPanCtrl. Fino ad ora la barra del barbiere si limita a mostrare attività in corso, ma non riporta alcuna indicazione sulla percentuale di lavoro compiuto. Adesso però, conoscendo la dimensione totale del volume e la quantità di file già catalogati, è possibile lavorare in altro modo. Intendo modificare la finestra in modo che mostri una barra che avanza progressivamente con il procedere delle operazioni; in particolare, la barra esprime percentualmente la dimensione complessiva dei file catalogati nei confronti del totale (ci si mette più a spiegarlo che a farlo).

figura 02

figura 02

In primo luogo, occorre modificare la finestra. Sull'interfaccia, con IB, si tratta solo di spuntare la voce intedeterminate dalle caratteristiche della barra. Con questa operazione diventano significative due altre caratteristiche, il range minimo ed il range massimo, che lascio rispettivamente a zero ed uno.

Scrivo poi un po' di metodi della classe WaitPanCtrl per manipolare opportunamente la barra. Ci sono tre metodi di classe:

+ (void) setMaxValue: (double) maxval;
+ (void) setCurrentValue: (double) currval;
+ (double) getCurrentValue ;

per permettere la gestione tramite istanza condivisa, cui corrispondono nascostamente altri metodi più efficaci:

- (void) localSetMaxValue: (double) maxval;
- (void) localSetDoubleValue: (double) currval;
- (double) localDoubleValue;

La loro realizzazione è talmente semplice da non meritare commenti:

+ (void) setMaxValue: (double) maxval
{
    [ [ WaitPanCtrl sharedProgress] localSetMaxValue: maxval ];
}

+ (void) setCurrentValue: (double) currval
{
    [ [ WaitPanCtrl sharedProgress] localSetDoubleValue: currval ];
}

+ (double) getCurrentValue
{
    return( [[ WaitPanCtrl sharedProgress] localDoubleValue] );
}

- (void) localSetMaxValue: (double) maxval
{
    [ progressBar setMaxValue: maxval ];
    maxValue = maxval ;
}

- (void) localSetDoubleValue: (double) currval
{
    [ progressBar setDoubleValue: currval ];
    currValue = currval ;
    [ progressBar displayIfNeeded ];
}

- (double) localDoubleValue
{
    return( currValue );
}

Dotato di questo armamentario, mi accorgo che il metodo performAddFilesModal va un po' stravolto per tenere conto delle mutate esigenze. Infatti, finora, quando si deve aggiungere al catalogo una lista di volumi, le cose funzionavano in questo modo (vedi il codice riportato in precedenza):

figura 03

figura 03

Mostro la finestra di attesa, apro la sessione modale e poi esamino ordinatamente tutti i volumi. Man mano che trovo un file, faccio fare un giro alla barra.

Adesso le cose cambiano. La finestra con la barra è re-inizializzata ad ogni cambio di volume, pur rimanendo attiva la sessione modale. Quindi la procedura diventa:

Mostro la finestra di attesa, apro la sessione modale e poi esamino ordinatamente tutti i volumi. Ogni volta che considero un nuovo volume, predispongo la barra; man mano che trovo un file, aggiorno la barra considerando la percentuale di file già esaminati.

A questo punto il metodo diventa

- (void)
performAddFilesModal: ( NSArray *)volList
{
    NSModalSession     session ;
    NSEnumerator     * enumerator = [volList objectEnumerator];
    NSString    * volPath ;
    NSDictionary *fsattrs ;
    unsigned long long    totalSpace, freeSpace, usedspace ;

    [ WaitPanCtrl showHide: TRUE ];

    volPath = [enumerator nextObject] ;
    // comincio una sessione modale per l'intera applicazione
    session = [NSApp beginModalSessionForWindow: [ [ WaitPanCtrl sharedProgress] window ] ];
    // eseguo la sessione
    [NSApp runModalSession:session] ;
    while ( volPath )
    {
        VolInfo     * fInfo ;
        fsattrs = [[NSFileManager defaultManager] fileSystemAttributesAtPath: volPath];
        totalSpace = [[fsattrs objectForKey:NSFileSystemSize] unsignedLongLongValue] ;
        freeSpace = [[fsattrs objectForKey:NSFileSystemFreeSize] unsignedLongLongValue] ;
        usedspace = totalSpace - freeSpace ;
        [ WaitPanCtrl setMaxValue: usedspace ];
        [ WaitPanCtrl setCurrentValue: 0 ];
        // mostro la finestra di attesa con apposita stringa
        [ WaitPanCtrl setText: [NSString stringWithFormat: @"Cataloging %@",
            [[ NSFileManager defaultManager] displayNameAtPath: volPath] ]];
        // costruisco l'alberatura dei file a partire dalla scelta
        fInfo = [[ VolInfo alloc ] initTreeFromPath: volPath ];
        // metto a posto le altre informazioni
        [ fInfo setFileSize: totalSpace ];
        [ fInfo setVolSize: totalSpace ];
        [ fInfo setVolFreeSize: freeSpace ];
        [ fInfo setVolSystemNodes: [[fsattrs objectForKey:NSFileSystemNodes] unsignedLongLongValue] ];
        [ fInfo setVolSystemFreeNodes: [[fsattrs objectForKey:NSFileSystemFreeNodes] unsignedLongLongValue] ];
        [ fInfo setVolFileSystemNumber: [[fsattrs objectForKey:NSFileSystemNumber] unsignedLongLongValue] ];
        // aggiungo la cosa al catalogo
        [ dataSource addFileEntry: fInfo ];
        volPath = [enumerator nextObject] ;
        if ( volPath == nil ) break ;
    }
    // finisco la sessione
    [NSApp endModalSession: session];
    // nascondo la finestra di attesa
    [ WaitPanCtrl showHide: FALSE ];
}

Devo anche modificare il metodo initWithPath della classe LSFileInfo in modo che rifletta il nuovo aspetto della barra: al posto della istruzione

[ WaitPanCtrl animate: self ];

presente all'inizio, inserisco l'istruzione

[ WaitPanCtrl setCurrentValue: ([ WaitPanCtrl getCurrentValue ] + [self fileSize])] ;

verso la fine (dopo che ho aggiornato la variabile di istanza fileSize).

La barra funziona in questo modo: ho impostato il valore massimo, all'interno del metodo performAddFilesModal, pari allo spazio occupato da tutti i file all'interno del volume. Nello stesso momento ho anche assegnato zero al valore corrente della barra. Con l'istruzione precedente assegno un nuovo valore al valore corrente, pigliando il valore precedente ed aggiungendo la dimensione del file appena esaminato. La barra (l'oggetto dell'interfaccia, i metodi messi a disposizione dal kit) si occupa in autonomia di calcolare in che percentuale il valore corrente si pone nei confronti del massimo, e disegna la barra più o meno piena.

Se le cose sono state fatte correttamente, alla fine, quando sono stati esaminati tutti i file, le due dimensioni dovrebbero coincidere, e la barra essere totalmente piena (anche se tale situazione rimane per un solo istante,che poi la finestra di attesa scompare).

Un'altra differenza e' possibile

In tutta questa (mia) confusione, è certo che mi sono dimenticato qualche altra modifica apportata al codice. Piuttosto che avermene a male, si utilizza la simpatica applicazione FileMerge (la trovate nella directory /Developer/Applications) che permette, tra le altre cose, di confrontare due intere directory (e sottodirectory). Operate il confronto fra le due directory che contengono tutti i file del progetto, quella relativa al precedente capitolo e quella del capitolo corrente, ed il gioco è fatto.

Licenza Creative Commons
Eccetto dove diversamente specificato, i contenuti di questo sito sono rilasciati sotto Licenza Creative Commons.
Pagina a cura di Livio Sandel (macocoa2012@gmail.com).