lunedì 30 dicembre 2013

Sfondi di BING su Gnome Shell



 

(Scripta Manent IV)

BING e i suoi sfondi 

Bing, come quasi tutti sanno, è il motore di ricerca di casa Microsoft.

Per motivi personali, di recente ho voluto provare questo mondo alternativo di servizi, la cui qualità non vuole essere oggetto di questo eloquio.

Una cosa che però indubbiamente colpisce di BING è la bellezza dei suoi sfondi, che cambiano quotidianamente e che sembrano presi da un vastissimo pool di immagini di alta qualità.

Probabilmente l'usabilità di tali sfondi è vincolata da quintali di clausule e copyright, quindi dovrebbero essere prudenzialmente usati solo in sede alle pagine di Bing, ma pare anche come sfondi per l'ultima versione del loro sistema operativo.

D'altra parte non se la prenderà la grande M, se motivato da fervente ammirazione e dalle quattro pagine di questo blog autarchico, nell'intenzione di far capire ciò che si può fare con power sh... ehm con bash , suggerirò un piccolo script per cambiare di conseguenza gli sfondi di Gnome Shell .

Passo dopo passo

Per essere certo che la procedura sia valida anche in futuro e anche, perché no, per altre pagine e altri sfondi a venire, spiegherò passo dopo passo "come lo feci".

Come lo feci

Prima di tutto ci serve un sistema per interrogare il sito di Bing, nell'intenzione di capire se lo sfondo è cambiato.

Un uomo di poche parole, si accontenterebbe di una voce in crontab o di un semplice sleep, ma i più arditi sapranno che ormai ben pochi spengono i laptop o i netbook e la maggior parte usano il comodissimo "suspend".

E qui casca lo sleep, nel senso che se mettiamo uno sleep, questo si sospende per un certo tempo, poi si sospende la sospensione quando il computer è sospeso, e alla fine riprende la sospensione, così che ogni sleep dura da T a infinito, e noi non vogliamo che questo accada, vero ?

Se trasformiamo lo script in un daemon di sistema,  non dobbiamo preoccuparci di farlo partire o finire, ci basterà fidarci, quindi scriviamo qualche riga :




#!/bin/bash
OLDTIME=0
 

{
while true
do

  # Ten minutes ...
  /bin/sleep 600

 
  NEWTIME=`date +%s`

  # Try to download after 12 hours only
  if [ $(($NEWTIME-$OLDTIME)) -ge 43200 ]
  then

    OLDTIME=$NEWTIME

    # Place your code here 
    # ..... fi
   
done
} &


Se lanciamo questo script da sh, magari all'interno di rc.local, lui per prima cosa va in background e diventa un daemon, figlio di 1 cioè di init e inizia un infinito loop ad intervalli di 10 minuti.

In questo loop, si legge il valore +%s di date, cioè il numero di secondi assoluti da EPOCH ([GMT]1/1/1970:00:00:00) e si compara, all'inizio con 0 e poi con i valori letti di volta in volta.

Un sistema di questo tipo, ogni 10 minuti circa, a partire dal primo loop, verificherà la scadenza di 12 ore ed eseguirà il codice in mezzo.

Notate bene che al massimo sarà sospeso lo sleep di 10 minuti. Il motivo per cui ne faccio eseguire uno all'inizio e non alla fine del primo loop, è perché il computer appena acceso, se è connesso col Wi-Fi, di solito non è ancora in rete e la rete ci servirà sicuramente per quello che stiamo per fare.

Quello che stiamo per fare è questo :

       ...   
    # Get the bing home page and search for g_img tag
    page=`/usr/bin/curl --user-agent "$USERAGENT" www.bing.com | /bin/grep g_img`
    # Get the token after g_img
    page=${page##*g_img=}
    # Check the url argument
    page=${page#*url:\'}
    # Extract the url until '
    page=${page%%\'*}

    /usr/bin/curl --user-agent "$USERAGENT" "http://www.bing.com/$page" > $FOLDER/current_background.jpg

    ...

Per capirlo bene, dobbiamo innanzi tutto precisare che per il momento Bing fornisce una pagina con codice javascript, che a sua volta contiene degli oggetti json. Un oggetto json si chiama g_img e contiene alcuni attributi, tra i quali il classico url, ossia una stringa json tra apici semplici.

La prima riga legge la pagina da www.bing.com, ed estrae il blocco contenente g_img e lo mette in una semplice variabile di memoria, chiamata page.

Il risultato sarà parecchio garbage con il nucleo di quello che ci interessa, cioè :

... g_img={url:'az/hprichbg/rb/immagine.jpg';id...

Ecco quindi gli abili colpi di bash, per pulire un po` il contenuto :

page=${page##*g_img=}
page=${page#*url:\'} 
page=${page%%\'*}

e che si traducono in :
  1. prendi tutto ciò che c'è dopo g_img= 
  2. di questo prendi solo quello che c'è subito dopo la prima parola url:' e qui notate che ho usato un solo #, cioè il minimo match dell'espressione regolare (il massimo porterebbe in fondo al file)
  3. di quello che hai ottenuto scarta tutto quello che trovi dal primo ' fino alla fine.
Se ci pensate un po` su capite che ora in page abbiamo qualcosa tipo :

az/hprichbg/rb/immagine.jpg

dove al posto di immagine, c'è uno bel URL col nome e la risoluzione dell'immagine stessa, basta accodarlo a al sito originale e lanciare il curl quindi su :

http://it.bing.com/az/hprichbg/rb/immagine.jpg

che scaricherà l'URL in standard output, da noi rediretto in un fantomatico :

$FOLDER/current_background.jpg

Gnome Shell e il suo sfondo

L'immagine di sfondo di Gnome Shell, punta solitamente ad una jpeg specifica.

Nonostante l'immagine sia naturalmente su disco da qualche parte, essa è considerata dinamica, cioè appena la si cambia, Gnome Shell se ne accorge e cambia lo sfondo di conseguenza, con un gradevole effetto fade-out,fade-in.

Per noi la situazione è perfetta, infatti se pensiamo di usare quello che abbiamo appena scritto come daemon di sistema, lanciato da rc.local, in modo che ne possano usufruire tutti gli utenti, possiamo riservarci un posto d'onore per esempio nella /opt , qualcosa tipo /opt/background e lì metterci la current_background.jpg.

Per far questo, presa una qualsiasi genericaimmagine.jpg, si può eseguire da su, cioè da utente root, la seguente operazione :

# mkdir /opt/background
# cp genericaimmagine.jpg /opt/background/current_background.jpg

Ora non ci resta che indicare in /opt/background/current_background.jpg, l'immagine di sfondo corrente e questo lo dobbiamo fare come utente "normale" per esempio usando Nautilus nella cartella /opt/background e cliccare aprendola col normale Image Viewer, dove col menù popup (tasto destro) troviamo una opzione per usare tale immagine come sfondo :



Quando lo script, agirà, sovrascriverà l'immagine e il gioco sarà fatto.

Mettiamo insieme tutto

A questo punto approntiamo lo script completo, che è questo :


#!/bin/bash
# Bing Image Downloader
 

{
 

export OLDTIME=0
export FOLDER=/opt/background

export USERAGENT="Mozilla/5.0"

while true
do
  # Ten minutes loop
  /bin/sleep 600

  export NEWTIME=`date +%s`

  # Try to download after 12 hours only
  if [ $(($NEWTIME-$OLDTIME)) -ge 43200 ]
  then

    OLDTIME=$NEWTIME

    # Get the bing home page and search for g_img tag
    page=`/usr/bin/curl --user-agent "$USERAGENT" www.bing.com | /bin/grep g_img`
    # Get the token after g_img
    page=${page##*g_img=}
    # Check the url argument
    page=${page#*url:\'}
    # Extract the url until '
    page=${page%%\'*}

    /usr/bin/curl --user-agent "$USERAGENT" "http://www.bing.com/$page" > $FOLDER/current_background.jpg

   
  fi

done

} &

notate che ho specificato delle query come Mozilla/5.0, giusto per non far vedere che sto usando curl.

Questo script l'ho salvato come bingimg.sh e inserito in /usr/local/sbin, ovviamente come superuser, quindi ho messo la classica riga in /etc/rc.local :

#!/bin/sh -e
#
# rc.local
#
# This script is executed at the end of each multiuser runlevel.
# Make sure that the script will "exit 0" on success or any other
# value on error.
#
# In order to enable or disable this script just change the execution
# bits.
#
# By default this script does nothing.

...
/usr/local/sbin/bingimg.sh

...
exit 0

Riavviando il sistema dopo 10 minuti l'immagine è cambiata e così via, giorno per giorno ...

E se ...

E se volete salvare le immagini, allora potete giochicchiare come meglio preferite. Per esempio potete mettere nella cartella background una cartella history e con un abile colpo di tee, scrivere al posto del curl che scarica l'immagine, qualcosa tipo :

       ...
    /usr/bin/curl --user-agent "$USERAGENT" "http://www.bing.com/$page" > $FOLDER/current_background.jpg   
# Remote image name
    OUTPUT=${page##*/}

    # Download only if it does not exists 
    if [ ! -e "$FOLDER/history/$OUTPUT" ]
    then
      /usr/bin/curl --user-agent "$USERAGENT" "http://www.bing.com/$page" | /usr/bin/tee $FOLDER/history/$OUTPUT > $FOLDER/current_background.jpg
    fi

    ...

che in più ha il vantaggio di sovrascrivere l'immagine solo se questa non esiste già, e tante altre cose che vi lascio immaginare.


lunedì 26 agosto 2013

Un efficiente backup incrementale con il TAR

Pulizie d'autunno

Si avvicina il tempo dell'autunno ed è il momento di pulizie dei dischi e dell'ambiente circostante i dischi, cioè lo spazio vitale.

Tra i meandri più oscuri di tale spazio vitale, ho ritrovato decine di obsoleti CD e DVD e addirittura dei floppy disk, con tracce di antico codice TurboC scritto da giovane e di altissimo valore sentimentale, ormai semi irrecuperabili.

Ripensandoci mi è venuto in mente di salvare il salvabile da qualche parte, che non significa semplicemente salvarlo su un disco o una chiavetta a sua volta da dimenticare da qualche parte, in attesa che venga perso o cancellato, ma di organizzare un sistema di backup più modulare di quello normalmente usato per il disco e ampiamente discusso in passati articoli.

L'archiviatore ufficiale del mondo Linux come sappiamo è il Tape ARchiver che, dopo il passaggio per diverse strutture e versioni, è e sarà universalmente riconosciuto da tutti i sistemi operativi come uno standard.

Abbiamo anche visto come col tar , grazie al fatto che non si tratta di un semplice archiviatore ma di un serializzatore di oggetti del File System, sia praticamente possibile far di tutto, compreso far volare un pinguino da un computer all'altro.

Qui analizzeremo il tar dal punto di vista del backup incrementale superando alcune sostanziali inadeguatezze e costruendo uno script particolarmente interessante per chiunque voglia memorizzare i propri progetti, anche in modo incrementale, in un singolo file portabile e standard.

Come funziona il tar incrementale ?

Non è mia intenzione causare reazioni eccessive e rabbiose da parte dei puristi dei comandi *nix e nemmeno elevarmi al di sopra delle parti e giudicare severamente strumenti in uso da decenni, ma mi sento di dire che l'utilizzo di tar come sistema di backup incrementale è tecnicamente "un casino".

Sia chiaro che questo è dovuto all'estrema versatilità del comando tar e delle infinite possibilità che ci offre e grazie alle quali vedremo come risolvere parte dei nostri problemi, ma ciò non toglie che il suo comportamento standard sia piuttosto complesso da capire, tanto che molti articoli su Internet, pretendono di spiegarlo ma non sono del tutto corretti.

Vediamo allora di spiegarlo correttamente, cercando di capirne il funzionamento.

Il tar come ho detto, è un semplice serializzatore di oggetti del File System, quindi scansiona i rami del File System e scrive in uno stream una sequenza, che può essere recuperata in modo da ricostruire altrove e/o poi gli stessi rami. L'archiviatore in sé, non comprime ma passa lo stream ad un semplice filtro.

Questo porta ad innumerevoli vantaggi in fatto di versatilità ma anche come fattore di compressione, specie quando si ha a che fare con molti files di testo in quanto, a differenza per esempio da un semplice zip, dove i files sono compressi singolarmente e poi aggiunti, qui i files sono compressi nella globalità e considerato che il buffer di un bzip2 (opzione best) è di circa 1M, consente di scendere ad un entropia infinitesimale.

Per contro si deve dire che a differenza di uno zip, una volta che si ha il prodotto compresso, non è possibile modificarlo (aggiungere, togliere o fare update), senza prima decomprimerlo e ciò può essere molto scomodo se non si ha abbastanza spazio.

Per quanto riguarda il backup incrementale il tar offre un semplice flag denominato --listed-incremental, da non confondersi con --incremental che è riferito al vecchio sistema, e già il fatto che siano due fa capire che tale vicenda è molto travagliata.

Capire cosa fa esattamente questo flag che deve essere usato con un nome file, cioè come :

--listed-incremental=<file>

è importante, perché molti lo trattano come se compisse mirabili operazioni nascoste.

In realtà produce una semplice lista, che non è nemmeno in formato testo ma è una specie di dump binario delle strutture indice, e non dà quasi nessuna indicazione sul file salvato.

È in sostanza una lista simile a quella che si potrebbe ricavare col un semplice find anche se con qualche controllo in più.

Fase 1 - Il primo tarball 

Consideriamo l'ipotetica cartella "saveme".

Per eseguire un tar incrementale si può procedere in questo modo :

tar cvfz saveme-20130826123545.tgz \
     --listed-incremental=saveme-20130826123545.idx \
     saveme


Il prodotto della suddetta operazione saranno ben due files :

saveme-20130826123545.tgz
saveme-20130826123545.idx

Il primo contenente il vero tarball e il secondo con l'indice dei files o lo stato del file system o in qualsiasi altro modo volete chiamarlo.

Per chi se lo chiedesse il valore 20130826123545 si ricava così :

date +%Y%m%d%H%M%S

ed è una comodissima "data ordinabile".

Fase 2 - Aggiornamento

Ipotizziamo di fare alcune modifiche alla cartella "saveme", aggiungendo, spostando e togliendo files.

Per aggiornare il backup ci serve il vecchio indice, cioè l'ultimo che abbiamo salvato.

In caso non volessimo tenere una storia di tutto il file system, ma usare un semplice backup incrementale, allora potremmo usare un indice unico, ma non conviene, in quanto gli indici sono di dimensioni molto ridotte, e con essi è possibile tenersi l'effettiva storia di una cartella e ripristinarla in qualsiasi momento del suo passato

La scelta più semplice è quindi trovare la nuova data e copiare l'indice :

cp saveme-20130826123545.idx saveme-20130826152632.idx 

e quindi

tar cvfz saveme-20130826152632.tgz \
     --listed-incremental=saveme-20130826
152632.idx \
     saveme


Quest'ultima operazione, prenderà in considerazione l'indice appena copiato ed eseguirà il backup dei soli files modificati in base al timestamp, quindi aggiornerà l'indice con il nuovo stato (di tutti i files), con i files in più e omettendo chiaramente quelli cancellati.

E i files presenti diventeranno quattro :

saveme-20130826123545.tgz
saveme-20130826123545.idx
saveme-20130826152632.tgz
saveme-20130826152632.idx

Nonostante la considerazione che i tarball sono comunque semplici tarball e possono essere estratti indipendentemente dagli indici, il fatto che files aumentino a dismisura e che tali indici restino separati, non è affatto una cosa positiva e introduce evidenti svantaggi dal punto di vista della gestibilità del backup e nella sua archiviazione.

Fase 3 - Restore

Per ripristinare lo stato di un progetto in un certo istante temporale, per esempio le 15.26.32 del 26/08/2013 (cioè l'ultimo), dobbiamo procedere solo sull'indice interessato ed estrarre tutti i backup fino a questo indice, in questo modo :

tar xvfz saveme-20130826123545.tgz \
     --listed-incremental=saveme-20130826
152632.idx \
     saveme

tar xvfz saveme-20130826152632.tgz \
     --listed-incremental=saveme-20130826
152632.idx \
     saveme


La logica di questa operazione è molto particolare, infatti il tar usa l'indice, solo per cancellare dal file system i files che non si trovano nell'indice stesso.

Per spiegarci meglio, immaginiamo che al primo backup in saveme, ci fossero tre files , A , B  e C .

Immaginiamo che prima di fare il secondo backup, avessimo tolto B , aggiornato C e aggiunto un file D.

L'indice 20130826152632 a questo punto, conterrebbe l'elenco : A, C, D .

Durante l'estrazione del primo tarball, che conterrebbe A,B e C , tutti i files sarebbero estratti, in quanto non ci sarebbe nulla da cancellare, curiosamente anche B che non si trova nell'indice.

Al secondo passaggio B sarebbe cancellato, e sarebbero estratti sempre tutti i files contenuti nel secondo tarball e cioè C che sarebbe così sovrascritto e come file nuovo, ripristinando lo stato del sistema.


Quindi l'archiviatore, non ricostruisce lo stato del sistema ma cerca di ripristinarlo con strani passaggi cancellando e sovrascrivendo più volte, con la scusa che l'hard disk di solito è trasparente a queste azioni (grazie alle fantastiche buffer cache).

Bisognerebbe chiedere a quelli che usano i DOM o in generale le Flash, magari con appositi file systems a rotazione se sono d'accordo, ma come al solito non si può pretendere tutto dalla vita.

Un po`d'ordine nel chaos

Cercando di comprendere il tutto e di fare un po`d'ordine nel chaos, mi rifaccio a uno dei primi progetti che ho descritto tra queste pagine, cioè Time Archive che usa un sistema di patch differenziali, sui tar binari di progetto e che si è rivelato ottimo per piccole modifiche, ma piuttosto dispendioso su grosse variazioni.

L'idea è quella di introdurre praticamente gli stessi comandi, anzi volendo di creare una nuova versione di Time Archive , e di chiamare quella vecchia per esempio DiffArchive o qualcosa del genere, usando semplicemente e solamente il concetto di tar incrementale .

I comandi da implementare sono i 4 di Time Archive , cioè U per update, R per rollback , X per extract e T per trace.

Gli scopi di questo progetto sono interessanti :
  1. Definire uno standard per i backup.
  2. Automatizzare le procedure.
  3. Mantenere l'affidabilità e il formato del tar con compressione bzip2.
  4. Usare un solo file per ogni progetto che mantenga la storia e le versioni.
In particolare per il punto 4, useremo un semplice tar non compresso che conterrà tutti i tar compressi, in ordine con indici e descrizioni. Il fatto che non sia compresso implicherà la sua facile modificabilità e affidabilità.

TimeArchive II

Il comando Update

Il comando update per questo timearchive si definisce come nella precedente versione :
timearchive U <archive> <folder> [ "<description>" ]
dove archive è il nome dell'archivio arbitrario che dovrà ospitare tutti gli altri archivi, folder è la cartella da archiviare e description è una breve descrizione opzionale della versione corrente.

Dal punto di vista logico, si individuano innanzi tutto il folder e l'archivio in termini assoluti, poi si può procedere con un codice come questo :

 tmpdir=`mktemp -d`
 todate=`date +%Y%m%d%H%M%S`

 if [ -e "$archfull" ]
 then
    lastidx=`tar tf "$archfull" | sort | grep idx | tail -n 1`
    tar xf $archfull $lastidx -O >$tmpdir/$todate.idx
 fi
 

 tar cfj $tmpdir/$todate.tb2 -C "${dirfull%/*}" --listed-incremental=$tmpdir/$todate.idx "${dirfull##*/}"
 

 # Update archive
 echo $4 >$tmpdir/$todate.txt
 tar -C $tmpdir -rf $archfull $todate.tb2
 tar -C $tmpdir -rf $archfull $todate.txt
 tar -C $tmpdir -rf $archfull $todate.idx
 rm $tmpdir/*
 rmdir $tmpdir

In sostanza si crea una cartella temporanea di lavoro e si determina la data. Se l'archivio esiste già, è estratto direttamente dal tar l'ultimo indice salvato, in questo modo :

    lastidx=`tar tf "$archfull" | sort | grep idx | tail -n 1`

Se invece l'archivio non esiste, l'indice sarà creato.

Poi c'è un tar che esegue il backup incrementale e alla fine i due files tarball , indice e un file contenente semplicemente l'echo della description, sono appesi al file (-rf) archivio principale, che se non c'è è ovviamente creato sul momento.

Quindi per ogni versione avremo l'aggiunta di tre files ad un file archivio principale, facilmente verificabile con :
tar tvf archive

Il comando eXtract

La sintassi del comando è :

timearchive X <archive> <folder> [ <date> ]
Dove archive è il solito archivio, folder  è il folder dove sarà estratta la cartella del progetto indicata prima, cioè se la cartella era per esempio Pictures indicando /tmp , troveremo in /tmp la Picures. Per ripristinarla nella cartella corrente basta indicare come folder il "." .

Infine date è la data che si vuol ripristinare ed è opzionale.

Estrarre il contenuto dell'archivio richiede l'estrazione di tutti i tarball fino ad una data specifica ed è espressa dal seguente blocco di codice :

  tmpfile=`mktemp`
  if [ -z "$4" ]
  then
    lastidx=`tar tf "$archfull" | sort | grep idx | tail -n 1`
  else
    lastidx=`tar tf "$archfull" | grep "$4.idx"`
    if [ -z "$lastidx" ]
    then
       echo "Date does not match"
       exit
    fi
  fi
  tar xf $archfull $lastidx -O >$tmpfile
  cd "$dirfull"
  tar tf $archfull | grep tb2 | tarext "$archfull" "$tmpfile" "$4"
  rm $tmpfile
A parte la creazione del file temporaneo che ci servirà per l'indice, con  -z "$4" si verifica se il quarto parametro e cioè la Date è vuota o meno.

Se è vuota si ricava l'ultimo indice, come nel caso dell'update, altrimenti l'indice è cercato esplicitamente (e se non si trova si ha un errore).

Estratto l'indice nel file temporaneo si analizza semplicemente il contenuto dell'archivio, si ordina in ordine crescente di data e si passano i nomi dei relativi tarball ad una funzione chiamata tarext .

Il tarext fa questo :
function tarext
{
  while read a
  do
    tar xfp "$1" "$a" -O | tar xj --listed-incremental="$2"
    if [ ! -z "$3" ]
    then
      if [ "$3.tb2" == $a ]
      then
        return
      fi
    fi
  done
}
ossia prosegue estraendo coerentemente tutti i tar sull'indice indicato fino a quando ha estratto quello giusto (che può essere anche l'ultimo), ricostruendo così lo stato in un preciso istante di tempo.

Notare che il tarball, in questo caso non è copiato su disco ma estratto in una pipe e il folder ricostruito senza occupare ulteriore spazio.

Il comando Rollback

Il comando rollback ripristina l'archivio ad una data prescelta che significa la cancellazione di tutti le successive versioni. È un comando che si usa raramente,  per esempio quando si sbaglia a fare un update.

La sintassi è semplicemente:
timearchive R <archive> <date>
Il funzionamento è piuttosto simile a extract :
if [ -z "$3" ]
  then
     echo "Missing rollback date"
     exit
  fi   
lastidx=`tar tf "$archfull" | grep "$3.idx"`
if [ -z "$lastidx" ]
then
   echo "Date does not match"
   exit
fi
tar tf $archfull | sort -r | rollback "$archfull" "$3" 

 

Si noti però che il sort è rovesciato, quindi sono cancellati tutti i files a partire dal fondo fino alla data scelta.  Chi li cancella è la seguente procedura :

function rollback
{
  while read a
  do       
    if [ "$a" == "$2.txt" ]
    then
      return
    fi     
    tar --delete --file "$1" "$a"
  done
}

Anche qui simile alla precedente ma tenendo conto dell'ordine inverso e dell'uscita prima e non dopo la data prescelta.

Non si può ovviamente fare il rollback ad una data inesistente, e cancellare il primo record, per fare quello basta cancellare l'archivio.

Il comando Trace

Mostra il contenuto dell'archivio. La sua sintassi è :
timearchive T <archive>
e il suo funzionamento è basato su una riga di codice  :
tar tf $2 | sort | grep txt | showcontent "$2"
e una funzione :

function showcontent
{
  while read l
  do
    echo -n ${l%%\.*}"  "
    tar xf $1 $l -O
  done
}

la quale, come possiamo osservare, mostra la data e il commento opzionale inserito durante l'update.

Se il commento non è stato inserito, il file è comunque presente in quanto echo, come una stringa vuota.

Lo script finale


Ed eccoci al consueto script complessivo che implementa il comando :


#!/bin/bash

function showcontent
{
  while read l
  do
    echo -n ${l%%\.*}"  "
    tar xf $1 $l -O
  done
}


function rollback
{
  while read a
  do      
    if [ "$a" == "$2.txt" ]
    then
      return
    fi    
    tar --delete --file "$1" "$a"
  done
}


function tarext
{
  while read a
  do
    tar xfp "$1" "$a" -O | tar xj --listed-incremental="$2"
    if [ ! -z "$3" ]
    then
      if [ "$3.tb2" == $a ]
      then
        return
      fi
    fi
  done
}


case $1 in

U)
  dirfull=$3
  if [ "${dirfull:0:1}" != "/" ]
  then
    dirfull=`pwd`/$dirfull
  fi
  if [ ! -d  "$dirfull" ]
  then
    echo "$3 must be an existing directory"
    exit
  fi
  archfull=$2
  if [ "${archfull:0:1}" != "/" ]
  then
    archfull=`pwd`/$archfull
  fi
  tmpdir=`mktemp -d`
  todate=`date +%Y%m%d%H%M%S`
  if [ -e "$archfull" ]
  then
    lastidx=`tar tf "$archfull" | sort | grep idx | tail -n 1`
    tar xf $archfull $lastidx -O >$tmpdir/$todate.idx
  fi
  tar cfj $tmpdir/$todate.tb2 -C "${dirfull%/*}" --listed-incremental=$tmpdir/$todate.idx "${dirfull##*/}"
  # Update archive
  echo $4 >$tmpdir/$todate.txt
  tar -C $tmpdir -rf $archfull $todate.tb2
  tar -C $tmpdir -rf $archfull $todate.txt
  tar -C $tmpdir -rf $archfull $todate.idx
  rm $tmpdir/*
  rmdir $tmpdir
;;

X)
  dirfull=$3
  if [ "${dirfull:0:1}" != "/" ]
  then
    dirfull=`pwd`/$dirfull
  fi
  if [ ! -d  $dirfull ]
  then
    echo "$3 must be an existing directory"
    exit
  fi
  archfull=$2
  if [ "${archfull:0:1}" != "/" ]
  then
    archfull=`pwd`/$archfull
  fi
  if [ ! -e $archfull ]
  then
     echo "Archive not found"
     exit
  fi
  tmpfile=`mktemp`
  if [ -z "$4" ]
  then
    lastidx=`tar tf "$archfull" | sort | grep idx | tail -n 1`
  else
    lastidx=`tar tf "$archfull" | grep "$4.idx"`
    if [ -z "$lastidx" ]
    then
       echo "Date does not match"
       exit
    fi
  fi
  tar xf $archfull $lastidx -O >$tmpfile
  cd "$dirfull"
  tar tf $archfull | grep tb2 | tarext "$archfull" "$tmpfile" "$4"
  rm $tmpfile
;;

R)
  archfull=$2
  if [ "${archfull:0:1}" != "/" ]
  then
    archfull=`pwd`/$archfull
  fi
  if [ ! -e $archfull ]
  then
     echo "Archive not found"
     exit
  fi
  if [ -z "$3" ]
  then
     echo "Missing rollback date"
     exit
  fi   
  lastidx=`tar tf "$archfull" | grep "$3.idx"`
  if [ -z "$lastidx" ]
  then
     echo "Date does not match"
     exit
  fi
  tar tf $archfull | sort -r | rollback "$archfull" "$3" 
;;

T)
    tar tf $2 | sort | grep txt | showcontent "$2"
;;

*)
  echo Bad paramenter, please use U,T,R or X.
;;
esac

con questa evoluzione del timearchive, ho ottenuto ottimi risultati specie su backup significativi e complessi e il risultato è comunque un tar, facilmente gestibile in caso di emergenza o trasferimento su altri sistemi.

giovedì 8 agosto 2013

Il Restore dopo il Backup


Premessa

Dopo una prolungata assenza dovuta a pigrizia cronica, torno tra le righe di questo blog autarchico.

Avevo predetto in passato che la nuova Debian Wheezy, sarebbe presto diventato un punto di riferimento nell'intero panorama sfenisciforme. Ebbene questa è stata la previsione più errata che la storia ricordi dopo quella della vittoria di Napoleone a Waterloo.

Potremmo dire che la cara Debian è stata accolta dalla comunità con un gelo polare, tra le critiche al kernel 3.2 (impensabile nel 2013) e la tristezza delle nuove interfacce.

Insomma, Ubuntu è riuscita a imborghesire il mondo Linux che ora pretende tutto, subito e possibilmente un tutto che non sia Gnome Shell, a costo di perdersi tra le ricerche di Amazon e i non menù di Unity.

Il Restore

Quando ho parlato dell'installazione di Debian Wheezy arzigogolando sulla vecchia testing, ho introdotto Debian Multimedia.

Senza allarmismi avevo avvisato che per usare tale repository occorreva operare un security breach, cioè installare una chiave di sicurezza ulteriore rispetto a quella dei repositories ufficiali.

Poi sono accaduti una serie di eventi, che oltre ad aver diffuso tali ingiustificati allarmismi, hanno portato da una parte ad un cambio di dominio con mancato rinnovo e dall'altra, all'inclusione di tali codec nei repositories principali, con conseguente sovrapposizione delle versioni.

A questo punto, l'introduzione di repositories non ufficiali è una pratica che sconsiglierei (al limite se vi serve Avidemux, potete compilarvelo come ho fatto io).

Cosa c'entra questo discorso col restore ?

C'entra perché per cancellare i vecchi repositories e fare un po`di pulizia ho deciso di provare a reinstallare la Wheezy da zero e, grazie alla tecnica di partizionamento del disco che ho spiegato a suo tempo, insieme a qualche link spazzato via e subito ricostruito il posizionamento della partizione dati, con /var, /opt e /usr/local è rimasto invariato.

Faccio notare che per essere sicuri è opportuno anche salvare /etc , visto che c'è sempre qualche configurazione di sistema che va rivista, magari con un diff, nel mio caso c'era un file xorg.conf che mi serviva per configurare il dual screen.

Nel mio vecchio post su rdiff-backup o qualsiasi altro backup impossibile preferiate, avevo chiarito che il backup serve solo sulla partizione dati che contiene anche ciò che installiamo e non sui pacchetti di sistema, i quali essendo appunto di sistema, sono in continuo aggiornamento e sono disponibili ovunque in rete.

Ripristinato il backup che preferite con il programma che preferite, che nel caso di rdiff-backup diventa qualcosa tipo :


rdiff-backup -r now <backup> <local>

o non ripristinato affatto nei casi, come questo, vogliate solo reinstallare il sistema operativo e tenervi la vostra partizione dati intonsa, nella vostra mente potrebbe balenare la domanda che ogni sistemista si pone almeno una volta nella vita  :

"Come si fa a rimuovere un sistema operativo e reinstallarlo esattamente come era prima ?"

Ebbene, premesso che moltissime aziende su questa domanda hanno costruito una fortuna, e molti sistemisti Windows ancora oggi si pongono disperatamente questa domanda, direi che Debian ha risolto decentemente questo dilemma (o quasi) con il famoso :

dpkg --get-selections

Get Selections

Questo semplicissimo comando mostra un elenco di pacchetti e lo stato, tipicamente install/deinstall , una lista con una serie di righe tipo questa :

....
xserver-xorg-video-vmware            install
xserver-xorg-video-voodoo            install
xterm                                install
xtrans-dev                           install
xulrunner-17.0                       install
xz-utils                             install
yasm                                 install
yelp                                 install
yelp-xsl                             install
zenity                               install
zenity-common                        install
zip                                  install
zlib1g:amd64                         install
....                                     

Ebbene cosa si fa con questa lista ?

La prima cosa è salvarcela da qualche parte, per esempio :

dpkg --get-selections > /<disco di backup>/dpkg.selections

meglio se lo si fa periodicamente, ideale se lo si fa insieme al backup, magari come root e si salva ovviamente sul disco di backup stesso.

Questo ci da una fotografia esatta di come è lo stato del sistema in un determinato momento, cosa che ci servirà proprio durante il Restore.

Set Selections ... ma anche no.

A questo punto su molti forum sbrigativi trovate scritto che la funzione opposta a dpkg --get-selection è  :

dpkg --set-selections  < /<disco di backup>/dpkg.selections 

seguito da un :

aptitude install

o qualcosa del genere.

Questo approccio, apparentemente semplice, non tiene conto di diversi fattori ma principalmente del fatto che a differenza di apt, aptitude non ha i poteri della supermucca e quindi le cose possono apparire molto più complesse di quello che ci si immagina.

Volendo un approccio più completo che tenesse conto anche dei pacchetti rimossi oltre che di quelli installati, ho provveduto ad uno script indicativo per maneggiare dpkg.selections, eccolo qui :


#!/bin/bash

function apt_purge
{
  while read t
  do
  a=($t)
  if [ ${a[1]} == deinstall ]
  then
    apt-get -y purge ${a[0]}
  fi
  done
}


function apt_install
{
  while read t
  do
  a=($t)
  if [ ${a[1]} == install ]
  then
    apt-get -y install ${a[0]}
  fi
  done
}

cat $1 | apt_install
cat $1 | apt_purge





Ipotizziamo di chiamarlo restorelist.sh , il comando da dare, una volta reinstallato il sistema e attaccato il disco di backup è il seguente :

sudo ./restorelist.sh <lista di get-selections>

La prima cosa che lo script fa è installare tutti i pacchetti richiesti, cioè quelli contrassegnati come install, poi nella fase successiva, rimuovere completamente (con un purge) tutti quelli che sono indicati come deinstall, quindi sia quelli precedentemente installati dalla distribuzione e successivamente rimossi, sia quelli installati durante l'ultima fase di install come pacchetti non strettamente dipendentie poi rimossi.

Conseguenze

Operare in questo modo sulla lista può provocare alcune piccole anomalie che devono essere risolte manualmente. Per esempio se si disinstalla Zeitgeist , lo ritroveremo installato, questo perché non appare nella lista sotto forma di deinstall ma è rimosso da questa e reintrodotto come dipendenza.

Questo accade in generale per tutte le dipendenze opzionali rimosse mentre non accade per pacchetti rimossi direttamente.

Un'altra cosa piuttosto rara che può accadere è che dipendenze rimosse diventino obbligatorie in release successive dei pacchetti, in questo caso potrebbe essere eliminato del software appena installato e si dovrà modificare manualmente la lista affinché ciò non accada.


Capita invece di frequenti che alcuni pacchetti non siano generici ma riportino un numero di versione (il che in generale consente di installare per esempio più versioni del gcc o dello xulrunner o più semplicemente del kernel. In questo caso il passaggio da una versione all'altra porta spesso ad una sparizione della versione precedente. Per ovviare a questo si può lanciare lo script con il seguente suffisso :

sudo ./restorelist.sh <lista di get-selections> 2>&1 >error.log

e leggersi il file di log per stabilire cosa non è stato trovato da una versione all'altra, quindi porvi rimedio.

In questo modo è possibile anche ripristinare, con un minimo di lavoro i pacchetti tra differenti versioni o sulle rolling release e nella sua semplicità è certamente uno strumento piuttosto potente.

venerdì 29 marzo 2013

L'importanza di chiamarsi Zeitgeist


Una spia a tempo pieno


"Strano gioco la guerra, l'unica mossa vincente è non giocarci. Che ne direbbe di una bella partita a scacchi ?", diceva lo W.O.P.R. in un famoso film degli anni 80.

Non so cosa c'entri esattamente questa frase, però mi è venuta spontanea pensando al fatto che la continua guerra termonucleare globale, che alcuni conducono in difesa della "dignità" di potersi fare gli affari propri almeno quando si è disconnessi dalla rete, è talmente sfiancante e misteriosa da convincere anche i più volenterosi ad abiurare.

È lo Zeitgeist appunto, cioè lo spirito dei tempi, tempi in cui il tuo computer e con lui gran parte del mondo, deve sapere più cose di te di quante tu stesso ne sappia e usarle nel tuo interesse, per sollevarti dal gravoso impegno di scegliere. Si chiama integrazione cibernetica o in linguaggio Borg : "Assimilazione".

Capita così che mentre scorri un backup e vai a vederti la lista dei pacchetti installati, sulla tua Free Debian o una Ubuntu qualsiasi con il più classico dei :
/usr/bin/dpkg --get-selections
scopri proprio in fondo all'elenco in ordine alfabetico un pacchetto dal nome curioso : zeitgeist , che qualche dipendenza ha voluto donarti.

Nel mio caso, da utilizzatore Debian, è stato Cairo Dock , su Ubuntu è una cosettina come Unity .

Fase 1 - La Consapevolezza


Conseguenza per la classica mente malata dell'utente cantinaro e paranoico, è andare a digitare la parolina in Google e trovare l'immancabile articolo di Wikipedia ... cito perché è interessante  :

"Zeitgeist è un servizio che registra le attività degli utenti, ovunque dai file aperti ai siti visitati e alle conversazioni: queste informazioni diventano disponibili per altre applicazioni per essere usati come linee del tempo e statistiche."

Eeh ?!?! Cosa ?!??

Cioè ... sul mio computer che monta Linux c'è un "demone" che legge tutto quello che faccio, i files che apro, le mie conversazioni , le pagine che visito e le memorizza in un database per metterle a disposizione di altre applicazioni ? Soprattutto quali applicazioni ?

Poi si legge "per essere usati come linee del tempo e statistiche" che, benché non significhi essenzialmente nulla, è piuttosto tranquillizzante.

Fase 2 - La paura


Peccato che due righe più sotto, nella ricerca ci sia un video su YouTube, dove un altro cittadino della rete ha postato una feature meravigliosa di Cairo Dock , quella che si interfaccia appunto, con zeitgeist (che ricordo è un punto cardine di Ubuntu Unity). Eccolo qui :




Da questo video apprendiamo che il nostro cittadino è Francese, che Venerdì 4 febbraio alle 14.40 ha ricevuto una mail scam del provider francese di Alice, per una offerta di una adsl a 10 euro, probabilmente è asiatico o ha simpatie asiatiche e ciò è dimostrato dal fatto che il 3 febbraio alle 23.43 ha visto un filmato MKV "Rabbit - Force", probabilmente scaricato da un torrent asiatico chiamato "Asian Drama Torrents"... e siamo solo al secondo 7 degli 85 che dura il filmato.

Più avanti si vedono anche immagini e thumbnails di varia natura.

So cosa starei pensando io se fossi in Voi, ma siccome Voi non siete me probabilmente starete pensando ad altro, posso però dire che la descrizione data da Wikipedia è un pochino limitata.

Questo mi fa pensare ad un articolo che casualmente pubblicai su questo piccolo blog autarchico l'anno scorso, che riguardava l'altra interfaccia operativa usata principalmente negli ambienti Open, cioè KDE  dove i mostri semantici avevano altri nomi esotici : "Akonadi" e in particolare "Nepomuk", i quali per altro erano mostruosi anche dal punto di vista architetturale.

In quei tempi parlai anche del Tracker di Gnome 2 che però aveva l'aria molto più innocua, zeitgeist è molto più infido.

Fase 3 - Conosci il tuo nemico e annientalo


Esaminando i packages, si scopre che vi sono diversi pacchetti che in linea di massima sono installati da Cairo Dock  :
  1. zeitgeist
  2. zeitgeist-core
  3. zeitgeist-datahub
  4. libzeitgeist
  5. python-zeitgeist

Ma potrebbero essere di più o di meno.

La struttura è simile a quella di un database, cioè ci sono il server e il client.

Il classico metapachetto è zeitgeist che carica il core e il data-hub . Questi due elementi si occupano di raccogliere dati e permetterne la comunicazione.

I punti 4 e 5 del nostro elenco invece sono integrati con i veri e propri programmi e servono per comunicare con zeitgeist ,  sono cioè quelle da cui dipendono di solito i programmi, nella fatispecie Cairo Dock.

Rimuoverle provocherebbe dei broken packages, rimuoverli fisicamente risolverebbe il problema (se non si usasse il plugin) ma al primo upgrade saremmo da capo.

D'altra parte rimuovendo il server coi relativi dati l'utilizzo del client diventa inutile, quindi apprestiamoci alla rimozione.

Prima di tutto vediamo dove sono i dati di zeitgeist, cioè nella cartella dell'utente (per fortuna) nel posto più logico dove trovarli e cioè :

.local/share/zeitgeist

Vedo subito che il log della mia attività è molto completo, infatti ci sono mega di cose private, compreso tutti i film che ho visto e cancellato, le immagini e i testi con nomi, cognomi e addirittura delle password.

Tutti lì a disposizione di ogni widget python o applet varia che li voglia inviare a chicchessia. Poi io uso Debian, ma in Ubuntu sarebbe un vero spasso, visto che zeitgeist è integrato con la barra delle ricerche  connessa al web , per giunta con un sacco di applet closed.

Dal mio punto di vista, preferisco annientare il tutto con :

sudo apt-get purge zeitgeist zeitgeist-core zeitgeist-datahub

operazione molto salubre che consiglio anche a voi.

Questo dovrebbe rimuovere completamente la cartella privata coi dati degli utenti, se non lo fa, fatelo voi a mano.

Ora potete dare logout e rientrare, per cancellare la situazione temporanea.

Ora se avete a disposizione un client valido, tipo il recent-events di Cairo Dock, usatelo contro se stesso e andate a vedere la lista che a questo punto dovrebbe risultare perfettamente vuota, altrimenti fidatevi sulla parola che non fa mai male.

E questo è tutto ?


Assolutamente no, perché Zeitgeist è appunto lo spirito dei tempi e i tempi di solito non si possono cambiare, a meno di non rinunciare a tutto e rifugiarsi sulle montagne a vivere da eremita (che per inciso è dove vivo io), però possiamo auto-congratularci e vantarci di avere non-risolto un serio non-problema.


mercoledì 20 febbraio 2013

Chromix III - PanMenu and Go !

Segue dalla puntata precedente.

 

Un buon Menu


Siamo giunti agli ultimi passaggi del progetto Chromix e ci serve il Menu.

Ma Perché ci serve un Menu ?


Ci serve perché il Menu di LXDE così come la maggior parte dei Menu delle varie interfacce grafiche, è studiato per far funzionare un Desktop e attinge alle relative applicazioni, mentre il nostro obiettivo è creare qualcosa di semplice che si interfacci con le Web-App di Chromium.

Devo dire che dopo molte titubanze, tipo la ricerca di un plugin adatto o l'idea di scriverlo, mi è venuto in mente che in fondo la cosa più facile era fare uno  scriptino in Python.

Uno scriptino in Python ?  


Si uno scriptino che usasse Python e le GTK, con tanto di supporto CAIRO.

Per installare ciò che ci serve occorre usare il terminale di root cioè quello rosso e poi dare il comando :

apt-get install python-gtk

e per quanto riguarda le operazioni come root abbiamo finito.

Aperto un terminale normale, invece creiamo due folder nascosti come chromix nella nostra HOME :


mkdir .panmenu
mkdir .menu
cd .panmenu

E siamo dentro la neonata cartella dove dobbiamo scrivere (se vogliamo con leafpad) il primo file che si chiama config e che contiene la configurazione del pannello :

Potrebbe essere  :

bottom=50
minrows=3
maxrows=8
mincols=3
maxcols=12
Altra riga accettata da questo file è defaulticon che indica il nome dell'icona di default, la quale se non indicata diventa /.local/share/icons/default.png.

Nella stessa cartella dobbiamo inserire l'icona del programma che si chiama panmenu.png , io ho scelto questa :



La stessa icona la potete copiare con un :

mkdir ~/.local/share/icons
cp panmenu.png  ~/.local/share/icons/default.png

Oppure potete sceglierne una completamente diversa.

Ora aggiungiamo il link chiamato : panmenu.desktop e scriviamo le seguenti righe di testo :

[Desktop Entry]
Version=1.0
Name=PanMenu
Comment=MenuPanel
Exec=/home/chromix/.panmenu/panmenu
Terminal=false
X-MultipleArgs=false
Type=Application
Icon=/home/chromix/.panmenu/panmenu.png

Lo script


Dopo avere aggiunto le parti secondarie arriviamo ora al vero e proprio pannello in python-gtk il cui codice è questo :


#!/usr/bin/python
#
#  PANMENU
#  A simple menu panel for Linux written in Python GTK
#
#  Copyright 2013 by ElDuraMinga (elduraminga.blogspot.com)
#
#  This program is free software: you can redistribute it and/or modify
#  it under the terms of the GNU General Public License as published by
#  the Free Software Foundation, either version 3 of the License, or
#  (at your option) any later version.
#
#  This program is distributed in the hope that it will be useful,
#  but WITHOUT ANY WARRANTY; without even the implied warranty of
#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#  GNU General Public License for more details.
#
#  See <http://www.gnu.org/licenses/>.
#

import pygtk
import gtk
import os
import fileinput
import gtk, gobject, cairo

global inside
inside=False

global page
page=0

global profile,home,folderlist

home=os.getenv("HOME")
folderlist=[]

global GLX,GLS
GLX=64
GLS=48

global bottom,minrows,mincols,maxrows,maxcols,defaulticon

bottom=0
minrows=5
mincols=3
maxrows=8
maxcols=8
defaulticon=home+"/.local/share/icons/default.png"


global nRows,nCols,nIndex,nCount
nRows=0
nCols=0
nIndex=-1
nCount=0

global pixmaps
pixmaps=[]


#Check single instance
def singleInstance() :
  if os.access("/run/lock/panmenu.lock",os.F_OK):
    quit()
  else :
    f=open("/run/lock/panmenu.lock","w")
    f.close()

def killInstance() :
  os.unlink("/run/lock/panmenu.lock")


# Read Global Profile
def profileRead() :
  global bottom
  global minrows
  global mincols
  global maxrows
  global maxcols
  global defaulticon
  global home
  for i in fileinput.input(home+"/.panmenu/config") :
    v=i.strip().split('=')
    if len(v)==2 :
      if (v[0]=='bottom') :
        bottom=int(v[1])
      if (v[0]=='minrows') :
        minrows=int(v[1])
      if (v[0]=='maxrows') :
        maxrows=int(v[1])
      if (v[0]=='mincols') :
        mincols=int(v[1])
      if (v[0]=='maxcols') :
        maxcols=int(v[1])
      if (v[0]=='defaulticon') :
        defaulticon=v[1]

def checkIcon( iconname ) :
    global defaulticon
    global home
    for dirname, dirnames, filenames in os.walk(home+"/.local/share/icons"):
      for i in filenames :
        if i[:len(iconname)]==iconname :
          return dirname+"/"+i
    return defaulticon



def parseApplicationFile( name ) :
  global folderlist
  fname=""
  ficon=""
  fexec=""
  for i in fileinput.input(name) :
    v=i.strip()
    if v[:5]=="Icon=" :
      if v[5]=='/' :
        ic=v[5:]
      else :
        ic=checkIcon(v[5:]+".png")
      ficon=ic
    if v[:5]=="Exec=" :
      fexec=v[5:]
    if v[:5]=="Name=" :
      fname=v[5:]
  folderlist.append([fname,ficon,fexec]) 
   

#Read the local applications directory
def applicationsRead() :
  global home
  dirlist=os.listdir(home+"/.menu")
  dirlist.sort()
  for i in dirlist :
    j=home+"/.menu/"+i
    if (os.path.isfile(j)) :
      if (i[-8:]==".desktop") :
        parseApplicationFile(j)
  dirlist=os.listdir(home+"/.local/share/applications")
  dirlist.sort() 
  for i in dirlist :
    j=home+"/.local/share/applications/"+i
    if (os.path.isfile(j)) :
      if (i[-8:]==".desktop") :
        parseApplicationFile(j)
       

def iconShow(surface,cr,x,y,w,h,alpha) :
    cr.save(); 
    width = surface.get_width()
    height = surface.get_height()   
    width=w/width;
    height=h/height;
    cr.scale(width, height);
    cr.set_source_surface(surface, x/width, y/height)
    if alpha :     
      cr.paint_with_alpha(0.5)
    else : 
      cr.paint();
    cr.restore();



# Create a GTK+ widget on which we will draw using Cairo
class Screen(gtk.DrawingArea):

    # Draw in response to an expose-event
    __gsignals__ = { "expose-event": "override" }

    # Handle the expose-event by drawing
    def do_expose_event(self, event):

        # Create the cairo context
        cr = self.window.cairo_create()

        # Restrict Cairo to the exposed area; avoid extra work
        cr.rectangle(event.area.x, event.area.y,
                event.area.width, event.area.height)
        cr.clip()

        self.draw(cr, *self.window.get_size())

    def draw(self, cr, width, height):
        global iconlist
        global nIndex
        j=page
        for i in range(nRows*nCols) :
          cx=GLX*((i%nCols)+1);
          cy=GLX*((i/nCols)+1);
          if (nIndex==j) :
            iconShow(folderlist[j][3],cr,float(cx-GLS/2),float(cy-GLS/2),float(GLS),float(GLS),True);
          else :
            iconShow(folderlist[j][3],cr,float(cx-GLS/2),float(cy-GLS/2),float(GLS),float(GLS),False);
          j+=1 
          if (j>=len(folderlist)) :
            break 
        if nIndex!=-1 : 
          cr.save(); 
          cr.set_source_rgb(0, 0, 0)
          
          cr.select_font_face("Nimbus Sans L", cairo.FONT_SLANT_NORMAL,cairo.FONT_WEIGHT_NORMAL)
          cr.set_font_size(20)
         
          cr.move_to(GLX/2, height-26)
          cr.show_text(folderlist[nIndex][0]) 
          cr.restore(); 
         

def motionEvent(widget, event):
  global folderlist
  global nIndex
  global inside
  inside=True
  j=page
  q=-1
  for i in range(nCols*nRows) :
    cx=GLX*((i%nCols)+1);
    cy=GLX*((i/nCols)+1);   
    if ( (event.x>=float(cx-GLX/2)) and (event.x<=float(cx+GLX/2)) and (event.y>=float(cy-GLX/2)) and (event.y<=float(cy+GLX/2)) ) :
      q=j
      break;
    j+=1
    if (j>=len(folderlist)) :
      break
  if (q!=nIndex) :
    nIndex=q
    widget.queue_draw();   
  return True

def buttonEvent(widget, event):
  global nIndex 
  global folderlist
  global page
  if event.type == gtk.gdk.BUTTON_PRESS  :
    if event.button == 1:
      killInstance()
      if (nIndex!=-1) :
        os.execl("/bin/sh","/bin/sh","-c",folderlist[nIndex][2]);
      gtk.main_quit()
    if event.button == 3:
      page+=nCols*nRows
      if page>=len(folderlist) :
        page=0
      widget.queue_draw()
  return True



def quitAll(widget, event):
  global inside
  if inside :
    killInstance()
    gtk.main_quit()
    return True
  
# Panel intialization and run
def run(Widget):
    global nRows
    global nCols
    global nIndex
    global nCount
    global folderlist
    global iconlist
   
    nRows=minrows
    nCols=mincols
    nCount=len(folderlist)
   
    while (nRows*nCols<nCount) :
      nRows+=1;
      if (nRows>maxrows) :
        nCols+=1;
        nRows=minrows
      if (nCols>maxcols) :
        nRows=maxrows
        nCols=maxcols
        nCount=nRows*nCols;
        break; 
       
       
    for i in folderlist :
      try:
        f = cairo.ImageSurface.create_from_png(i[1])
      except Exception, e:
        f = cairo.ImageSurface.create_from_png(defaulticon)
      i.append(f)
 
    window = gtk.Window()
    window.connect("delete-event", quitAll)
    widget = Widget()
    widget.show()
    window.add(widget)
   
   
    widget.connect("motion_notify_event", motionEvent)
    widget.connect("button_press_event", buttonEvent)
    widget.connect("leave_notify_event", quitAll)

    widget.set_events(gtk.gdk.EXPOSURE_MASK
                            | gtk.gdk.LEAVE_NOTIFY_MASK
                            | gtk.gdk.BUTTON_PRESS_MASK
                            | gtk.gdk.POINTER_MOTION_MASK
                            | gtk.gdk.POINTER_MOTION_HINT_MASK)
   
   
   
    color = gtk.gdk.Color(red=65535, green=65535, blue=65535, pixel=0)
    window.background=color
    window.resize(GLX*(nCols+1), GLX*(nRows+1)+32);
    window.move(5, gtk.gdk.screen_height() - bottom - 32 - GLX*(nRows+1));
    window.present()
    window.set_name ("menupanel")
    window.set_title("Menu Panel")
    window.set_decorated(False)
    gtk.main()
   
if __name__ == "__main__":
    singleInstance()
    try :
      profileRead()
      applicationsRead()
      run(Screen)
    except e:
      killInstance()



Lo dovete copia-incollare dentro un file chiamato panmenu nella stessa cartella, menù e renderlo eseguibile tipo :

chmod 755 panmenu


Come funziona PanMenu


Panmenu recupera le applicazioni dai desktop link contenuti in .menu, cioè quelle che aggiungeremo noi e .local/share/applications che invece sono quelle che aggiunge Chromium, ossia le web-app.

Entrambi andranno a reperire le icone relative in .local/share/icons . Notare che questo menù non usa le libmenu, ma fa per conto suo ed è estremamente dinamico.

Il suo funzionamento è la costruzione di una finestra a sinistra in basso BOTTOM righe sopra l'ultima linea, individuando la serie di icone del pannello mettendole in ordine alfabetico, infine visualizzando un rettangolo che si estende da MINROWS MINCOLS a MAXROWS MAXCOLS, in modo progressivo.

Oltre il termine di MAXROWS/MAXCOLS si procede a pagine e per andare sulle successive si usa il Right-Click del mouse a rotazione.

Il pannello scompare quando si esce dal menu col mouse o quando si preme su un link, nel qual caso viene usato un execl con sh per lanciare il comando, che resta così regolarmente "figlio" del pannello.

Nota : Se il menù non si apre più controllare ed eventualmente rimuovere il lock per l'istanza singola : /run/lock/panmenu.lock


Integrare PanMenu nel launcher di LXDE

 

Per prima cosa, con leafpad si va a modificare il file :

 ~/.config/lxpanel/LXDE/panels/panel

Aggiungendo al primo launcher una altro Button, col seguente codice :



...
Plugin {
    type = launchbar
    Config {
        Button {
            id=/home/chromix/.panmenu/panmenu.desktop
        }
        Button {
            id=/usr/share/applications/chromium.deskto       
        }
    }
}
...


poi dobbiamo fare in modo che l'applicazione non sia visualizzata nella taskbar, modificando opportunamente la configurazione di openbox cioè il file :

~/.config/openbox/lxde-rc.xml

aggiungendo in fondo, prima della chiusura del tag XML </applications> quattro righe:

...
  # end of the example
-->

    
 <application name="panmenu" >
   <skip_taskbar>yes</skip_taskbar>
   <skip_pager>yes</skip_pager>
 </application>

  </applications>
</openbox_config>
...

Ora non ci resta che popolare, con due applicazioni di base il .menu e per far questo diamo la sequenza dei comandi :

cd ~/.menu
cp /usr/share/applications/lxterminal.desktop .
cp /usr/share/applications/pcmanfm.desktop .
cd ~/.local/share/icons/
cp /usr/share/icons/nuoveXT2/48x48/apps/terminal.png lxterminal.png
cp /usr/share/icons/nuoveXT2/48x48/apps/file-manager.png system-file-manager.png
Cioè copiamo sia il link a queste due applicazioni (in .menu) che le relative icone (in .local/share/icons), come indicate all'interno dei link.

Abbiamo scelto icone 48x48 che è la dimensione standard per il menu.

Siccome non sono accettate le sostituzioni tipo %U nei links, andiamo nuovamente in .menu e sostituiamo la riga di exec di pcmanfm.desktop con :

...
Exec=pcmanfm ~/Downloads
...

A questo punto possiamo riavviare.

 Riavvio


Se avete fatto i regolari passaggi, cliccando sul nuovo launcher nel pannello del menù vedrete qualcosa tipo :






Facendo click sul file manager per altro, entreremo direttamente in Downloads, dove ci si aspetta che Chromium metta ciò che scarichiamo.

Inoltre sul tab sinistro di pcmanfm troviamo Applications , se clicchiamo qui, scopriamo che si tratta di un altro menu completo da cui possiamo avviare tutte le applicazioni installate :



dunque possiamo rimuovere quello sul pannello e lasciare solo il nostro PanMenu e Chromium.


 

 

Aggiungere Web-Apps e Links


Se apriamo Chromium sulla new-tab classica, dovremmo trovare l'elenco di tutte le applicazioni che abbiamo integrato.

All'inizio c'è solo il Chrome Web Store che però è un Web-App come tutte le altre dunque possiamo benissimo inserirlo come applicazione.

Per farlo clicchiamo col tasto destro sull'icona stessa e scegliamo Create Shortcuts :



Qui scegliamo di mettere il link nel menu e non sul desktop.



Ora, se andiamo a vedere il nostro menù, sarà diventato così :



E potremo lanciare l'applicazione come se fosse locale :




Come possiamo osservare nella finestra non c'è alcuna barra e l'icona nella taskbar non è quella di Chromium ma dello store !

Questo ovviamente, si può fare con tutte le applicazioni che vogliamo e ciò è abbastanza grandioso.

Ma c'è molto altro, tipo inserire un LINK come se fosse una web-app, con l'unico problema che l'icona sarà quella dello standard web di solito 16x16, poi scalata di tre volte.

Comunque apriamo Chromium , andiamo su un sito tipo www.twitter.com e clicchiamo sul menù di Chromium , sotto Tools troviamo Create Application Shortcut .



Stessa operazione di prima, cioè creazione del link non sul desktop ma nel menu ed ecco il nostro nuovo menu con l'uccellino.



 

Ed ecco il nostro Chromix



Abbiamo praticamente concluso il lavoro su Chromix.

Se volete, potete portare avanti piccole rifiniture, tipo cambiare la PNG che sta in /usr/share/lxde/images/logout-banner.png, con l'icona di Chromium 256x256, in modo da avere un menu di chiusura più raffinato e tante altre cose che lascio fare a voi.

Per ora arrivederci.