Tutti gli articoli di Andrea Tassotti

20141231-181722.jpg

Salvataggio e ripristino di una VM libvirt (parte I)

Con l’adozione definitiva da parte di Red Hat e altri vendor di kvm/qemu quale piattaforma di virtualizzazione con hypervisor l’evoluzione software a contorno di questa tecnologia ha fatto grandi passi, consentendo ad oggi di ottenere una perfetta architettura di virtualizzazione di livello enterprise anche a casa vostra gratuitamente (mediante distro quali Ubuntu, Fedora, ecc).
La scalabilità dell’architettura viene garantita dalla infrastruttura costruita a partire dalla libvirt: un demone per il controllo anche remoto dell’hypervisor, una interfaccia amministrativa grafica e una testuale e altro a corollario.

20141231-181855.jpg

Da questa complessa architettura risulta ancora assente uno strumento integrato e completo per il salvataggio e ripristino delle VM.

Assenza però non significa impossibilità: con strumenti disponibili e standard si può realizzare un semplice ma efficace e sicuro software per la realizzazione di procedure di salvataggio e ripristino automatico di VM gestite da libvirt.

Questo sarà oggetto di questa serie di articoli.

Gli strumenti disponibili
La console amministrativa virsh ha i comandi dumpxml e define atti a salvare e ripristinare la definizione di una VM (un dominio, nella nomenclatura libvirt). Questo è un buon punto di partenza.
Quello che manca è il salvataggio della componente storage della VM. Questa è descritta nel file XML di definizione, ed in ogni caso è uno o più file collocati nel percorso /var/lib/libvirt/images/.
Questo percorso è accessibile solo da utente amministratore (l’hypervisor che ne gestisce l’accesso è un servizio di sistema), e questo diviene un requisito per una costruenda procedura automatica.

Il salvataggio di una VM è dunque la copia della sua definizione e dei file delle immagini disco.
Per realizzarlo è sufficiente dunque copiare questi files in un archivio (magari compresso) su un supporto esterno.

Vedremo della prossima puntata il codice (script shell) con cui realizzare questo compito.

Defrag_icon

Deframmentare Linux ?

Non vogliamo tirarla lunga con un articolo accademico sulla frammentazione e sulle tecniche implementate per minimizzarla sui filesystem della famiglia ext*.

Vogliamo solo farvi giocare concretamente con questo concetto/problema che sfugge all’attenzione degli utenti (e sistemisti Linux) per un assunto ben poco esatto ma comunque in concreto valido: “Linux non soffre di problemi di frammentazione”.

In realtà questo, come abbiamo detto, non è “esattamente” vero (e non vogliamo tediarvi con questioni tecniche), ma è altrettanto vero che il filesystem ext2 (e successori) ha dei buoni anticorpi per risolvere l’influenza della deframmentazione.

Il primo giocattolo per poter cominciare a saggiare con mano l’esistenza di questa realtà è il comando filefrag. Questo è disponibile nel pacchetto e2fsprogs (disponibile sia su distribuzioni RedHat like che Debian like).

Questo comando è capace di rilevare la frammentazione di un file indicato, in numero e identità dei blocchi di indirezione (extents).

A partire da questo e considerando che tra gli artifici del filesystem ext2 (e successori) per evitare la framentazione vi è la tecnica di allocare sequenzialmente i blocchi di un file in fase di creazione, possiamo porre le basi per un nuovo giocattolo.

Prendiamo ora il seguente nuovo assunto: “per deframmentare un file basta copiarlo”.
La nuova copia (per via del modo di allocazione blocchi) sarà “probabilmente” deframmentato. Il “probabilmente” è dovuto al fatto che i blocchi contigui devono essere disponibili. Ecco perché nell’implementazione provvediamo a copiare il file su un filesystem di appoggio (di base /tmp), liberiamo spazio sul filesystem sorgente (per aumentare la probabilità di trovare blocchi consecutivi) e ricopiamo nuovamente il file.

Dato che “repetita iuvant”, ripetere questa operazione quando la deframmentazione non è stata pienamente possibile potrà renderla possibile (a patto che il filesystem sia nel frattempo mutato rendendo disponibile nuove sequenze di blocchi contigui).

Ed ecco il nostro defrag per Linux:

#!/bin/bash
#
#
[ "$USER" != "root" ] && echo Must be root && exit 1

TEMPFILE=`mktemp`
trap 'rm -f $TEMPFILE' EXIT 1 15

echo -n "Raccolgo elenco file(s) ... "
find ${1:-.} -type f 2>/dev/null | grep -v $TEMPFILE >$TEMPFILE
echo fatto
NFILES=`cat $TEMPFILE | wc -l`
echo "Trovati $NFILES file(s)"

exec 3<> $TEMPFILE
declare -i i
declare -i defrag

i=0
defrag=0
read <&3
while [ -n "$REPLY" ]
do
EXTENTS=`filefrag "$REPLY" | cut -d: -f2 | awk '{ print $1 }'`
if [ ${EXTENTS:-0} -gt 2 ] 2>/dev/null; then
# Usare mktemp -p /altrodisco (se server più spazio)
TEMP=`mktemp`
cp -dp $REPLY $TEMP
rm -f $REPLY
cp $TEMP $REPLY
rm -f $TEMP
defrag=$(( defrag + 1 ))
fi
i=$((i + 1))
echo -en "\r$i/$NFILES ($(( 100 * i / NFILES ))%)"
read <&3
done
echo
exec 3>&-
echo "Processati $i file(s)"
echo "Deframmentati $defrag file(s)"
trap '' 1 15
exit 0

Ovviamente, in quanto giocattolo, non possiamo chiedervi di provarlo in ambienti di produzione. Pur essendo (crediamo) esente da problemi, non è consigliato soprattutto per il suo tempo di esecuzione, che è legato proporzionalmente alla dimensione e numero dei files da ottimizzare.

Non è il migliore degli approcci, ma è capace di ottenere il suo scopo. Soprattutto è capace di mostrarci il motivo nascosto del perché in Linux abbiamo pochi problemi di frammentazione (la copia dei file che ogni tanto avviene o la creazione degli stessi sono alla base della deframmentazione che avviene fuori dal nostro sguardo)

A noi bastava fornire una fascinazione riguardo il problema.

 

200px-X11.svg

GDM: Niente panico !

Se la nostra sessione grafica si rifiuta di autenticarci attraverso Gnome Display Manager, e ci ritroviamo di nuovo una bella richiesta di credenziali, niente panico !

Prima di tutto vediamo di accedere in console testo: premiamo la combinazione di tasti “Ctrl+Alt+F1” ed inseriamo le nostre credenziali.

Se la registrazione delle nostre credenziali va a buon fine e accediamo alla nostra sessione testo, allora il panico deve rimanere un lontano ricordo. Siamo ad un passo dalla soluzione.

Una rapida occhiata al log potrebbe rilevare il seguente messaggio:

lightdm: pam_succeed_if(lightdm:auth): requirement "user ingroup nopasswdlogin" not met by user "mioutente"

Ottimo!

Nel 99.99% dei casi il problema è dovuto alla corruzione del file ~/.Xauthority.

I motivi possono essere diversi, ma nella maggioranza dei casi è una interruzione di alimentazione del sistema durante una sessione grafica in corso (avete ritrovato il vostro sistema spento?).

Il file ~/.Xauthority è parte del metodo di autorizzazione “cookie-based” utilizzato dal server X; dei diversi schemi di autorizzazione supportati dal server X il GDM supporta solo lo schema MIT-MAGIC-COOKIE-1.

Il “biscotto magico” (magic cookie) è un arbitrario insieme di dati definiti (dal comando xauth) per un server X che ciascun programma client deve dimostrare di conoscere (inviandone una copia).

Per una configurazione workstation, in cui il server X è disponibile per una singola sessione utente, il cookie viene generato all’avvio del server X dedicato alla sessione del singolo utente, pertanto copia del cookie viene posta nella home directory dell’utente nel file .Xauthority.

utente@ubuntu01:~$ od -c .Xauthority
0000000 001  \0  \0  \b   u   b   u   n   t   u   0   1  \0 001   1  \0
0000020 022   M   I   T   -   M   A   G   I   C   -   C   O   O   K   I
0000040   E   -   1  \0 020 340 224 367 025   F   8 034   z 343 205 177
0000060 311   m   s   _ 225 001  \0  \0  \b   u   b   u   n   t   u   0
0000100   1  \0 001   0  \0 022   M   I   T   -   M   A   G   I   C   -
0000120   C   O   O   K   I   E   -   1  \0 020   3 341 203 317 206 207
0000140 212   }   }   {   s 331   : 355 262 343
0000152

E se non si riesce ad entrare in sessione testo?

Allora PANICO ! …  😯

no, si scherza! 😆

Questo lo vedremo in un prossimo post.

tux-banner

BASH: il problema del costrutto while e OpenSSH

Capita spesso in BASH di realizzare un costrutto che iteri il comando OpenSSH; capita spesso che questo avvenga a partire da una WHILE READ in quanto questo non è altrimenti sostituibile con un FOR o con un parallel-ssh (es. il file di forma tabellare fornisce più di un parametro a OpenSSH).

Esempio

while read HOST USER CMD; do ssh $USER@$HOST "$CMD"; done < lista

Ebbene, quello che otteniamo è l’esecuzione dell’OpenSSH solo in corrispondenza della prima riga del file senza alcun tipo di evidenza di errore. Nella magiorparte dei casi viene data la colpa al costrutto WHILE o al comando READ.
Nulla di più falso!

Cosa accade in realtà?

Nella realtà accade che il comando OpenSSH legge da STDIN, e il corpo istruzioni del costrutto WHILE READ non maschera lo standard input del costrutto, pertanto i comandi all’interno del corpo istruzioni possono attingere al medesimo STDIN. Quindi alla prima esecuzione di OpenSSH, questo consuma l’intero STDIN, lasciando la successiva iterazione priva di altri dati, portandola conseguentemente a corretta conclusione.

Soluzione 1
Utilizzare l’opzione -n del comando OpenSSH

while read HOST USER CMD
do
ssh -n $USER@$HOST "$CMD"
done < lista

Soluzione 2 (valida per somandi anche differenti da OpenSSH)

while read HOST USER CMD
do
ssh $USER@$HOST "$CMD" < /dev/null
done < lista
tux-jail-banner

Rompere una jailroot

A volte capita che su appliance o semplicemente su sistemi con sicurezza molto elevata il nostro utente sia collocato in una jailroot, ossia in un ambiente isolato dal root filesystem e con un ristretto numero di comandi disponibili.

Ovviamente stiamo parlando di una situazione in ambiente Linux (ovvero un qualsiasi sistema basato su un kernel linux, ed in particolare superiore o equivalente alla version 2.4).

Ebbene non essendo possibile mantenere questo isolamento  (per varie ragioni, ma essenzialmente per la necessaria gestione dei processi) per quanto riguarda il filesystem virtuale /proc, questo ci consente di rompere parzialmente l’isolamento della jailroot e conoscere molto del sistema reale che l’isolamento presunto ci voleva nascondere.

Essenzialmente la rimozione di molti comandi amministrativi (e molte utilità) vorrebbero impedire la lettura di quelle configurazioni che di norma potrebbero essere lette (ma non manipolate) da un utente normale; altre configurazioni non sono accessibili per la sicurezza intrinseca del filesystem. Ma stiamo parlando delle configurazioni statiche. La configurazione in essere (running) risiede (per molte componenti del sistema essenzialmente) nel kernel, ed il kernel è accessibile tramite il filesystem virtuale /proc, in moltissime sue parti anche da utenti non privilegiati.

Mediante questo assunto possiamo portare quindi due esempi su come accedere a informazioni quali tabella di routing (in assenza del comando route) oppure alla lista dei processi (in assenza del comando ps) facendo uso solo del filesystem virtuale /proc e di soli comandi interni ad una bash.

Tabella di routing

Se non ci hanno voluto fornire il comando route, possiamo ovviare mediante:

while read IFA DST GW FL REF USE MET MSK MTU WIN IRTT do
if [ $IFA != "Iface" ]; then
echo -ne "$IFA\t ${DST:4:2}${DST:6:2}${DST:2:2}${DST:0:2}"
echo -e "\t${GW:4:2}${GW:6:2}${GW:2:2}${GW:0:2}"
fi
done < /proc/net/route

Unico neo di questo approccio è il non poter contare su un comando tipo ipcalc per tradurre da formato esadecimale a dotted-quad. Però questo può essere fatto in un secondo momento: quello che conta è che il dato è perfettamente accessibile.

Lista processi

Se non ci hanno voluto fornire il comando ps,  possiamo ovviare con:

 for pid in /proc/[0-9]* do
[ -f $pid/cmdline ] && read CMD < $pid/cmdline while read TAG VALUE REST; do
[ "$TAG" = "Pid:" ] && echo -ne $VALUE"\t"
[ "$TAG" = "PPid:" ] && echo -ne $VALUE"\t"
[ "$TAG" = "Name:" ] && echo -ne $VALUE"\t"
[ "$TAG" = "State:" ] && echo -ne $VALUE $REST"\t"
[ "$TAG" = "Uid:" ] && echo -ne $VALUE"\t"
done < $pid/status
[ -n "$CMD" ] && echo -n $CMD
echo done

Potremmo continuare a ricercare e decodificare informazioni da /proc.

Ma è sufficiente per indicare il senso di questo approccio.

cocoa_cup

Introduzione a Cocoa – Parte III

KVO, KVC & Binding
Key-Value Observing (KVO) è un meccanismo che consente agli oggetti di ricevere notifiche su cambiamenti di specifiche proprietà di altri oggetti.

E’ basato sul protocollo informale NSKeyValueObserving.

Le proprietà osservate possono essere semplici attributi, relazioni uno-a-uno o uno-a-molti. Nel contesto MVC, questo meccanismo è particolarmente importante in quanto consente agli oggetti della vista di osservare i cambiamenti negli oggetti del modello attraverso il livello controllo.
Essenziale in questo la tecnologia Cocoa Bindings.

Cocoa fornisce molti oggetti della capacità di essere osservati attraverso il protocollo NSKeyValueObserving.

Key-Value Coding
Key-Value Coding (KVC) è un meccanismo per accedere in modo indiretto agli attributi e relazioni degli oggetti usando identificatori stringa.
kvc

Tecnica sottointesa da molti meccanismi e tecnologie Cocoa; particolare rilevanza ha invero nella programmazione di applicazioni Core Data, con uso intensivo di binding, e altro.
Il meccanismo KVC è reso possibile da protocollo informale NSKeyValueCoding.
Due metodi di questo protocollo (valueForKey: e setValue:forKey:) sono particolarmente importanti in quanto consentono di leggere e scrivere valori delle proprietà di oggetti conformi mediante una chiave di accesso nominale (una stringa con il nome della proprietà). NSOject fornisce una implementazione base di questi metodi. Specializzazioni sono possibili.
Implementare questi metodi può significare dotare le classi di proprietà “virtuali”, ossia non corrispondenti a effettivi attributi negli oggetti, ma “calcolati” o comunque gestiti in modo meno diretto (composti da più proprietà, ecc).
Stabilire collegamenti tra oggetti via KVO è una capacità di Interface Builder.

Cocoa e metafore di interfaccia grafica
Cocoa trova soluzioni per le seguenti metafore:

  • gestione finestre
  • menu statici vs dinamici
  • undo e redo
  • drag & drop (vedi protocollo NSTableViewDataSource)

Cocoa, Applicazioni e Interface Builder
Nell’architettura complessiva di un’applicazione Cocoa entra in gioco anche il concetto di “bundle”.
L’applicazione MacOSX è un insieme (bundle, per l’appunto) di risorse: non solo il codice eseguibile, ma anche descrizioni formali dell’interfaccia, immagini, dati, ecc.
La costruzione di questo insieme è demandata a XCode; nello specifico vendono posti insieme il prodotto della compilazione ed in particolare tutto quanto è compreso nella cartella Resource e del progetto corrente.

In particolare in questa cartella è presente un NIB (ad oggi in formato binario, dunque si parla di XIB, ma per quel che segue è del tutto equivalente) un archivio di informazioni prodotto dal programma Interface Builder che, come appare ovvio, riguarderanno la descrizione formale delle componenti di interfaccia grafica.
xib

In realtà la cosa è più generica: nella maggiorparte dei casi Interface Builder gestisce informazioni per l’interfaccia grafica, ma nei casi pratici può qualcosa di più.
Quando si parla di informazioni per l’interfaccia, in realtà si deve parlare propriamente di istanze di oggetti di classi Cocoa (in particolare di ApplicationKit) capaci di astrarre concetti come finestra, menu, casella di testo, ecc.
Interface Builder dunque istanzia (crea) oggetti di interfaccia: la descrizione dell’interfaccia dunque altro non è che un “contenitore di istanze di oggetti”, un concetto che apparentemente sembra una cosa strana ma che è semplicemente spiegabile introducendo il pattern di programmazione della serializzazione di oggetti.
Interface Builder istanzia dunque oggetti Cocoa (ogni icona nella sua finestra che elenca il contenuto di un NIB è una istanza) mentre l’utente programatore “disegna” l’interfaccia; in seguito li “serializza” nel file NIB/XIB.
Il processo di deserializzazione avviene alla partenza della applicazione che si ritroverà dunque già create tutte le istanze degli oggetti introdotti da Interface Builder.
A ciascun oggetto creato dal NIB verrà inviato il messaggio (leggi “verrà eseguito il metodo”) initWithCoder: e awakeFromNib:.
Eventuali specializzazioni di oggetti (classi utente derivare da qualche classe del framework) potranno governare queste fasi implementando una loro versione di questi metodi.
Ogni icona in Interface Builder rappresenta una istanza di una certa classe.

La seguente icona
object
rappresenta ad esempio una istanza di una classe NSObject o di una versione personalizzata (in questo caso il tipo è da indicare esplicitamente nella finestra inspector di Interface Builder).

Questa è una possibilità di generare istanze di oggetti, anche utente, in maniera visuale e con nascita legata all’avvio dell’applicazione, e dunque esistenti “per costruzione”.

Allocazione di oggetti nel codice ObjC
Diversa la sorte di una istanza di oggetto creata da codice utente.
Tipicamente l’allocazione avviene per mezzo del messaggio statico alloc: inviato ad una classe, a cui segue il messaggio init: (semplice o in qualche variante con argomenti) inviato all’istanza ottenuta.

[[NSObject alloc] init];

Qualora le specializzazioni utente delle classi abbiano la necessità di istanziare oggetti a partire da un NIB e a partire da codice devono implementare entrambi i messaggi initWithCoder: che init: ed eventuali varianti.

Oggetti e messaggi
Gli oggetti Cocoa sono progettati per essere immersi in un circuito di invio reciproco di messaggi atti a costituire quella collaborazione tra oggetti che in object-oriented produce l’essenza di un programma.
E’ dunque importante conoscere, specie nell’ottica della specializzazione, significato e ordine di invio di ciascun messaggio.
Raramente sarà il nostro codice specializzato a inviare messaggi; più frequentemente dovremo analizzare e realizzare le specializzazioni in relazione alla catena di messaggi che “l’applicazione” (vista in una ottica di autonomia di funzionamento) invia alle sue componenti, anche in relazione ad interazione con l’utente (eventi).

Continua

cocoa_cup

Introduzione a Cocoa – Parte II

Cocoa e pattern di programmazione
Cocoa è un framework complesso. Nella sua progettazione ed implementazione sono utilizzate molteplici tecniche di progettazione ad oggetti.
Tra queste ricordiamo l’adozione dei seguenti pattern di programmazione:

  • Modello-Vista-Controller (MVC)
  • Delega
  • Delega di data source
  • Observer – KVC, KVO & binding
  • Notifica

L’adozione di tali modelli di programmazione è praticamente obbligatoria: nella adozione più semplice (senza coinvolgere il progetto di nuove classi) si esplicita nell’individuare specifici oggetti (anche realizzati da utente) capaci di essere attori per uno di questi ruoli.
Cerchiamo di specificare brevemente.

Delega
Il processo di delega è un modo che una istanza di oggetto ha per “traslare” l’implementazione di un suo comportamento (metodo) con il metodo omonimo appartenente ad un altro oggetto (anche di tipo diverso).
Il processo di delega è una forma di composizione (composizione dei comportamenti) tra oggetti.
delega

Una classe che prevede processo di delega per i suoi oggetti avrà un attributo indicante l’oggetto delegato (delegate). Per classi gestite con Interface Builder è sufficiente collegare l’attributo delegato di un oggetto ad un altro oggetto per stabilire la possibilità che il processo di delega abbia luogo.
Nel processo di delega, uno stesso oggetto delegato può essere delegato ad implementare più di un compito (metodo).
Vediamo come esempio quali metodi vengono delegati dalla classe NSTableView ad un suo oggetto delegato:

  • tableView:willDisplayCell:forTableColumn:row:
  • tableView:shouldSelectRow:
  • tableView:shouldSelectTableColumn:
  • selectionShouldChangeInTableView:
  • tableView:shouldEditTableColumn:row:

Modello-Vista-Controllo (MVC – Model/View/Controller)
Pattern di programmazione storico introdotto da SmallTalk e comune a tutti gli approcci ad oggetti alle interfacce grafiche (Java compreso).
Si fonda sulla separazione logica ed implementativa della rappresentazione dei dati dell’applicazione (modello), della componente visuale di interfaccia utente (vista) e della componente che coordina i due e costituisca tutto quanto è eventualmente procedurale nella applicazione (controller).
Una applicazione Cocoa è fortemente conforme al pattern MVC.
Non a caso il framework introduce classi e derivazioni da queste per rappresentare una implementazione delle componenti conforme al modello, con forme e comportamenti prestabiliti a governare le più disparate esigenze.
Per Vista e Controllo abbiamo:

  • NSView
  • NSController

Per il modello si possono usare tutte le classi conforme al modello “container” o “collection” quali:

  • NSArray
  • NSSet

oppure la classe NSManagedObject con l’adozione del framework Core Data (>= 10.4).
mvc

Composizione
La composizione è una tecnica di ADT (Abstract Type Definition) per la composizione delle informazioni degli oggetti. In progettazione object-oriented viene indicata come avente maggiori vantaggi dell’ereditarietà (si prefersce in sostanza descrivere i problemi con relazione ha-un invece che è-un, per ridurre la complessità della gerarchia di derivazione o eventuali effetti collaterali).
Questa filosofia è adottata anche da Cocoa.
Alcune istanze di classe dell’ApplicationKit esistono solo come elementi parte di classi più grandi come “aiuto all’implementazione”.
Un esempio classico è la NSTextFieldCell, che incapsulata in unica istanza in una NSTableView costituisce l’elemento che “disegna” tutte le celle della tabella, riutilizzata più volte dall’algoritmo di disegno della classe NSTableView.
In questo caso si ha anche una scelta architetturale particolare per cui non si incapsulano n istanze di celle per quante sono gli elementi della tabella (aumento di complessità), ma una sola istanza che non è un vero oggetto grafico (non deriva da NSView), ma è uno strumento che implementa il comportamento grafico del “disegnatore”.

Protocolli
Un protocollo è un modo alternativo alla derivazione per rendere possibile l’intoperabilità di classi molto differenti che devono rispondere ai medesimi messaggi.
L’insieme dei messaggi in comune, invece di essere una interfaccia, diviene un protocollo.
Un protocollo diviene così una interfaccia che qualsiasi classe può adottare in maniera indipendente dal progetto.
Esistono due tipi di protocolli in objc: formali ed informali.
Un protocollo informale è sostanzialmente una categoria di NSObject. Contrariamente ad una interfaccia, un protocollo informale non è da implementarsi obbligatoriamente. Prima che sia invocato un metodo, l’oggetto chiamante controlla se l’oggetto destinatario del messaggio implementa il metodo. Dato che i metodi opzionali di un protocollo sono stati introdotti solo nel Objective-C 2.0, i protocolli informali sono stati essenziali nel modo in cui le classi di Foundation e AppKit hanno implementato la delega..

NSObject protocols:

  • autorelease
  • class
  • conformsToProtocol:
  • description
  • hash
  • isEqual:
  • isKindOfClass:
  • isMemberOfClass:
  • isProxy
  • performSelector:
  • performSelector:withObject:
  • performSelector:withObject:withObject:
  • release
  • respondsToSelector:
  • retain
  • retainCount
  • self
  • superclass
  • zone

Un protocollo formale dichiara una lista di metodi che ci si aspetta utilizzata da una classe utente. Hanno una propria sintassi per dichiarazione, adozione e controllo di tipo.

protocollo_formale

Esempio particolare di protocollo: NSTableViewDataSource
Il metodo setDataSource: di una NSTableView vuole un oggetto conforme ad protocollo informale NSTableVewDataSource:

- (void)setDataSource:(id<NSTableViewDataSource>)anObject

L’oggetto che viene impostato come “sorgente dati” sarà in realtà un oggetto sottoposto a processo di delega che risponde al protocollo informale richiesto.

In particolare delega i seguenti compiti (che sono dettati dal protocollo):

Delegate Message Significato
numberOfRowsInTableView: Lettura valori
tableView:objectValueForTableColumn:row: Lettura valori
tableView:setObjectValue:forTableColumn:row: Impostazione valori
tableView:acceptDrop:row:dropOperation: Dragging
tableView:namesOfPromisedFilesDroppedAtDestination:forDraggedRowsWithIndexes: Dragging
tableView:validateDrop:proposedRow:proposedDropOperation: Dragging
tableView:writeRowsWithIndexes:toPasteboard: Dragging
tableView:sortDescriptorsDidChange: Ordinamento

Osservatori e Notifiche
L’Observer pattern è un design pattern utilizzato per tenere sotto controllo lo stato di diversi oggetti.

Observer
Un observer definisce una dipendenza uno-a-molti tra oggetti al fine di notificare e aggiornare automaticamente tutti gli oggetti dipendenti al cambiamento dello stato dell’oggetto osservato. Il pattern Observer è essenzialmente un modello “pubblica e sottoscrivi” in cui un soggetto e i suoi osservatori sono non strettamente accoppiati. La comunicazione può avvenire tra gli osservatori e gli osservati senza che l’uno sappia molto dell’altro.

Notifications
Il meccanismo di notifica in Cocoa implementa un sistema diffusione messaggi uno-a-molti in accoro con il Observer pattern. Gli oggetti di un programma aggiungono se stessi o altri oggetti ad una lista di osservatori per una o più notifiche, ciascuna delle quali viene identificata da una stringa (nome della notifica).
notification
Gli oggetti delegati vengono automaticamente registrati per ricevere messaggi corrispondenti alle notifiche.

Esempio: delegato di una NSTableView. Questi messaggi informano il delegato quando la selezione è stata modificata o quando una colonna viene mossa o ridimensinoata:

Delegate Message Notification
tableViewColumnDidMove: NSTableViewColumnDidMoveNotification
tableViewColumnDidResize: NSTableViewColumnDidResizeNotification
tableViewSelectionDidChange: NSTableViewSelectionDidChangeNotification
tableViewSelectionIsChanging: NSTableViewSelectionIsChangingNotification

 

Continua

EALogoBlack

Archeologia informatica: Interchange File Format

In questo mondo moderno in cui il numero dei formati file proprietari si moltiplica, fa tenerezza pensare ad un lontano passato (era il 1985) quando un colosso dell’informatica ludica (Electronic Arts) definiva uno standard per un formato file universale, tentando così di frenare quello che già all’epoca era una continua corsa alla Babele dei formati.

L’IFFInterchange File Format

L’intento era quello di definire un modello di file capace di contenere diverse componenti multimediali per i suoi software: immagini, suono, animazioni, test, ecc.

Questo standard non è stato più mantenuto e portato avanti, ma molte sono state le influenze e le similitudini che da questo sono state derivate; infatti pur non avendo avuto un futuro autonomo, lo standard è ancora enormemente diffuso (nei suoi eredi e direttamente).

Strutturalmente il file è costruito sul concetto di chunk, ossia un corpo dati identificato da un tipo (una parola a 32 bit di caratteri alfanumerici) e una dimensione (una seconda parola a 32 bit). Se la prima parola è da coniderarsi come sequenza di 4 caratteri alfanumerici interpretabili umanamente con un simbolo, la seconda parola sono un valore a 32 bit in formato big-endian.

Nel mondo Macintosh quei quattro caratteri sono noti come OSType, una parola a 32 bit che ricorre in molte strutture del sistema operativo e in qualche modo ispirate agli stessi principi del’IFF (difficile dire chi ha ispirato chi, data la quasi contemporaneità della nascita del Macintosh con IFF); nel mondo Windows gli sviluppatori chiamano FourCC questi stessi caratteri alfanumerici distintivi di una informazione.

L’uso dell’ordinamento di bit big-endian deriva dalla costruzione di questo standard su architetture Motorola 68000, il cuore dei sistemi Commodore Amiga e Apple Macintosh per i quali Electonics Arts stava sviluppando diverso software di successo (oltre a giochi, programmi di grafica come Deluxe Paint, e di musica come Deluxe Music Construction Set, ecc).

DMCS fu il primo programma capace di sfruttare la capacità di playback a 4 voci con campionamento 8-bit 22Khz dei sistemi Amiga, portando la tecnologia dei campionamenti alla portata di tutti in un epoca in cui una tecnologia analoga era appannaggio di un dispositivo professionale da circa $1700 (vedi Ensoniq Mirage).

La logica di costruzione del formato assomiglia alla codifica TLV (Type/Lenght/Value) dei protocolli di comunicazione:

  • semplici funzioni di analisi per ricercare componenti
  • elementi facilmente trascurabili durante la lettura
  • ordine di apparizione degli elementi nel file non importante
  • formato binario delle informazioni

La definizione del formato ha una certa ricorsività basata su tre strutture basilari: il ‘FORM’, il ‘LIST’ e il ‘CAT ‘. Tre identificatori di altrettanti chunk contenitori che rappresentano un formato specifico (identificato a sua volta da un altra parola 32-bit sempre simbolica), una lista di formati coordinati e un gruppo di formati non meglio correlati.

Lo standard prevedeva in qualche modo un pubblico registro degli identificatori dei chunk dati al fine di evitare collisioni (Commodore ne tenne uno di tutti i formati introdotti da software per le sue macchine); il registro doveva tener conto anche delle specifiche sul dato, al fine di rendere effettiva l’interoperabilità dei sistemi che adottassero il formato.
I vari chunk “registrati” in realtà spesso incorporavano dati destinati ad architetture specifiche e/o comunque molto legati a software o a sistemi operativi (è il caso di immagini HAM – Hold & Modify, una specifica tecnologia per schede grafiche Amiga): questo è certamente contradditorio rispetto alla possibilità di interscambio, ma di contro la definizione pubblica dei chunk poteva comunque consentire l’implementazione di una qualche forma di traduzione.

In ogni caso interpretare un formato file non significa poter gestire i dati: questo è vero anche nel caso dell’XML, pertanto IFF non si trova da solo in questo !

Una particolare implementazione di IFF che è sopravvissuta al tempo è il formato AIFF (Audio Interchange File Format) di Apple.
Un altro erede diretto del IFF è certamente il RIFF (Resource Interchange File Format) di Microsoft e IBM: RIFF è esattamente un IFF  che usa una endianess little-endian (per processori Intel). E’ il formato dei file AVI e WAV.

Tra le similitudini possiamo invece annoverare:

  • il resource fork dei sistemi Macintosh Classic ha una analoga struttura a chunk e identificazione tramite ID alfanumerici a 32 bit (ma anche alcune differenze)
  • il formato SMF (Standard Midi File, definito dalla MIDI Manufacturers Association) è un IFF mancato; quello che manca è il container FORM. Per il resto rispetta esattamente la stessa filosofia, tant’é che Microsoft ha introdotto il chunk RMID per incapsulare un SMF in un formato RIFF.

Molte idee dell’IFF rimangono ancora oggi in formati come XML (ad esempio la possibilità di saltare intere componenti di informazioni quando non se ne conosce il significato o non si ha modo di interpretarli), così come l’idea di marcare sezioni di informazioni con identificatori di 4 caratteri alfanumerici e lunghezza (vedi estensione ID3v2 al formato MP3).

Benché la sua definizione legata ad architetture a 32 bit ne impedisca l’utilizzo per dimensioni di dati/file superiori ai circa 4GB, la sua meccanica di funzionamento lo colloca ancora ai primi posti nella semplicità d’uso e implementazione.
Il concorrente diretto per gli stesi scopi è certamente l’XML (almeno questa è la vulgata): questo ha il vantaggio della portabilità (come IFF), ma l’implementazione di un buon parser XML non è meno onerosa (anzi!) di un semplice e completo parser IFF. E IFF è più rapidamente interpretabile e compatto nelle dimensioni.

Mi domando perché le buone idee spesso vengono abbandonate a favore di presunte novità, mode, interessi diversi ! Strane domande mi faccio !

cocoa_cup

Introduzione a Cocoa – Parte I

Con questo post vogliamo iniziare una serie di articoli più astratti sulla programmazione Cocoa, quasi un tutorial.

La programmazione in ambito nativo MacOSX si fonda su due pilastri: Objective-C e Cocoa Framework. Questo documento non si prefigge lo scopo di coprire lo studio particolareggiato di queste tecnologie (non potrebbe), ma di fornire un rapido accesso allo sviluppo software MacOSX.
Per poter governare la programmazione in ambito MacOSX occorre conoscere inoltre gli strumenti XCode, ivi compresa la componente Interface Builder per lo sviluppo delle interfacce grafiche (prima di Xcode 4 era una applicazione esterna).
La programmazione Objective-C è una programmazione multi-paradigma: l’Objective-C (da adesso objc) si fonda sul linguaggio C (programmazione procedurale) e costruisce su questo una sovrastruttura per il paradigma Object-Oriented (con l’introduzione di specifiche sintassi).
Per l’implementazione del paradigma ad oggetti, l’objc introduce un runtime environment che costituisce un “motore” (utilizzato da ogni applicazione costruita in objective-c) atto a realizzare la metafora dei messaggi e la componente dinamica del modello ad oggetti del linguaggio (polimorfismo, dynamic binding, ecc).

L’adozione del modello ad oggetti viene supportata da alcuni framework: primo tra tutti Foundation (Origina da NextStep Foundation library), responsabile della costruzione organica del modello a partire dalla classe NSObject.

  • Tutte le classi di questo framework hanno nomi con prefisso “NS”, che intende ricordare l’origine in NextStep.

Nel contesto grafico MacOSX il principale framework adottato è Cocoa: questo estende Foundation con l’adozione di tutto un insieme di framework tematici. Anche in questo caso le classi hanno nomi con prefisso “NS”.
Il corollario di framework che costituiscono Cocoa si posizionano su diversi livelli di una gerarchia di astrazione delle componenti del S.O. e altro: ogni framework è dato per semplificare e riutilizzare un progetto relativo a situazioni specifiche (Core Audio, Core Graphics, Core Animation, Core Data, ecc).

  • Le classi appartenenti a questi framework possono essere riconosciute sempre attraverso la convenzione dei prefissi, ma non in senso rigoroso (es. quelle Core Data hanno esempio prefisso “NS”, mentre Core Graphics ha “CG”).
  • La forma dei prefissi è legata a fattori storici e gerarchici.

Tutti questi framework sono da riferirsi ad uno SDK relativo alla versione software del sistema operativo: un SDK ha un numero di versione pari al numero di versione del S.O. (es. SDK per Tiger è 10.4, per Leopard è 10.5, per Snow Leopard 10.6, per Lion 10.7, Mountain Lion 10.8, Mavericks 10.9).

API Compliance
Un’applicazione può essere compilata solo per uno specifico SDK (selezione che avviene nella configurazione del “target” in XCode); si può adottare una programmazione parametrica (ovvero con direttive condizionali di preprocessing) al fine di modellare il codice per applicare modifiche atte a compilare per specifico SDK e modulare le eventuali differenze rispetto ad un altro.

#if (MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_5)
// codice per Leopard e oltre
#else
// codice pre-Leopard
#endif

ABI Compliance
Un’applicazione può essere invero compilata per architetture differenti (PPC vs Intel): questo è l’adozione dei concetti di Universal Binary.

  • In questo caso Lion ha creato uno spartiacque: la soppressione di Rosetta e l’impossibilità di sviluppare codice per PPC ha impedito lo sviluppo in un unico progetto per più piattaforme.

Cocoa e GNUStep
GNUStep è una versione “open” di NextStep, e per questo ha molte similitudini con Cocoa.
Un codice realizzato in Cocoa è “riutilizzabile” in contesto GNUStep. Gli strumenti di sviluppo di GNUStep sono però gli originali Project Builder e Interface Builder di NextStep e divergono dunque per alcuni aspetti dalle novità introdotte da MacOSX e da XCode.
Riutilizzare il codice è possibile ma occorre riportarlo all’interno di un progetto GNUStep.
Il vincolo maggiore è il livello di compatibilità rispetto ai vari SDK disponibili nel mondo MacOSX: attualmente GNUStep è paragonabile a Panther, ossia 10.3. Un software calibrato per 10.4 oppure adottante framework particolari come Core Data, Core Audio o Core Animation, difficilmente saranno “portabili” nel mondo GNUStep.
Lo saranno solo se soluzioni a quelle differenze vengano prodotte dal progetto GNUStep stesso oppure in modo autonomo da chi sviluppa il software.

Cocoa runtime environment
Possiamo parlare di un vero e proprio runtime environment anche nel caso del framework Cocoa.
Il concetto di “applicazione” è proprio del framework, il quale offre un suo modello e una implementazione. In particolare il progetto riguardante il concetto di applicazione è parte dello specifico framework ApplicationKit.
Possiamo così leggittimamente parlare di “applicazioni Cocoa” contro “applicazioni BSD“, benchè in entrambi i casi si possa adottare objc e Foundation.
Cocoa suddivide il concetto di “applicazione” in due possibili modelli alternativi:

  • applicazione
  • applicazione basate su documento

Applicazione
Una applicazione incentrata su un ciclo di eventi che pongono in relazione gli elementi grafici presenti (finestre, menu, ecc).

Applicazione basata su document
Una applicazione il cui fulcro è un “gestore di documenti”; ciascun documento ha una propria finestra di interfaccia e una propria base dati di riferimento. Ciò che viene implementato in riferimento ad un documento astratto viene replicato (nei comportamenti) per ciascun docuento “aperto”.

Entry point di un programma Cocoa
Proprio in ragione della presa in carico da parte del framework del progetto “applicazione”, non esiste un “corpo principale del programma” come in altri ambienti di sviluppi.
Per tutte le applicazioni Cocoa si ha un unico “punti di inizio del programm” che è l’esecuzione della funzione NSApplicationMain(). Questo viene precostruito dal progetto modello in XCode.

int main(int argc, char *argv[])
{
return NSApplicationMain(argc,  (const char **) argv);
}
  • Ovviamente è data la possibilità di interferire con le caratteristiche base dell’applicazione (intesa come flusso principale di programma) attraverso alcune classi di ApplicationKit (utilizzando metodi e attributi di classe specifici) e implementando una versione ad-hoc della funzione NSApplicationMain().

Classi che riguardano l’applicazione sono: NSApplication, NSBundle, NSApp.
La complessità dell’intera architettura è tale che non può essere ripercorsa da questo documento introduttivo.

Continua.

articolo_itunes

Ancora su iTunes e playlist

Con l’ultimo post su iTunes ho potuto riabbracciare la programmazione AppleScript che avevo tralasciato per lunghi anni, da quando lavoravo ancora con un OS Classic 7.5.5 !

Ovviamente ci ho ripreso gusto, pertanto eccomi di nuovo con un altro post che prosegue in qualche modo il precedente.

L’idea di questa nuova estensione alle funzionalità di iTunes mi viene dal passato, ossia da quei lettori di CD Audio che erano parte degli impianti Hi-Fi degli anni 90 e che oggi sono solo appannaggio di impianti detti di “Hi-Fi esoterico“, dove un componente di tal genere può costare anche fino e oltre i 1000€! Oggi che il mondo musicale è sempre più liquido, un lettore CD Audio è qualcosa rara, pertanto per molti sono solo un ricordo.

In ogni caso quei dispositivi avevano una funzione (almeno quelli di una certa fascia) interessante che non si è diffusa nel mondo liquido. La scansione rapida delle tracce.

La scansione prevedeva l’ascolto dei primi 10 secondi di ogni traccia del disco.

Ebbene abbiamo implementato due script per iTunes che mimano questa funzione sulla playlist selezionata.

Il primo è esattamente a scansione dall’inizione di ogni traccia della playlist selezionata; il secondo invece è una variante che mi è utile nella costruzione di playlist per sonorizzazioni: consente l’ascolto degli ultimi 10 secondi di ciascuna traccia della playlist. Questa funzione è molto utile per trovare i migliori collegamenti tra fine e inizio di tracce che devono susseguirsi in un mix continuativo.

Rammentiamo (cosa non fatta nell’altro post) che per integrare i nostri AppleScript in iTunes sarà sufficiente collocarli in /Library/iTunes/Scripts.

Ovviamente anche in questo caso gli script sono un abbozzo funzionante che può essere migliorato a vostro piacere.

--
-- Scan mode 1
--
--	Head of track for 10 seconds
--
property thePlaylist : ""
property scanTime : 10

tell application "iTunes"
	-- Per le sorgentti che non sono una playlist (come Film, ecc)
	try
		set thePlaylist to (view of front window)
	on error number errn
	end try

	if thePlaylist ≠ "" then
		repeat with theTrack in (tracks of thePlaylist)
			play theTrack
			repeat while player position < scanTime
				delay 0.2
			end repeat
		end repeat
	end if
end tell
--
-- Scan mode 2
--
--	Tail of track for 10 seconds
--
property thePlaylist : ""
property scanTime : 10

tell application "iTunes"
	-- Per le sorgentti che non sono una playlist (come Film, ecc)
	try
		set thePlaylist to (view of front window)
	on error number errn
	end try

	if thePlaylist ≠ "" then
		repeat with theTrack in (tracks of thePlaylist)
			play theTrack
			set player position to (duration of theTrack) - scanTime
			repeat while player position < (duration of theTrack) - 1
				delay 0.2
			end repeat
			pause
		end repeat
	end if
end tell