tag:blogger.com,1999:blog-41204515897488764202024-03-11T04:23:53.473+01:00Il Blog di StefanoPhp, C#, Ubuntu Linux e tanto altro :-)Unknownnoreply@blogger.comBlogger132125tag:blogger.com,1999:blog-4120451589748876420.post-54918764632554192202015-06-27T12:00:00.000+02:002015-06-27T12:00:00.559+02:00Come estrarre un file tar con phpA partire dal Php 5.2.0, è disponibile la classe <span style="font-family: "Courier New",Courier,monospace;"><a href="http://php.net/manual/en/book.phar.php" target="_blank">Phar</a></span>, che permette di interagire con archivi Zip e Tar.<br />
In questo post mostro un semplice snippet per estrarre un archivio Tar in una directory temporanea.<br />
<br />
<pre class="php" name="code">$phar = new PharData('nome-archivio.tar');
//Estraggo tutti i files posizionandoli in /tmp/
$phar->extractTo('/tmp/', null, true);
</pre>
Unknownnoreply@blogger.com0tag:blogger.com,1999:blog-4120451589748876420.post-81986009215202710472015-06-26T11:29:00.001+02:002015-06-26T15:33:23.928+02:00Elencare tutti i files con estensione jpg in una directory con phpOggi pubblico un semplice codice php che sfrutta la comoda funzione <span style="font-family: "Courier New",Courier,monospace;"><a href="http://php.net/manual/en/function.glob.php" target="_blank">glob()</a></span>. <br />
Questa funzione permette di ricercare files con un determinato pattern all'interno di una cartella specificata.<br />
Di seguito, un semplice snippet per ricercare files con estensione <span style="font-family: "Courier New",Courier,monospace;">.jpg</span> <br />
<br />
<pre class="php" name="code">$_DIR = '/var/www/html/images/';
$images_array = glob($_DIR . '*.jpg');
foreach ($images_array as $image) {
echo $image;
}
</pre>
Unknownnoreply@blogger.com0tag:blogger.com,1999:blog-4120451589748876420.post-28787434933747498432015-06-18T15:00:00.000+02:002017-03-14T17:38:03.139+01:00Automatic scaling di una finestra in C#Sviluppando un software a finestre in C# e non una web application, spesso si ha il problema di renderlo in qualche modo "responsive" e quindi adattabile alle varie risoluzioni dello schermo o al ridimensionamento della finestra.<br />
<br />
Dopo aver cercato su internet per un po', sono finalmente giunto ad uno script facile e veloce per gestire la cosa. Una premessa è doverosa: il C# ha un suo metodo di scaling che però non agisce direttamente sui Font. Il che significa che, oltre allo scaling del controllo, è necessario effettuare anche un ridimensionamento del font con la stessa proporzione.<br />
E' importante disabilitare la funzionalità di autoscale nativa, che permette un adattamento solo parziale e successivamente ridimensionare ogni controllo della finestra con una determinata proporzione (ratio).<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh57Dc0kPoB5nVvCVbdDctODut5CGI3NSfvvC-vBHkZIIo53tmzoCa6Lu8ZO4hlwH38Plxckjpe_8m_2rbz7phGrOeDqITjwM2C-Lc-JftnVfkWaHFeVBLdDzSAjbElo0Iy6uZOPQfeoLg/s1600/Senzanome.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="246" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh57Dc0kPoB5nVvCVbdDctODut5CGI3NSfvvC-vBHkZIIo53tmzoCa6Lu8ZO4hlwH38Plxckjpe_8m_2rbz7phGrOeDqITjwM2C-Lc-JftnVfkWaHFeVBLdDzSAjbElo0Iy6uZOPQfeoLg/s320/Senzanome.png" width="320" /></a></div>
<br />
Di seguito i pochi passi necessari per il corretto funzionamento del ridimensionamento.<br />
<br />
1. Aggiungere nelle dichiarazione delle variabili
<br />
<pre class="csharp" name="code">private Size currSize;
</pre>
2. Aggiungere nel costruttore, dopo <span style="font-family: "courier new" , "courier" , monospace;">InitializeComponent()
</span><br />
<pre class="csharp" name="code">this.AutoScaleMode = AutoScaleMode.None;
currSize = this.Size;
</pre>
3. Aggiungere la seguente funzione
<br />
<pre class="csharp" name="code">private void autoscale()
{
Font tempFont;
SizeF ratio = SizeF.Empty;
//calculate resizing ratio
ratio = new SizeF((float)this.Width / currSize.Width, (float)this.Height / currSize.Height);
//loop through all controls and scale them
foreach (Control c in this.Controls)
{//Get current font size, Scale object and scale font
tempFont = new Font(c.Font.Name,
c.Font.SizeInPoints * ratio.Width * ratio.Height);
c.Scale(ratio);
c.Font = tempFont;
}
//update current size
currSize = this.Size;
}
</pre>
4. Utilizzare la funzione precedente nell'evento <span style="font-family: "courier new" , "courier" , monospace;">SizeChanged </span>della windows form
<br />
<pre class="csharp" name="code">private void Form1_SizeChanged(object sender, EventArgs e)
{
autoscale();
}
</pre>
Unknownnoreply@blogger.com0tag:blogger.com,1999:blog-4120451589748876420.post-2855073312395098432015-06-17T12:39:00.001+02:002015-06-17T12:39:54.148+02:00Monitorix - uno snello sistema di monitoring risorse per Linux<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgSp2izjEzWdl2QrLUFlaKPN3jS1M3lpASucnlMra0fsRn8ApzFek82iHTFnVPMP77ReV5uhQwYSQqVxCvos49EVjV-YwENSi9DGbfbAz4qiKbvwPt3BICnew8ObY_7LugrUvYaLBDQsDE/s1600/1.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="252" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgSp2izjEzWdl2QrLUFlaKPN3jS1M3lpASucnlMra0fsRn8ApzFek82iHTFnVPMP77ReV5uhQwYSQqVxCvos49EVjV-YwENSi9DGbfbAz4qiKbvwPt3BICnew8ObY_7LugrUvYaLBDQsDE/s320/1.png" width="320" /></a></div>
<br />
Oggi presento <a href="http://www.monitorix.org/" target="_blank">Monitorix</a>, un sistema di monitoraggio delle risorse del sistema per Linux. E' strutturato in due parti:<br />
- un demone che colleziona i dati storicizzandoli;<br />
- un webserver con uno script cgi che permette la visualizzazione dei dati raccolti in formato grafico.<br />
<br />
L'installazione è molto semplice, sul sito si trovano le istruzioni. Riporto i passi per l'installazione su Debian / Ubuntu:<br />
<br />
1. Modificare il file /etc/apt/sources.list aggiungendo<br />
<pre class="bash" name="code">deb http://apt.izzysoft.de/ubuntu generic universe</pre>
<br />
2. Scaricare la chiave GPG del repository<br />
<pre class="bash" name="code">wget http://apt.izzysoft.de/izzysoft.asc</pre>
<br />
3. Aggiungere la chiave<br />
<pre class="bash" name="code">sudo apt-key add izzysoft.asc</pre>
4. Installare monitorix<br />
<pre class="bash" name="code">sudo apt-get update
sudo apt-get install monitorix</pre>
<br />
A questo punto, ci si può godere il risultato collegandosi all'indirizzo ip del server sul quale monitorix è installato, oppure in locale: porta <span style="font-family: "Courier New",Courier,monospace;">8080</span>, sottocartella <span style="font-family: "Courier New",Courier,monospace;">monitorix/</span><br />
<pre class="bash" name="code">http://localhost:8080/monitorix/</pre>
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh3N2u-Ld9l62BFNvKhEsinlC8JKqpl-n0aST9FI_kyGc_BcRCZhuixB4CuDZY29-l8pLV6Ss_yAdJMXzW8Xknn_PDO1whCbGbDYYnEGip2WneTiueaNbCW3T5dPdCJriZAyudBVlxlqvk/s1600/2.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="318" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh3N2u-Ld9l62BFNvKhEsinlC8JKqpl-n0aST9FI_kyGc_BcRCZhuixB4CuDZY29-l8pLV6Ss_yAdJMXzW8Xknn_PDO1whCbGbDYYnEGip2WneTiueaNbCW3T5dPdCJriZAyudBVlxlqvk/s320/2.png" width="320" /></a></div>
<br />
<br />Unknownnoreply@blogger.com0tag:blogger.com,1999:blog-4120451589748876420.post-44470928667415949102015-06-05T11:08:00.000+02:002015-06-05T11:09:08.004+02:00Caricamento automatico delle classi in Php<blockquote class="tr_bq">
<i><span style="font-family: "Trebuchet MS",sans-serif;">In principio fu l'include (o il require) di tanti file php che contenevano funzioni. </span></i><br />
<i><span style="font-family: "Trebuchet MS",sans-serif;">Poi vennero le classi, incluse a mano una per una.</span></i></blockquote>
Ed ora? Adesso il Php è un linguaggio molto più maturo, dotato di namespace, che permettono a classi con lo stesso nome di coesistere (con namespace diversi) all'interno dello stesso progetto. In questo modo, includere una libreria di terze parti progettata a namespace non comporta più problemi di omonimia di classi.<br />
Altra caratteristica utilissima, novità rispetto al passato, è l'autocaricamento delle classi. Onde evitare una lunghissima lista di inclusioni, una per ogni classe, è stata introdotta la funzionalità di autoload.<br />
Come funziona? Semplice: utilizzando la funzione <span class="function"><span style="font-family: "Courier New",Courier,monospace;"><a class="function" href="http://php.net/manual/it/function.spl-autoload-register.php">spl_autoload_register()</a></span> spiegheremo all'interprete Php come comportarsi in caso di classi "che non conosce", ovvero che non sono state esplicitamente incluse nello script. Per maggiori informazioni, <a href="http://php.net/manual/it/language.oop5.autoload.php" target="_blank">consiglio il sito ufficiale</a>.</span><br />
<span class="function">Vediamo un semplice esempio:</span><br />
<pre class="php" name="code">//Autoloading classes
spl_autoload_register(function($className)
{
//Obtain the pure class name
$pureClassName = getRealClassName($className);
//Build the path
$namespace = getNameSpace($className);
if(file_exists($namespace.'/'.$pureClassName.'.php')) {
include_once($namespace.'/'.$pureClassName.'.php');
}
});
function getRealClassName($fullClassName) {
//Explode class name
$classNameArray = explode('\\',$fullClassName);
//Obtain the pure class name
return end($classNameArray);
}
function getNameSpace($fullClassName) {
//Explode class name
$classNameArray = explode('\\',$fullClassName);
//Remove the pure class name
array_pop($classNameArray);
//Remove the main namespace (first)
array_shift($classNameArray);
//Build the path
$namespace = implode('/', $classNameArray);
return $namespace;
}
</pre>
Breve spiegazione: nel caso il Php non trovi una classe, tenta una inclusione automatica, cercando il file posizionato nel percorso indicato dal namespace (stessa struttura delle cartelle quindi) e chiamato come il nome della classe, con estensione php. Le due funzioni <span style="font-family: "Courier New",Courier,monospace;">getRealClassName()</span> e <span style="font-family: "Courier New",Courier,monospace;">getNameSpace()</span> sono semplici funzioni che interpretano il nome della classe separando namespace dal nome "proprio" della classe stessa. Unica cosa degna di nota, viene eliminato il primo ramo del namespace, che solitamente è il nome del progetto. <br />
Quindi, riassumendo, un comando simile:<br />
<pre class="php" name="code">new \ProjectName\Core\Othernamespace\Classname()</pre>
Causerà una include automatica del percorso
<br />
<pre class="php" name="code">Core/Othernamespace/Classname.php</pre>
Unknownnoreply@blogger.com0tag:blogger.com,1999:blog-4120451589748876420.post-77334359177029040882015-05-22T07:56:00.002+02:002015-06-10T17:03:15.862+02:00Impostare la replicazione Mysql - procedura passo passo<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhNehj4CvdxIvOBDOUeMJzNRLqZmehH_zxdOexrLYSTjQEgOyjccUmfFBmxTmhtaQ1MARZCa9BWjrvjOovMHnEZ0YnNwBxtAjsbcH1KROiFNFvCnQS0Nj1_buEKPU3Zq-ELDj4bDZAQDHI/s1600/MySQL_Replication.png" imageanchor="1"><img border="0" height="108" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhNehj4CvdxIvOBDOUeMJzNRLqZmehH_zxdOexrLYSTjQEgOyjccUmfFBmxTmhtaQ1MARZCa9BWjrvjOovMHnEZ0YnNwBxtAjsbcH1KROiFNFvCnQS0Nj1_buEKPU3Zq-ELDj4bDZAQDHI/s320/MySQL_Replication.png" width="320" /></a></div>
Se gestite un server MySql in azienda, e volete dormire sonni tranquilli, è ovviamente fondamentale la schedulazione di un backup serale. Questo spesso non è sufficiente: il downtime per il ripristino in caso di guasto può essere troppo lungo, o addirittura può essere concreto il rischio di perdere dati di una buona metà giornata di lavoro.<br />
<br />
Per dormire sonni tranquilli, basta affidarsi alla <b>MySql Replication</b>. Come funziona? Semplice, gli attori in gioco sono minimo due:<br />
- un server MySql <i>Master (master_host)</i><br />
- uno o più server MySql <i>Slave (slave_host)</i><br />
Una magia rende possibile l'allineamento dei due server: ogni operazione eseguita sul master viene a sua volta eseguita anche sullo slave. Risultato: due server con dati identici (a meno di un leggero scostamento temporale nelle operazioni tra master e slave).<br />
Di seguito la procedura che ho seguito, passo passo, per l'impostazione del sistema.<br />
<br />
<h4>
Fase 1: impostazione del server Master</h4>
E' fondamentale spiegare al server MySql principale che farà da Master. Per fare questo occorre agire sul file di configurazione my.cnf (o my.ini) aggiungendo le seguenti direttive nella sezione [mysqld]<br />
<pre class="bash" name="code">[mysqld]
log-bin=mysql-bin
server-id=1
</pre>
Il parametro server-id può essere un numero qualsiasi tra 1 e (2<sup>32</sup>-1) e deve essere univoco tra i vari server MySql della rete Master Slave che si vuole creare. Il mio consiglio è quello di andare in ordine crescente: 1 per il master, dal 2 in poi per gli slave.<br />
Importante: dopo questa modifica, riavviare il servizio MySql. Da questo momento ogni operazione eseguita sul server sarà salvata in un log.<br />
<br />
<h4>
Fase 2: dump del database del server Master</h4>
Si suppone ovviamente che il server MySql che farà da master sia un server che possiede già dei dati al suo interno. Il che significa che è necessario sincronizzare i dati tra master e slave. Per fare ciò, ci viene in aiuto il comando mysqldump<br />
<pre class="php" name="code">mysqldump -h master_host -u root - p --all-databases --master-data -e > dump.sql</pre>
Il comando eseguirà il dump creando il file dump.sql. Apriamo a questo punto il file con un editor di testi e segnamoci, da una parte, i comandi relativi al master_log_file e master_log_pos. Ad esempio, nel mio caso:<br />
<pre class="bash" name="code">MASTER_LOG_FILE='mysql-bin.000016', MASTER_LOG_POS=458850265;</pre>
Come ho scritto, segnamo a parte le informazioni relative al log file e alla posizione del log. La posizione del log indicherà allo slave la posizione di partenza del dump importato.<br />
<br />
<h4>
Fase 3: creazione di un utente specifico per la replicazione</h4>
Questa fase non è obbligatoria ma altamente consigliata. Semplicemente è necessario creare un utente per la replicazione sul server master:<br />
<pre class="bash" name="code">CREATE USER 'repl'@'%' IDENTIFIED BY 'slavepass';
GRANT REPLICATION SLAVE ON *.* TO 'repl'@'%';
</pre>
<br />
<h4>
Fase 4: impostazione del server slave</h4>
Sul server slave, è necessario inserire nel file di configurazione, nella sezione [mysqld], la direttiva<br />
<pre class="bash" name="code">[mysqld]
server-id=2</pre>
o comunque un numero diverso da altri server Mysql e soprattutto differente dall'id del master. Riavviare il servizio MySql del server slave.<br />
<br />
<h4>
Fase 5: importazione del dump sul server slave</h4>
E' necessario in questa fase importare il dump precedentemente creato. Dalla shell eseguire:<br />
<pre class="php" name="code">mysql -h slave_host -u root -p < dump.sql</pre>
Ed attendere il completamento dell'operazione.<br />
<br />
<h4>
Fase 6: impostazione delle informazioni del master sul server slave</h4>
In questa fase, il server slave viene informato su quale sarà il suo master di riferimento. Avendo sotto mano la posizione del log e il nome del log del master segnato in precedenza, è necessario eseguire sul server slave:<br />
<pre class="bash" name="code">CHANGE MASTER TO
MASTER_HOST='master_host',
MASTER_USER='repl',
MASTER_PASSWORD='slavepass',
MASTER_LOG_FILE='mysql-bin.000016',
MASTER_LOG_POS=458850265</pre>
<h4>
Fase 7: avvio della replicazione sul server slave</h4>
Con un semplice<br />
<pre class="bash" name="code">START SLAVE;</pre>
si iniziano le danze. E' possibile a questo punto controllare lo stato della replicazione eseguendo sul server slave<br />
<pre class="bash" name="code">SHOW SLAVE STATUS;</pre>
<br />
[Immagine via clusterdb.com]Unknownnoreply@blogger.com2tag:blogger.com,1999:blog-4120451589748876420.post-65871937159075685132015-05-14T17:28:00.000+02:002015-06-05T14:57:05.981+02:00JarvisPHP - Il tuo maggiordomo personale in Php!<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjh9RaN_iNsOvjoldf-PlJPj2zviOzifFiMtUkDT-PtTZ7jMigCn6QZzKPgFDnXBtTQUIVbKUDDQE0-BxXRidcx_Q9nUwQsnkm5B8-7SPa_ScLgNcAmBiiUISO7CMAvnQv9AIv2cf4y6ec/s1600/J.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="323" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjh9RaN_iNsOvjoldf-PlJPj2zviOzifFiMtUkDT-PtTZ7jMigCn6QZzKPgFDnXBtTQUIVbKUDDQE0-BxXRidcx_Q9nUwQsnkm5B8-7SPa_ScLgNcAmBiiUISO7CMAvnQv9AIv2cf4y6ec/s640/J.png" width="640" /></a></div>
<br />
JarvisPhp è la mia ultima creazione. In breve si tratta di un sistema REST API scritto in Php che permette una interazione diretta con l'utente attraverso dei comandi. Questi comandi possono essere vocali, pronunciati e riconosciuti da un STT (Speech to text) come ad esempio una applicazione Android mediante le funzioni Android STT, e successivamente inviati (in stringa) alle API di JarvisPHP.<br />
Lo scenario è il seguente: l'utente (il padrone di casa) parla attraverso un auricolare bluetooth collegato con uno smartphone, premendo il bottone dell'auricolare. Il comando viene interpretato dal riconoscitore vocale della app ed inviato a JarvisPHP, che tenta di comprenderlo ed eseguire qualche azione.<br />
JarvisPHP è stato pensato per un Raspberry PI vista la sua comodità e dimensione, ma può essere usato su qualsiasi sistema *nix.<br />
<br />
<h3>
<br />Download</h3>
E' possibile scaricare l'ultima versione di JarvisPHP su Github a questo indirizzo:<br />
<a href="https://github.com/bianchins/JarvisPHP" target="_blank">https://github.com/bianchins/JarvisPHP </a><br />
<h3>
<br />Documentazione</h3>
Potete fare riferimento al wiki per tutta la documentazione: <br />
<a href="https://github.com/bianchins/JarvisPHP/wiki/">https://github.com/bianchins/JarvisPHP/wiki/</a><br />
<h3>
<br />Architettura del sistema</h3>
Come può JarvisPHP eseguire una azione? Come riesce ad interagire con l'ambiente? Semplicemente, utilizza dei plugins per eseguire il comando che ha interpretato. Per esempio, chiedendo "Chi sei?" il sistema attiverà il plugin "Info plugin" che risponderà "Il mio nome è...". <br />
Un plugin (<a href="https://github.com/bianchins/JarvisPHP/wiki/How-to-write-a-plugin" target="_blank">è semplicissimo scrivere un proprio plugin, basta seguire queste istruzioni</a>) può fare qualsiasi cosa: per esempio, interagire con l'interfaccia <code>GPIO</code> di un Raspberry Pi (<a href="http://www.raspberrypi.org/">http://www.raspberrypi.org</a>),
suonare musica, interrogare API pubbliche meteo, leggere email, connettersi a facebook e leggere le notifiche, e così via.Unknownnoreply@blogger.com0tag:blogger.com,1999:blog-4120451589748876420.post-85524797233929213352015-01-10T10:25:00.000+01:002015-01-10T10:25:00.138+01:00Summernote - un editor WYSIWYG per BootstrapOggi presento <a href="http://hackerwins.github.io/summernote/" target="_blank">Summernote</a>, un editor WYSIWYG per <a href="http://getbootstrap.com/" target="_blank">Bootstrap</a> molto ben fatto. Tra le caratteristiche che lo rendono un ottimo prodotto (a mio parere) sono la facilità di installazione, la gestione del menù personalizzata e la possibilità di mostrare il sorgente del testo immesso.<br />
<br />
<h4>
Installazione</h4>
Si può installare direttamente dai <a href="https://github.com/HackerWins/summernote/archive/master.zip" target="_blank">sorgenti</a> oppure tramite bower:<br />
<pre class="bash" name="code">bower install summernote
</pre>
<br />
<h4>
Dipendenze</h4>
Utilizza ovviamente Bootstrap (e jQuery), assieme a font-awesome. Questo significa che è necessario includere i seguenti file (se non sono già inclusi nella pagina html):<br />
<pre class="html" name="code"><link href="//netdna.bootstrapcdn.com/font-awesome/4.0.3/css/font-awesome.min.css" rel="stylesheet"></link>
<!-- include summernote css/js-->
<link href="summernote.css" rel="stylesheet"></link>
<script src="summernote.min.js"></script>
</pre>
<br />
<h4>
Inserimento del codice html e javascript</h4>
Un semplice <span style="font-family: "Courier New",Courier,monospace;"><div>
</span> verrà trasformato in un editor Summernote con poche righe di codice javascript:<br />
<pre class="html" name="code"><div id="summernote">Hello Summernote</div>
</pre>
<pre class="javascript" name="code"><script>
$(document).ready(function() {
$('#summernote').summernote();
});
</script>
</pre>
<br />
<h4>
Risultato</h4>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhE376HXS8Yw_TOyIlrGq9lXD9fZ8hwVxtLr4M2R4ofsJaGB9JBmd97xLSniC4dwtOeHlq1Pm5otIb0VXEquDVqA8jvbq5PWtBdW660AW8rGo5eUj8Ie2TL_gcWRr56zwh4OgJTuoVhcWA/s1600/Summernote_-_Super_Simple_WYSIWYG_editor_on_Bootstrap_-_2014-10-25_12.30.56.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhE376HXS8Yw_TOyIlrGq9lXD9fZ8hwVxtLr4M2R4ofsJaGB9JBmd97xLSniC4dwtOeHlq1Pm5otIb0VXEquDVqA8jvbq5PWtBdW660AW8rGo5eUj8Ie2TL_gcWRr56zwh4OgJTuoVhcWA/s1600/Summernote_-_Super_Simple_WYSIWYG_editor_on_Bootstrap_-_2014-10-25_12.30.56.png" style="width: 100%;" /></a></div>
<br />
:-)Unknownnoreply@blogger.com0tag:blogger.com,1999:blog-4120451589748876420.post-80491233714637770352015-01-09T10:22:00.001+01:002015-01-09T10:22:41.075+01:00Il meteo con Php e le API di Yahoo WeatherA tempo perso sto preparando una sveglia con Raspberry PI, che al momento di suonare si colleghi ad un sistema meteo, scarichi le informazioni e <a href="http://stefanobianchini.blogspot.it/2013/08/come-far-parlare-il-raspberrypi-tramite.html" target="_blank">le pronunci tramite un sintetizzatore vocale</a>.<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgEef2NMBDy4JQCRhUePd7kjo-0IH-08pq2uAGHxgjNSDJrkYYZC_tAYsJ2tmOVX0-b8A4foEEGNGUzlMIN70UXXtWUFSkYF6NL8eXbN9O1qespa5MOgtvvt7cU99pyU698BO-hChXZLZw/s1600/Pure_CSS_One_Div_Weather_Animated_Icons_-_2015-01-09_10.16.42.png" imageanchor="1" style="max-width:100%"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgEef2NMBDy4JQCRhUePd7kjo-0IH-08pq2uAGHxgjNSDJrkYYZC_tAYsJ2tmOVX0-b8A4foEEGNGUzlMIN70UXXtWUFSkYF6NL8eXbN9O1qespa5MOgtvvt7cU99pyU698BO-hChXZLZw/s1600/Pure_CSS_One_Div_Weather_Animated_Icons_-_2015-01-09_10.16.42.png" height="85" width="550" /></a></div>
<br />
Il primo passo è quindi quello di sviluppare un sistema che possa comprendere le condizioni meteo esterne. Ho trovato il servizio di API Yahoo Weather e me ne sono innamorato!<br />
Di seguito è mostrato il codice dello script php che ho sviluppato: utilizza le funzioni curl per interfacciarsi con le API che rispondono in JSON, ho mappato i codici delle condizioni meteo in italiano (<a href="https://developer.yahoo.com/weather/documentation.html" target="_blank">come da documentazione ufficiale</a>) e ho scovato su stackoverflow una <a href="http://stackoverflow.com/questions/7490660/converting-wind-direction-in-angles-to-text-words" target="_blank">funzione per tradurre la direzione del vento</a> da gradi alle classiche direzioni da rosa dei venti. <br />
<pre class="php" name="code"><?php
//Tradotto in php dall'originale
//http://stackoverflow.com/questions/7490660/converting-wind-direction-in-angles-to-text-words
function degToCompass($num) {
$val=floor(($num/22.5)+.5);
$arr=["N","NNE","NE","ENE","E","ESE", "SE", "SSE","S","SSW","SW","WSW","W","WNW","NW","NNW"];
return $arr[($val % 16)];
}
$condizioni = array(
"0"=> "tornado",
"1"=> "tempesta tropicale",
"2"=> "uragano",
"3"=> "forti temporali",
"4"=> "temporali",
"5"=> "pioggia mista a neve",
"6"=> "pioggia mista a nevischio",
"7"=> "neve mista a nevischio",
"8"=> "pioviggine gelata",
"9"=> "pioggerella",
"10"=> "pioggia gelata",
"11"=> "rovesci",
"12"=> "rovesci",
"13"=> "raffiche di neve",
"14"=> "rovesci di neve leggeri",
"15"=> "soffia neve",
"16"=> "neve",
"17"=> "grandinare",
"18"=> "nevischio",
"19"=> "polvere",
"20"=> "nebbioso",
"21"=> "foschia",
"22"=> "foschia",
"23"=> "ventoso",
"24"=> "ventoso",
"25"=> "freddo",
"26"=> "nuvoloso",
"27"=> "Sereno",
"28"=> "Sereno",
"29"=> "parzialmente nuvoloso",
"30"=> "parzialmente nuvoloso",
"31"=> "Sereno",
"32"=> "soleggiato",
"33"=> "Sereno",
"34"=> "Sereno",
"35"=> "pioggia mista e grandine",
"36"=> "caldo",
"37"=> "isolati temporali",
"38"=> "temporali sparsi",
"39"=> "temporali sparsi",
"40"=> "Rovesci sparsi",
"41"=> "tormenta di neve",
"42"=> "rovesci di neve sparsi",
"43"=> "tormenta di neve",
"44"=> "parzialmente nuvoloso",
"45"=> "rovesci temporaleschi",
"46"=> "rovesci di neve",
"47"=> "Temporali isolati",
"3200"=> "non disponibile"
);
$BASE_URL = "http://query.yahooapis.com/v1/public/yql";
$yql_query = 'select * from weather.forecast where woeid in (select woeid from geo.places(1) where text="Rimini, Italy") and u="c"';
$yql_query_url = $BASE_URL . "?q=" . urlencode($yql_query) . "&format=json";
$session = curl_init($yql_query_url);
curl_setopt($session, CURLOPT_RETURNTRANSFER,true);
$json = curl_exec($session);
$phpObj = json_decode($json);
echo "\nMeteo per Rimini\n";
echo "----------------\n";
echo "Temperatura: ";
echo $phpObj->query->results->channel->item->condition->temp."° C\n";
echo "Condizioni meteo: ";
echo $condizioni[$phpObj->query->results->channel->item->condition->code]."\n";
echo "Alba: ";
echo $phpObj->query->results->channel->astronomy->sunrise."\n";
echo "Tramonto: ";
echo $phpObj->query->results->channel->astronomy->sunset."\n";
echo "Umidità: ";
echo $phpObj->query->results->channel->atmosphere->humidity."%\n";
echo "Pressione: ";
echo $phpObj->query->results->channel->atmosphere->pressure." millibar\n";
echo "Previsioni: ";
echo $condizioni[$phpObj->query->results->channel->item->forecast[0]->code];
echo ", t. max ".$phpObj->query->results->channel->item->forecast[0]->high;
echo "° C, t. min ".$phpObj->query->results->channel->item->forecast[0]->low." °C \n";
echo "Vento: ";
echo $phpObj->query->results->channel->wind->speed." km/h ";
echo degToCompass($phpObj->query->results->channel->wind->direction)."\n";
</pre>
Unknownnoreply@blogger.com1tag:blogger.com,1999:blog-4120451589748876420.post-86342443500734272422014-10-23T22:18:00.001+02:002015-05-13T15:12:06.804+02:00Estendere un disco LVM in Ubuntu Server 14.04 su Aruba CloudUna delle comodità dei server in cloud è la possibilità di aggiungere risorse man mano che servono. Mentre per CPU e Ram è sufficiente operare sul pannello di controllo dell'infrastruttura cloud, per l'aumento dello spazio disco bisogna eseguire qualche operazione in più. Per quale motivo? Perché lo spazio aggiuntivo, impostato dal configuratore, estende il disco fisicamente, ma non la partizione del sistema operativo. E senza queste operazioni ci si ritroverà un disco più grande, ma la partizione (attiva) del sistema operativo delle dimensioni precedenti!<br />
<br />
Avendo alcuni server su <a href="http://www.cloud.it/" target="_blank">Aruba Cloud</a>, ho avuto la necessità di estendere il disco di una macchina virtuale con sistema operativo Ubuntu Server 14.04. Le istruzioni che mostrerò sono però valide anche per altri sistemi operativi linux, in particolare tutti quelli che supportano <a href="http://it.wikipedia.org/wiki/Gestore_logico_dei_volumi" target="_blank">LVM</a>. <br />
<br />
Il primo passo sarà quello di spegnere la macchina virtuale ed agire sul pannello di controllo del cloud, aggiungendo spazio al disco principale. Nel mio caso sono voluto passare da 10 GB a 30 GB.<br />
Si può ora riattivare la macchina virtuale. I passi sono semplici:<br />
<br />
1. controllare mediante il comando <span style="font-family: "Courier New",Courier,monospace;">parted</span> l'effettivo spazio libero non allocato, digitandovi il comando <span style="font-family: "Courier New",Courier,monospace;">print free</span> ed analizzando il risultato. Ciò che ci interessa, è la riga "Free Space" di 21.5 GB (nel mio caso). <br />
Per uscire, basta digitare "q". <br />
<pre class="bash" name="code">root@CloudServer:~# parted
GNU Parted 2.3
Using /dev/sda
Welcome to GNU Parted! Type 'help' to view a list of commands.
(parted) print free
Model: VMware Virtual disk (scsi)
Disk /dev/sda: 32.2GB
Sector size (logical/physical): 512B/512B
Partition Table: msdos
Number Start End Size Type File system Flags
32.3kB 1049kB 1016kB Free Space
1 1049kB 256MB 255MB primary ext2 boot
256MB 257MB 1048kB Free Space
2 257MB 10.7GB 10.5GB extended
5 257MB 10.7GB 10.5GB logical lvm
10.7GB 32.2GB 21.5GB Free Space
</pre>
2. creare una nuova partizione con il comando<br />
<pre class="bash" name="code">root@CloudServer:~# cfdisk</pre>
selezionare la riga dello spazio libero interessato, selezionare New, poi scegliere partizione Logica [Edit: un utente mi ha giustamente fatto notare che a questo punto, è necessario specificarne il formato, ossia 8E]. Infine selezionare Write per salvare le modifiche e infine Quit per uscire. Nel mio caso è stato necessario riavviare la macchina virtuale, quindi consiglio di farlo.<br />
<br />
3. controllare l'avvenuta creazione della partizione, nel mio caso /dev/sda6<br />
<pre class="bash" name="code">root@CloudServer:~# fdisk -l /dev/sda
Disk /dev/sda: 32.2 GB, 32212254720 bytes
255 heads, 63 sectors/track, 3916 cylinders, total 62914560 sectors
Units = sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 512 bytes
I/O size (minimum/optimal): 512 bytes / 512 bytes
Disk identifier: 0x000bc621
Device Boot Start End Blocks Id System
/dev/sda1 * 2048 499711 248832 83 Linux
/dev/sda2 501758 62914559 31206401 5 Extended
/dev/sda5 501760 20969471 10233856 8e Linux LVM
/dev/sda6 20969535 62914559 20972512+ 83 Linux
</pre>
<br />
4. creare il volume fisico per sda6<br />
<pre class="bash" name="code">root@CloudServer:~# pvcreate /dev/sda6
Physical volume "/dev/sda6" successfully created
</pre>
<br />
5. controllare i volumi fisici<br />
<pre class="bash" name="code">root@CloudServer:~# pvdisplay
--- Physical volume ---
PV Name /dev/sda5
VG Name vg
PV Size 9.76 GiB / not usable 2.00 MiB
Allocatable yes (but full)
PE Size 4.00 MiB
Total PE 2498
Free PE 0
Allocated PE 2498
PV UUID 5fDfUK-6RjG-nFMX-iNgM-44rF-7zz0-4JIr9Y
"/dev/sda6" is a new physical volume of "20.00 GiB"
--- NEW Physical volume ---
PV Name /dev/sda6
VG Name
PV Size 20.00 GiB
Allocatable NO
PE Size 0
Total PE 0
Free PE 0
Allocated PE 0
PV UUID hu4B6U-WDfx-zocW-13uX-BiwJ-2CoM-LHKhT9
</pre>
<br />
6. estendere il nuovo volume creato (sda6) dandogli lo stesso VG name del volume principale (sda5), nel mio caso vg<br />
<pre class="bash" name="code">root@CloudServer:~# vgextend vg /dev/sda6
Volume group "vg" successfully extended
</pre>
<br />
7. controllare il nome del volume logico da estendere (nel mio caso /dev/vg/lv_root)<br />
<pre class="bash" name="code">root@CloudServer:~# lvdisplay
--- Logical volume ---
LV Path /dev/vg/lv_swap
LV Name lv_swap
VG Name vg
LV UUID CohmrO-3wcX-zLMV-exxT-gUDu-dAxo-vJhfD4
LV Write Access read/write
LV Creation host, time ubuntu, 2014-07-18 10:33:32 +0200
LV Status available
# open 2
LV Size 952.00 MiB
Current LE 238
Segments 1
Allocation inherit
Read ahead sectors auto
- currently set to 256
Block device 252:0
--- Logical volume ---
LV Path /dev/vg/lv_root
LV Name lv_root
VG Name vg
LV UUID VxYaWt-nPDZ-zTos-Bhbk-0dyz-vlX4-5P06Qc
LV Write Access read/write
LV Creation host, time ubuntu, 2014-07-18 10:33:44 +0200
LV Status available
# open 1
LV Size 8.83 GiB
Current LE 2260
Segments 1
Allocation inherit
Read ahead sectors auto
- currently set to 256
Block device 252:1
</pre>
<br />
8. estendere il volume logico<br />
<pre class="bash" name="code">root@CloudServer:~# lvextend -l+100%FREE /dev/vg/lv_root
Extending logical volume lv_root to 28.82 GiB
Logical volume lv_root successfully resized
</pre>
<br />
9. estendere il file system<br />
<pre class="bash" name="code">root@CloudServer:~# resize2fs /dev/mapper/vg-lv_root
resize2fs 1.42.9 (4-Feb-2014)
Filesystem at /dev/mapper/vg-lv_root is mounted on /; on-line resizing required
old_desc_blocks = 1, new_desc_blocks = 2
The filesystem on /dev/mapper/vg-lv_root is now 7556096 blocks long.
</pre>
<br />
10. controllare lo spazio libero per assicurarsi che tutto sia andato a buon fine<br />
<pre class="bash" name="code">root@CloudServer:~# df -h
Filesystem Size Used Avail Use% Mounted on
/dev/mapper/vg-lv_root 29G 1.7G 26G 6% /
none 4.0K 0 4.0K 0% /sys/fs/cgroup
udev 991M 4.0K 991M 1% /dev
tmpfs 201M 532K 200M 1% /run
none 5.0M 0 5.0M 0% /run/lock
none 1002M 0 1002M 0% /run/shm
none 100M 0 100M 0% /run/user
/dev/sda1 236M 39M 185M 18% /boot
</pre>
<br />
Ho liberamente adattato le istruzioni <a href="http://www.geoffstratton.com/2013/08/resize-disk-ubuntu-lvm/" target="_blank">che si possono trovare a questo indirizzo</a> per farli funzionare correttamente con il template Ubuntu 14.04 di Aruba Cloud.Unknownnoreply@blogger.com18tag:blogger.com,1999:blog-4120451589748876420.post-86326672114345760552014-04-07T11:00:00.000+02:002014-04-07T11:00:00.949+02:00Includere un file html esterno con jQueryUn semplice snippet per includere dinamicamente il contenuto html di un file esterno con jQuery:<br />
<br />
<pre class="javascript" name="code"> $('body').append($('<div></div>').load('file_esterno.html', function() {
//Qui operazioni opzionali sull'html appena caricato
});
</pre>
:-)Unknownnoreply@blogger.com0tag:blogger.com,1999:blog-4120451589748876420.post-43639588233920256382014-04-03T18:30:00.000+02:002014-04-03T18:30:00.473+02:00jVectorMap - Mappe geografiche vettoriali in Javascript<a href="http://jvectormap.com/" target="_blank">jVectoMap</a> è una utile libreria che permette di mostrare ed interagire con mappe geografiche vettoriali in Javascript. Il vantaggio di questo progetto, oltre a quello di essere open source, è che utilizza tecnologie native dei browser, quindi html, javascript, svg o vml, css, senza bisogno di plugin aggiuntivi. La <a href="http://jvectormap.com/tutorials/getting-started/" target="_blank">sezione tutorial</a> è molto ben fatta, ne consiglio una attenta lettura.<br />
Qualche tempo fa ho avuto bisogno di sfruttare questa libreria per mostrare una mappa geografica della provenienza dei preventivi richiesti dagli utenti su un determinato sito web. Per ottimizzare tutto però ho voluto caricare i dati relativi al numero dei preventivi dei singoli stati in ajax con il formato JSON, vediamo come.
<br />
<pre class="html" name="code"><div id="mappamondo-richieste" style="height:350px;width:100%;"></div>
</pre>
<pre class="javascript" name="code">$.ajax({
type: "GET",
url: 'http://api.example.com/stats/world/',
dataType: "json",
}).done(function( json_response ) {
$('#mappamondo-richieste').vectorMap({
map: 'world_mill_en',
series: {
regions: [{
values: json_response.worldData,
scale: ['#C8EEFF', '#0071A4'],
normalizeFunction: 'polynomial'
}]
},
onRegionLabelShow: function(e, el, code){
if(!json_response.worldData[code]) json_response.worldData[code] = 0;
el.html(el.html()+' ('+json_response.worldData[code]+')');
}
});
}).fail(function(jqXHR, textStatus) {
console.log( "Request failed: " + textStatus + " " + jqXHR.status );
});
</pre>
<br />
La mappa utilizzata è la <a href="http://jvectormap.com/maps/world/world/" target="_blank">world_mill_en</a>, ossia rappresenta l'intero globo terrestre. Le sigle delle nazioni seguono il formato a due lettere (ad esempio IT per Italia, FR per Francia, DE per Germania, ecc.).<br />
L'url richiamata è una API (nel mio progetto scritta in Php) che risponde in formato JSON con una particolare struttura, ad esempio:<br />
<pre class="javascript" name="code">{
"worldData":
{
"IT":"97",
"US":"357"
}
}
</pre>
Il risultato finale non delude le mie aspettative :-)<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEikvDJ7EiPO4nYIAKB725ZCxHfXiVIsQObucCKMvAYFKSKOI3_8QWwjoPl9IFj7aRRmaM8GXyDiUM_oIEBc-vurPZT6pPXLDX3Iy0tvekbP5aXBUv7ZyPhMkvNpHcO9IdLcZMXLVIBJnNc/s1600/jvectormapesempio.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEikvDJ7EiPO4nYIAKB725ZCxHfXiVIsQObucCKMvAYFKSKOI3_8QWwjoPl9IFj7aRRmaM8GXyDiUM_oIEBc-vurPZT6pPXLDX3Iy0tvekbP5aXBUv7ZyPhMkvNpHcO9IdLcZMXLVIBJnNc/s1600/jvectormapesempio.png" /></a></div>
Unknownnoreply@blogger.com1tag:blogger.com,1999:blog-4120451589748876420.post-37595124030967819002014-03-29T15:00:00.000+01:002014-03-29T15:00:06.018+01:00C# - Stampante direttamente con comandi nativiPoco tempo fa ho avuto la necessità di stampare, con comandi nativi, su una stampante termica Zebra.<br />
In passato mi era capitato di dover stampare con comandi nativi, ma su stampantine con porta seriale, quindi i comandi altro non erano che stringhe inviate in seriale.<br />
In questo caso, invece, la stampante era collegata via usb e accessibile solo tramite drivers. Come fare? Incredibilmente la Microsoft ci viene in aiuto, e ha pubblicato <a href="http://support.microsoft.com/kb/322091/it" target="_blank">una classe fantastica chiamata RawPrinterHelper</a> che permette proprio di interagire direttamente con la stampante, a qualsiasi porta essa sia collegata (parallela, usb, seriale). <br />
E' una classe statica, e per usarla è sufficiente eseguire il metodo SendStringToPrinter; il primo parametro sarà il nome della stampante, così come è definita nell'elenco stampanti di Windows, mentre il secondo parametro sarà la stringa di comandi da inviare direttamente alla stampante. <br />
Ad esempio, in caso di una stampante Zebra:<br />
<pre class="csharp" name="code">RawPrinterHelper.SendStringToPrinter("Zebra GX420T", "^XA^FDESEMPIO^FS^XZ");
</pre>
Di seguito posto anche il codice originale della classe, che si può trovare comunque a questo indirizzo: <a href="http://support.microsoft.com/kb/322091/it">http://support.microsoft.com/kb/322091/it<br /></a>
<pre class="csharp" name="code">
using System;
using System.Drawing;
using System.Drawing.Printing;
using System.Windows.Forms;
using System.Runtime.InteropServices;
using System.IO;
namespace TintoLavaApp
{
public class RawPrinterHelper
{
// Structure and API declarions:
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
public class DOCINFOA
{
[MarshalAs(UnmanagedType.LPStr)]
public string pDocName;
[MarshalAs(UnmanagedType.LPStr)]
public string pOutputFile;
[MarshalAs(UnmanagedType.LPStr)]
public string pDataType;
}
[DllImport("winspool.Drv", EntryPoint = "OpenPrinterA", SetLastError = true, CharSet = CharSet.Ansi, ExactSpelling = true, CallingConvention = CallingConvention.StdCall)]
public static extern bool OpenPrinter([MarshalAs(UnmanagedType.LPStr)] string szPrinter, out IntPtr hPrinter, IntPtr pd);
[DllImport("winspool.Drv", EntryPoint = "ClosePrinter", SetLastError = true, ExactSpelling = true, CallingConvention = CallingConvention.StdCall)]
public static extern bool ClosePrinter(IntPtr hPrinter);
[DllImport("winspool.Drv", EntryPoint = "StartDocPrinterA", SetLastError = true, CharSet = CharSet.Ansi, ExactSpelling = true, CallingConvention = CallingConvention.StdCall)]
public static extern bool StartDocPrinter(IntPtr hPrinter, Int32 level, [In, MarshalAs(UnmanagedType.LPStruct)] DOCINFOA di);
[DllImport("winspool.Drv", EntryPoint = "EndDocPrinter", SetLastError = true, ExactSpelling = true, CallingConvention = CallingConvention.StdCall)]
public static extern bool EndDocPrinter(IntPtr hPrinter);
[DllImport("winspool.Drv", EntryPoint = "StartPagePrinter", SetLastError = true, ExactSpelling = true, CallingConvention = CallingConvention.StdCall)]
public static extern bool StartPagePrinter(IntPtr hPrinter);
[DllImport("winspool.Drv", EntryPoint = "EndPagePrinter", SetLastError = true, ExactSpelling = true, CallingConvention = CallingConvention.StdCall)]
public static extern bool EndPagePrinter(IntPtr hPrinter);
[DllImport("winspool.Drv", EntryPoint = "WritePrinter", SetLastError = true, ExactSpelling = true, CallingConvention = CallingConvention.StdCall)]
public static extern bool WritePrinter(IntPtr hPrinter, IntPtr pBytes, Int32 dwCount, out Int32 dwWritten);
// SendBytesToPrinter()
// When the function is given a printer name and an unmanaged array
// of bytes, the function sends those bytes to the print queue.
// Returns true on success, false on failure.
public static bool SendBytesToPrinter(string szPrinterName, IntPtr pBytes, Int32 dwCount)
{
Int32 dwError = 0, dwWritten = 0;
IntPtr hPrinter = new IntPtr(0);
DOCINFOA di = new DOCINFOA();
bool bSuccess = false; // Assume failure unless you specifically succeed.
di.pDocName = "My C#.NET RAW Document";
di.pDataType = "RAW";
// Open the printer.
if (OpenPrinter(szPrinterName.Normalize(), out hPrinter, IntPtr.Zero))
{
// Start a document.
if (StartDocPrinter(hPrinter, 1, di))
{
// Start a page.
if (StartPagePrinter(hPrinter))
{
// Write your bytes.
bSuccess = WritePrinter(hPrinter, pBytes, dwCount, out dwWritten);
EndPagePrinter(hPrinter);
}
EndDocPrinter(hPrinter);
}
ClosePrinter(hPrinter);
}
// If you did not succeed, GetLastError may give more information
// about why not.
if (bSuccess == false)
{
dwError = Marshal.GetLastWin32Error();
}
return bSuccess;
}
public static bool SendFileToPrinter(string szPrinterName, string szFileName)
{
// Open the file.
FileStream fs = new FileStream(szFileName, FileMode.Open);
// Create a BinaryReader on the file.
BinaryReader br = new BinaryReader(fs);
// Dim an array of bytes big enough to hold the file's contents.
Byte[] bytes = new Byte[fs.Length];
bool bSuccess = false;
// Your unmanaged pointer.
IntPtr pUnmanagedBytes = new IntPtr(0);
int nLength;
nLength = Convert.ToInt32(fs.Length);
// Read the contents of the file into the array.
bytes = br.ReadBytes(nLength);
// Allocate some unmanaged memory for those bytes.
pUnmanagedBytes = Marshal.AllocCoTaskMem(nLength);
// Copy the managed byte array into the unmanaged array.
Marshal.Copy(bytes, 0, pUnmanagedBytes, nLength);
// Send the unmanaged bytes to the printer.
bSuccess = SendBytesToPrinter(szPrinterName, pUnmanagedBytes, nLength);
// Free the unmanaged memory that you allocated earlier.
Marshal.FreeCoTaskMem(pUnmanagedBytes);
return bSuccess;
}
public static bool SendStringToPrinter(string szPrinterName, string szString)
{
IntPtr pBytes;
Int32 dwCount;
// How many characters are in the string?
dwCount = szString.Length;
// Assume that the printer is expecting ANSI text, and then convert
// the string to ANSI text.
pBytes = Marshal.StringToCoTaskMemAnsi(szString);
// Send the converted ANSI string to the printer.
SendBytesToPrinter(szPrinterName, pBytes, dwCount);
Marshal.FreeCoTaskMem(pBytes);
return true;
}
}
}
</pre>Unknownnoreply@blogger.com4tag:blogger.com,1999:blog-4120451589748876420.post-1993943704453104362014-03-28T15:53:00.004+01:002014-03-28T15:53:55.600+01:00Chiamate cross thread su controlli winform - C#Se si utilizzano thread durante lo sviluppo di applicazioni c#, si può avere la necessità di agire direttamente su controlli <i>winform</i>, ad esempio impostare il contenuto di una casella di testo in una finestra.<br />
Se però si tenta di agire direttamente sul controllo, ad esempio da un oggetto <span style="font-family: "Courier New",Courier,monospace;">backgroundWorker</span> (che altro non è che un thread), a runtime viene generata una eccezione e il debugger si lamenta per una <b>chiamata cross-thread</b> non correttamente gestita.<br />
Per fortuna la soluzione è alquanto semplice, racchiusa in poche righe di codice. Ad esempio, nel codice eseguito dal <span style="font-family: "Courier New",Courier,monospace;">backgroundWorker</span>, basterà inserire una chiamata invoke sul controllo con il quale intendiamo interagire. <br />Dato una textbox chiamata <span style="font-family: "Courier New",Courier,monospace;">txt_esempio</span>, il codice quindi sarà:<br />
<br />
<pre class="csharp" name="code">try
{
txt_esempio.Invoke((MethodInvoker)delegate() {
txt_esempio.Text = "Testo modificato da backgroundWorker";
});
}
catch (InvalidOperationException ioe) { }
</pre>
Unknownnoreply@blogger.com1tag:blogger.com,1999:blog-4120451589748876420.post-40357960426677111412014-01-20T12:46:00.001+01:002014-01-25T18:29:26.082+01:00Crono, un Time Tracker open source in php codeigniterDopo qualche settimana di lavoro vi presento <a href="https://github.com/bianchins/crono/" target="_blank">Crono</a>, una web application per il time tracking open source (quindi è gratuito) scritta in Php.<br />
Specifico che il software è formato da un sistema di APIs sviluppato in <a href="http://ellislab.com/codeigniter" target="_blank">Codeigniter</a> e un frontend che si basa su <a href="http://getbootstrap.com/" target="_blank">Twitter Bootstrap</a> e <a href="http://jquery.com/" target="_blank">jQuery</a>. Il DBMS utilizzato è MySql.<br />
Per utilizzarlo potete <a href="https://github.com/bianchins/crono/" target="_blank">clonare il repository</a> (sempre aggiornato) oppure <a href="https://github.com/bianchins/crono/archive/master.zip" target="_blank">scaricare la versione 1.0</a>.<br />
Dopo averlo copiato nella cartella <span style="font-family: "Courier New",Courier,monospace;">wwwroot</span> di un webserver che supporti il php è necessario visitare da un browser la cartella <span style="font-family: "Courier New",Courier,monospace;">install</span> per installarlo correttamente.<br />
<br />
<b>Crono</b> ha le seguenti caratteristiche:<br />
<ul>
<li>registrazione e gestione di attività (task) sia live che manuali, collegate a progetti </li>
<li>gestione progetti</li>
<li>gestione clienti </li>
<li>gestione utenti</li>
<li>sistema di installazione</li>
</ul>
<br />
Qualche screenshot di <b>Crono</b>:<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjb_XmPbTbKb4qE_K6XSTm0mLkCsQxPStHr2Tb0nKzgPSApVTICelhJv-GzrSFmwjPu-0pCPAWCnHJk0bvYLhOsbXdNH53WRptDU6ebWjAPDOZDgo1XyqkeuVb9icdtYBzBJimRAzAKaYY/s1600/Crono_Time_Tracker_-_Dashboard_-_2014-01-20_00.09.44.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjb_XmPbTbKb4qE_K6XSTm0mLkCsQxPStHr2Tb0nKzgPSApVTICelhJv-GzrSFmwjPu-0pCPAWCnHJk0bvYLhOsbXdNH53WRptDU6ebWjAPDOZDgo1XyqkeuVb9icdtYBzBJimRAzAKaYY/s1600/Crono_Time_Tracker_-_Dashboard_-_2014-01-20_00.09.44.png" height="154" width="320" /></a></div>
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhJNPD_uArI8rytlOrSQSd3ZHnwdC9G-z02altZaoPpJazzhN_l-jD4x050_qDiOp7LaDKusnncYw0wIQdibmX1Vxxlpj1dgDX4a_SWIeyEGcfeDK4Dp3tAnHGJ4M5Yfc0ft01VTAVKOfY/s1600/Crono_Time_Tracker_-_Dashboard_-_2014-01-20_00.11.17.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhJNPD_uArI8rytlOrSQSd3ZHnwdC9G-z02altZaoPpJazzhN_l-jD4x050_qDiOp7LaDKusnncYw0wIQdibmX1Vxxlpj1dgDX4a_SWIeyEGcfeDK4Dp3tAnHGJ4M5Yfc0ft01VTAVKOfY/s1600/Crono_Time_Tracker_-_Dashboard_-_2014-01-20_00.11.17.png" height="154" width="320" /></a><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEheO2fmEk2hyphenhyphenP-GpNBfiCIbqteD3IjTAw0Vy0P-U8HemkqOIZND4BgP6Et5jsqEGEg-VeGIlLsF8t3hmcwD_9Us2np-ZVZKPWPq-4L5Lqi0Y8itRHcyJHJ0bpW_hn20ExgeajW9xVgXB_w/s1600/Crono_Time_Tracker_-_Customers_-_2014-01-20_00.12.55.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEheO2fmEk2hyphenhyphenP-GpNBfiCIbqteD3IjTAw0Vy0P-U8HemkqOIZND4BgP6Et5jsqEGEg-VeGIlLsF8t3hmcwD_9Us2np-ZVZKPWPq-4L5Lqi0Y8itRHcyJHJ0bpW_hn20ExgeajW9xVgXB_w/s1600/Crono_Time_Tracker_-_Customers_-_2014-01-20_00.12.55.png" height="154" width="320" /></a></div>
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgNDxaEW2jneu0CMRxiVRDY9MS0kR5hyphenhyphenOdBjqviegQphRShWgD4wE9nywUaaEuy9rPm3-0Eiy57aIkrbN81qEi8I8XJBifOpDT0brxGTI2_-PHOdSWIS5FdJmEnqXStrv3aL6nkNpZivQA/s1600/Crono_Time_Tracker_-_Dashboard_-_2014-01-20_00.10.14.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgNDxaEW2jneu0CMRxiVRDY9MS0kR5hyphenhyphenOdBjqviegQphRShWgD4wE9nywUaaEuy9rPm3-0Eiy57aIkrbN81qEi8I8XJBifOpDT0brxGTI2_-PHOdSWIS5FdJmEnqXStrv3aL6nkNpZivQA/s1600/Crono_Time_Tracker_-_Dashboard_-_2014-01-20_00.10.14.png" height="154" width="320" /></a></div>
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgnmrDEHzZph7H4tfoIJW1D6bZ-CS68ISuE5XnOIGu0w4FVTFihHz40bp855SqgW6x0y2YZJnDCCZmU1dhhMsGbUqfkWGJofsUc0dijYjJW-4OLufAhddnXQOAXWbnjN6TZzv-H3XGgBxI/s1600/Crono_Time_Tracker_-_Dashboard_-_2014-01-20_00.10.45.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgnmrDEHzZph7H4tfoIJW1D6bZ-CS68ISuE5XnOIGu0w4FVTFihHz40bp855SqgW6x0y2YZJnDCCZmU1dhhMsGbUqfkWGJofsUc0dijYjJW-4OLufAhddnXQOAXWbnjN6TZzv-H3XGgBxI/s1600/Crono_Time_Tracker_-_Dashboard_-_2014-01-20_00.10.45.png" height="154" width="320" /></a></div>
<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi3hanveqx49j3gECX_i77DC_8ItVqFSYQEe5Z8BlFrjSeYa8QDC4iW_p_PbSOxczbh37KoTHtJRaBOCJqKLuoZyb_WiVzumopnvYCf_AMy_Bz_yAcxhWHtI4ovUSgkyVmDlcBJE9BQCiw/s1600/Crono_Time_Tracker_-_Projects_-_2014-01-20_00.12.47.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi3hanveqx49j3gECX_i77DC_8ItVqFSYQEe5Z8BlFrjSeYa8QDC4iW_p_PbSOxczbh37KoTHtJRaBOCJqKLuoZyb_WiVzumopnvYCf_AMy_Bz_yAcxhWHtI4ovUSgkyVmDlcBJE9BQCiw/s1600/Crono_Time_Tracker_-_Projects_-_2014-01-20_00.12.47.png" height="154" width="320" /></a></div>
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjd3OMhDg2Y_BMkEoK8C2s7VmsFn_mTHrdByl0D_6XLG_oeLD519pO0L0RaoVZOvUI3T4uf49ew7ipHaiSkxJaWr65f6t7i2YCqmYBehPHUUECP44PL5JmvcFif7gctPc8ZId2Ll0b8i-0/s1600/Crono_Time_Tracker_-_Timer_entries_-_2014-01-20_00.12.37.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjd3OMhDg2Y_BMkEoK8C2s7VmsFn_mTHrdByl0D_6XLG_oeLD519pO0L0RaoVZOvUI3T4uf49ew7ipHaiSkxJaWr65f6t7i2YCqmYBehPHUUECP44PL5JmvcFif7gctPc8ZId2Ll0b8i-0/s1600/Crono_Time_Tracker_-_Timer_entries_-_2014-01-20_00.12.37.png" height="154" width="320" /></a></div>
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh9p8k6tPUTGV-_-PrmiKGyicH1rC_iULD0XtD0mm4dUscWWstzrNs0Cq1FGz-RDQo3eWPA9XJbL08Z9iKPg3MKmlvxGRpXXqXTIGPt67H6lLO4R1XelmmuH9QufKzptxB6aNCWAE7Rnfo/s1600/Crono_Time_Tracker_-_Users_-_2014-01-20_00.13.30.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh9p8k6tPUTGV-_-PrmiKGyicH1rC_iULD0XtD0mm4dUscWWstzrNs0Cq1FGz-RDQo3eWPA9XJbL08Z9iKPg3MKmlvxGRpXXqXTIGPt67H6lLO4R1XelmmuH9QufKzptxB6aNCWAE7Rnfo/s1600/Crono_Time_Tracker_-_Users_-_2014-01-20_00.13.30.png" height="154" width="320" /></a></div>
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj2ISf-an_5ZD-RrjNQd10lEkwOTSCbVdbbujNFaN2jFMwAlY87TmryGeRkrChJFHx0_b7ZJvrCy4qYhO8Feayp74xogl8evnr01lbUN8qjMPEX_zZK20kuSMEXWOAZQa9GRq4wV9KjlyA/s1600/Crono_Time_Tracker_-_Users_-_2014-01-20_00.13.40.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj2ISf-an_5ZD-RrjNQd10lEkwOTSCbVdbbujNFaN2jFMwAlY87TmryGeRkrChJFHx0_b7ZJvrCy4qYhO8Feayp74xogl8evnr01lbUN8qjMPEX_zZK20kuSMEXWOAZQa9GRq4wV9KjlyA/s1600/Crono_Time_Tracker_-_Users_-_2014-01-20_00.13.40.png" height="154" width="320" /></a></div>
<span id="goog_1006372130"></span><span id="goog_1006372131"></span><br />Unknownnoreply@blogger.com1tag:blogger.com,1999:blog-4120451589748876420.post-77613588302061765732013-12-06T18:51:00.002+01:002013-12-06T18:51:52.629+01:00Manipolazione di immagini con CodeIgniter<div style="text-align: justify;">
<br /></div>
<div style="text-align: justify;">
CodeIgniter permette di manipolare le immagini con <a href="http://ellislab.com/codeigniter/user-guide/libraries/image_lib.html" target="_blank">una apposita libreria</a>, eseguendo le seguenti operazioni:</div>
<ul>
<li>ridimensionamento</li>
<li>cropping</li>
<li>rotazione</li>
<li>aggiunta di watermark</li>
</ul>
<h3>
Ridimensionamento </h3>
<ul>
</ul>
<div style="text-align: justify;">
La image library viene caricata dal framework mediante l'apposita funzione del framework (<i>load</i>), con parametro un array di configurazione. A seconda della configurazione utilizzata può essere richiamata una funzione di manipolazione immagini. Ad esempio, per eseguire un ridimensionamento devono essere specificati i parametri width e height:</div>
<pre class="php" name="code">$config['image_library'] = 'gd2';
$config['source_image'] = '/percorso/mypic.jpg';
$config['maintain_ratio'] = TRUE;
$config['width'] = 75;
$config['height'] = 50;
//Carico la libreria con la configurazione
$this->load->library('image_lib', $config);
//Ridimensiono l'immagine /percorso/mypic.jpg
$this->image_lib->resize();
</pre>
<div style="text-align: justify;">
Il listato precedente ridimensiona l'immagine sovrascrivendola. Se si preferisce mantenere l'immagine originale, creando una nuova immagine ridimensionata, è possibile utilizzare il parametro di configurazione new_image:
</div>
<pre class="php" name="code">$config['new_image'] = '/percorso/nuovaimmaginediversa.jpg';
</pre>
<h3>
Rotazione</h3>
Una rotazione di una immagine può essere eseguita con 5 opzioni diverse:<br />
<ul>
<li>90 gradi</li>
<li>180 gradi</li>
<li>270 gradi</li>
<li>"hor'' (inversione orizzontale)</li>
<li>"vrt'' (inversione verticale)</li>
</ul>
Di seguito un esempio di codice per l'inversione orizzontale dell'immagine:
<br />
<pre class="php" name="code">$config['image_library'] = 'gd2';
$config['source_image'] = '/percorso/mypic.jpg';
$config['rotation_angle'] = 'hor';
//Carico la libreria con la configurazione
$this->load->library('image_lib', $config);
$this->image_lib->rotate();
</pre>
<h3>
Cropping</h3>
<div style="text-align: justify;">
Per ritagliare una immagine si può utilizzare la funzione <i>crop</i> specificando gli assi X e Y lungo i quali eseguire il cropping:</div>
<pre class="php" name="code">$config['image_library'] = 'gd2';
$config['source_image'] = '/percorso/mypic.jpg';
$config['x_axis'] = '100';
$config['y_axis'] = '60';
//Carico la libreria con la configurazione
$this->load->library('image_lib', $config);
//Eseguo il cropping
$this->image_lib->crop();
</pre>
<h3>
Watermark</h3>
<div style="text-align: justify;">
L'aggiunta di un watermark (filigrana) può avvenire attraverso due modalità, <i>text</i> (aggiunta di un testo all'immagine) e <i>overlay</i> (aggiunta di una piccola immagine in overlay all'immagine originale). Vediamo un esempio dell'ultimo caso, con una piccola immagine da inserire in basso a destra, con un padding di 5 pixel dai vertici:</div>
<pre class="php" name="code">$config['image_library'] = 'gd2';
$config['source_image'] = '/percorso/mypic.jpg';
$config['wm_vrt_alignment'] = 'bottom';
$config['wm_hor_alignment'] = 'right';
$config['wm_padding'] = '5';
$config['wm_overlay_path'] = '/percorso/watermark.png';
//Carico la libreria con la configurazione
$this->load->library('image_lib', $config);
//Eseguo l'aggiunta del watermark
$this->image_lib->watermark();
</pre>
<div style="text-align: justify;">
<br /></div>
<div style="text-align: justify;">
Ora la risposta ad un problema che mi ha fatto sbattere un po' la testa prima di comprenderne la (ovvia) soluzione. Se voglio manipolare due immagini di fila, ma con una configurazione diversa? Notiamo nei listati precedenti come la libreria venga caricata dal metodo <i>load</i> con l'array di configurazione, ma questo deve avvenire solo una volta, dopodiché la libreria è già stata caricata all'interno dell'esecuzione di codeigniter. La risposta è l'utilizzo delle funzioni <i>clear</i> ed <i>initialize</i>, vediamone un esempio concreto:</div>
<pre class="php" name="code">//Impostazioni cropping
$config['image_library'] = 'gd2';
$config['source_image'] = '/percorso/mypic.jpg';
$config['x_axis'] = '100';
$config['y_axis'] = '60';
//Carico la libreria con la configurazione
$this->load->library('image_lib', $config);
//Eseguo il cropping
$this->image_lib->crop();
//Cancello le impostazioni della libreria
$this->image_lib->clear();
//Impostazioni per rotazione orizzontale
$config['image_library'] = 'gd2';
$config['source_image'] = '/percorso/mypic.jpg';
$config['rotation_angle'] = 'hor';
//Carico la libreria con la configurazione
$this->image_lib->initialize($config);
//Eseguo la rotazione
$this->image_lib->rotate();
</pre>
Unknownnoreply@blogger.com0tag:blogger.com,1999:blog-4120451589748876420.post-91758206420987890502013-11-28T09:25:00.001+01:002013-11-28T09:25:42.108+01:00API RESTful con Codeigniter usando codeigniter-restserver<div class="separator" style="clear: both; text-align: center;">
</div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi-hxXs-ZjGt5v4eQooVDl78-By-Q36hcSvN0R-USUfoZK3Pj0ULAjSRMdyKfu4UOQMpi0jUbuVwG4L_apPfEtOAmzwSV7K0BLX_ngcRZM7E2dyC_LKychMbV1FI_5Bo6l0a4ShdZ2ANfs/s1600/restful_api1.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="78" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi-hxXs-ZjGt5v4eQooVDl78-By-Q36hcSvN0R-USUfoZK3Pj0ULAjSRMdyKfu4UOQMpi0jUbuVwG4L_apPfEtOAmzwSV7K0BLX_ngcRZM7E2dyC_LKychMbV1FI_5Bo6l0a4ShdZ2ANfs/s320/restful_api1.png" width="320" /></a></div>
<br />
Nel <a href="http://stefanobianchini.blogspot.it/2013/11/un-semplice-sistema-api-get-con.html" target="_blank">post precedente</a> ho mostrato come creare un semplice sistema
API con il solo metodo GET. Ma se volessimo un server API che risponda
secondo lo standard <a href="http://en.wikipedia.org/wiki/Representational_state_transfer" target="_blank">RESTful</a>, dovremmo gestire anche i metodi POST, PUT
e DELETE. <br />
In particolare, lo standard consiglia di utilizzare:
<br />
<ul>
<li>GET per richieste di lettura di una risorsa</li>
<li>POST per richieste di creazione di una risorsa</li>
<li>PUT per richieste di modi ca di una risorsa</li>
<li>DELETE per richieste di eliminazione di una risorsa </li>
</ul>
Per fare questo velocemente e possibile appoggiarsi ad una libreria di
terze parti, <a href="https://github.com/philsturgeon/codeigniter-restserver" target="_blank">codeigniter-restserver</a>.<br />
<br />
<h3>
Installazione</h3>
Il primo passo è ovviamente scaricare il pacchetto dalla pagina github della libreria. Il pacchetto includerebbe anche CodeIgniter, ma è molto probabile che si voglia integrare <i>restserver </i>in un progetto codeigniter gi à esistente. In questo caso è su fficiente copiare i files
<br />
<pre>application/libraries/Format.php
application/libraries/REST_Controller.php
application/config/rest.php
</pre>
nella propria directory application, e ricordarsi di caricare automaticamente la classe <i>REST_Controller</i> come libreria nel fi le di con gurazione
<br />
<pre>application/config/autoload.php
</pre>
Per personalizzare le opzioni di restserver, basta modi care l'apposito fi le precedentemente copiato:
<br />
<pre>application/config/rest.php
</pre>
<h3>
Funzionamento di base</h3>
Per il buon funzionamento del sistema è necessario creare controller che estendono la classe base <i>REST_Controller</i>. La libreria elabora le richieste sulla base del metodo HTTP utilizzato e della risorsa (controller), eseguendo la funzione corrispondente all'insieme dei due elementi, sempre seguendo la logica CodeIgniter. Per spiegarmi meglio, ipotizziamo di avere il seguente controller:
<br />
<pre class="php" name="code">class News extends REST_Controller
{
public function index_get()
{
// Lettura delle news
}
public function index_post()
{
// Crea una news
}
public function index_put()
{
// Modifica una news
}
public function index_delete()
{
// Elimina una news
}
}
</pre>
Una richiesta del tipo
<br />
<pre>GET http://www.example.com/news
</pre>
comporterà l'esecuzione della funzione <span style="font-family: "Courier New",Courier,monospace;">index_get</span>, <i>index </i>perché l'url è senza una funzione specificata (news è direttamente il controller) e <i>get </i>perché il metodo HTTP utilizzato è, appunto, GET.<br />
<br />
L'accesso ai parametri è garantito dalle funzioni get, post e put.
<br />
<pre class="php" name="code">$this->get('id'); //mappatura di $this->input->get()
$this->post('id'); //mappatura di $this->input->post()
$this->put('id');
</pre>
Per i parametri del metodo DELETE, poiché lo standard non li prevede, è sufficiente gestirli direttamente dalla funzione richiamata dal controllore:
<br />
<pre class="php" name="code">public function index_delete($id)
{
$this->response(array(
'returned from delete:' => $id,
));
}
</pre>
<br />
E' possibile inviare in output una struttura dati con la funzione <span style="font-family: "Courier New",Courier,monospace;">response</span>, gestendo anche la risposta HTTP direttamente (opzionale).
In questo modo la libreria si preoccuperà di formattare la risposta a seconda dello standard impostato nella configurazione (io consiglio JSON).
Inoltre, gestendo le risposte HTTP, è possibile utilizzare codici appropriati come il 201 per <i>HTTP 201 Created</i>
e 404 per <i>HTTP 404 Not Found</i> (in quest'ultimo caso il primo parametro sarà una risorsa vuota, come ad esempio un array senza elementi).
<br />
<pre class="php" name="code">public function index_post()
{
// ...crea una news
$this->response($news, 201); // Manda la risposta HTTP 201
}
</pre>
Queste sono i tips principali per lo sviluppo di un sistema API con codeigniter-restserver. Non ho parlato per motivi di tempo delle altre caratteristiche della libreria, quali la gestione di API Keys, la gestione dell'autenticazione ecc. per le quali rimando direttamente al <a href="https://github.com/philsturgeon/codeigniter-restserver" target="_blank">sito ufficiale</a>.Unknownnoreply@blogger.com0tag:blogger.com,1999:blog-4120451589748876420.post-11391368223451410642013-11-27T08:38:00.002+01:002013-11-27T08:43:47.766+01:00Un semplice sistema API GET CodeIgniter con risposta JSONIn questo post volevo descrivere una implementazione relativa ad un sistema API che usa esclusivamente il metodo HTTP GET, sempre in ottica di progetti web strutturati con una netta separazione tra vista (lato client) e controllo (lato server).<br />
Il mio framework php preferito (<a href="http://ellislab.com/codeigniter" target="_blank">CodeIgniter</a>) gestisce in modo nativo le richieste con metodo HTTP GET, quindi creare un semplice sistema di API a sola lettura è molto semplice. Ipotizziamo di creare una sottocartella chiamata <i>api</i> nella cartella <i>controller</i>, e di posizionarvi dentro i vari controller che rispecchieranno le risorse delle API.<br />
<br />
<pre>application
|- controllers
|- api
|- news.php
</pre>
<br />
A questo punto sappiamo che CodeIgniter gestirà che richieste del tipo<br />
<pre>http://www.example.com/api/news/getAll
http://www.example.com/api/news/getSingle/12</pre>
richiedendo l'esecuzione del controller <i>news.php</i> che compare nell'albero di esempio, con i metodi <i>getAll </i>senza parametri e <i>getSingle</i> con un parametro GET valorizzato a "12".<br />
Vediamo quindi il codice del controller:<br />
<pre class="php" name="code">
class News extends CI_Controller {
public function getAll()
{
$query = $this->db->get('news');
$elenco_news = array();
foreach ($query->result() as $row)
{
$news = new stdClass();
$news->id = $row->id;
$news->titolo = $row->titolo;
$news->contenuto = $row->contenuto;
array_push($elenco_news, $news);
}
echo json_encode($elenco_news);
}
public function getSingle(id)
{
$this->db->from('news')->where('id',$id);
$query = $this->db->get();
$row = $query->row();
$news = new stdClass();
$news->id = $row->id;
$news->titolo = $row->titolo;
$news->contenuto = $row->contenuto;
echo json_encode($news);
}
}
</pre>
Sarà sufficiente quindi creare un controller per ogni tipo di risorsa (ad esempio news, eventi, banner) ed i relativi metodi, ricordandosi di strutturare le risposte in formato JSON come da esempio.Unknownnoreply@blogger.com0tag:blogger.com,1999:blog-4120451589748876420.post-69835717853269215652013-11-19T17:01:00.000+01:002013-11-19T17:01:11.287+01:00Chiamate AJAX cross domain JQuery ad API JSONUltimamente i progetti web-based sono strutturati tutti in modo simile, ossia con un backend API e un frontend che interroga le API, solitamente in ajax.<br />
In particolare i miei progetti sono caratterizzati da PHP + <a href="http://ellislab.com/codeigniter" target="_blank">CodeIgniter</a> lato API (con risposte JSON), e <a href="http://jquery.com/" target="_blank">jQuery</a> lato frontend. <br />
Il jQuery si comporta magnificamente con le chiamate Ajax, ma qualche giorno fa mi è capitato il caso di interrogare le API da un dominio diverso da quello del server dove risiedono. Il problema è relativo al fatto che jQuery non permette chiamate ajax cross-domain con tipologia JSON oppure testo normale, come restrizione di sicurezza.<br />
Leggendo la documentazione, viene specificato che le chiamate cross-domain sono possibili solo per la tipologia di scambio dati JSONP.<br />
Ma che diavolo è <a href="http://www.html.it/articoli/jsonp-e-le-richieste-cross-domain-1/" target="_blank">JSONP</a>? Il fantomatico JSON with Padding è un formato di interscambio dati basato su due semplici punti:<br />
- una funzione di callback<br />
- come parametro della precedente, il JSON che intendiamo trasmettere.<br />
Ad esempio, questo è JSONP
<br />
<pre class="javascript" name="code">funzionecallback({"status":"ok"})
</pre>
Mentre il rispettivo JSON sarebbe<br />
<pre class="javascript" name="code">{"status":"ok"}
</pre>
La funzione di callback lato client si può specificare mediante un apposito comando di configurazione di $.ajax oppure lasciare che sia jQuery a decidere il nome della callback ed a inviarlo come parametro GET (chiamato appunto, <i>callback</i>) alle API.<br />
Vediamo quindi nel dettaglio come gestire le nozioni precedentemente apprese. Lato API, vediamo un semplice esempio in cui vogliamo come output un oggetto di prova contenente id e descrizione:<br />
<pre class="php" name="code">$obj = new stdClass();
$obj->id = "56321564";
$obj->descrizione = "Prova di descrizione";
if ($_GET['callback']!='') {
echo $_GET['callback'].'('.json_encode($obj).')';
}
else {
echo json_encode($obj);
}</pre>
In questo modo siamo in grado di capire in maniera molto semplice (senza header http) in che formato mostrare il risultato: se esiste un parametro GET chiamato callback, lo script deve utilizzare il formato JSONP, altrimenti il JSON normale.
<br />
Ed ora la parte frontend, in jQuery:<br />
<pre class="javascript" name="code">var apiUrl = "http://www.example.com/api/example.php";
$( document ).ready(function() {
$.ajax({
type: "GET",
url: apiUrl,
dataType: "jsonp" ,
crossDomain: true,
}).done(function( json_response ) {
console.log(JSON.stringify(json_response));
}).fail(function(jqXHR, textStatus) {
$('#result').html( "Request failed: " + textStatus + " " + jqXHR.status );
});
});
</pre>
La quale produrrà il risultato voluto in console:
<br />
<pre class="javascript" name="code">{"id":"56321564", "descrizione": "Prova di descrizione"}</pre>
Importanti sono l'opzione <code>dataType</code> nella chiamata Ajax impostata su <code>jsonp</code> e <code>crossDomain </code>impostato su <code>true</code>.Unknownnoreply@blogger.com0tag:blogger.com,1999:blog-4120451589748876420.post-82167160945991668102013-08-04T20:18:00.001+02:002013-08-07T08:25:10.792+02:00Come far parlare il Raspberry Pi tramite php<div class="separator" style="clear: both; text-align: center;">
<a href="http://www.mrinformatica.eu/images/RaspberryPi/raspberry-pi.jpeg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="239" src="http://www.mrinformatica.eu/images/RaspberryPi/raspberry-pi.jpeg" width="320" /></a></div>
<br />
Qualche tempo fa ho cominciato a divertirmi con il mio <a href="http://www.raspberrypi.org/" target="_blank">Raspberry Pi</a>. Una delle prime cose che ho voluto fare è stata quella di far parlare il raspPi attraverso il sintetizzatore vocale. In che modo? Con una pagina php richiamata dall'utente.<br />
Per realizzare il tutto, basta installare un webserver (lighttpd nel mio caso) con interprete php5 e il sintetizzatore vocale (<a href="http://espeak.sourceforge.net/" target="_blank">espeak</a>), configurando i diritti della www-root:<br />
<br />
<pre class="shell" name="code">sudo apt-get install lighttpd
sudo apt-get install php5-common php5-cgi php5
sudo lighty-enable-mod fastcgi-php
sudo service lighttpd force-reload
sudo chown www-data:www-data /var/www
sudo chmod 775 /var/www
sudo apt-get install espeak
</pre>
Facciamo una prova, poi eseguiamo visudo per dare i diritti di esecuzione all'utente <span style="font-family: "Courier New",Courier,monospace;">www-data</span> attraverso <span style="font-family: "Courier New",Courier,monospace;">visudo</span>
<br />
<pre class="shell" name="code">espeak -vit "Prova di testing"
sudo visudo
</pre>
Lo so che è un incredibile errore di sicurezza, ma tanto il mio RaspberryPi sta solo in casa e quindi per facilità ho abilitato l'utente a poter eseguire con <span style="font-family: "Courier New",Courier,monospace;">sudo </span>e senza password qualsiasi comando. Aggiungiamo quindi la seguente riga nel visudo<br />
<pre class="shell" name="code">www-data ALL=(ALL) NOPASSWD: ALL</pre>
Creiamo una pagina php sotto la cartella <span style="font-family: "Courier New",Courier,monospace;">/var/www</span> con il seguente contenuto:
<br />
<pre class="php" name="code"><?php
echo exec(" sudo /usr/bin/espeak -vit \"Questo messaggio viene riprodotto ogni volta che si visita la pagina da un browser\"", $out, $out2);
?>
</pre>
Questo ovviamente apre uno scenario fantastico relativo alla domotica :-)Unknownnoreply@blogger.com0tag:blogger.com,1999:blog-4120451589748876420.post-37880988909578021102013-08-03T11:07:00.001+02:002013-08-03T11:08:09.973+02:00C# - programma per l'update automatico degli eseguibiliHo creato qualche tempo fa un piccolo e semplice programma in C# che si occupa di fare da "loader" per le applicazioni. Ad esempio ipotizziamo di avere una applicazione HelloWorld; il loader (un altro eseguibile) si comporterà come segue:<br />
<ul>
<li>appena viene eseguito, controlla sul server (tramite un percorso di rete windows) se la versione dell'applicazione HelloWorld presente in locale sia meno recente di quella sul server. Se così è, scarica la versione più recente dal percorso (è possibile specificare dalla configurazione se aggiornare una intera cartella o solo l'eseguibile).</li>
<li>una volta finito il primo punto, esegue l'applicazione HelloWorld che si è copiato in locale.</li>
</ul>
Tutto il comportamento è racchiuso dentro un BackgroundWorker (un semplice oggetto per gestire un thread in C#). Vediamo nel dettaglio il comportamento:<br />
<pre class="csharp" name="code"> private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
try
{
FileInfo fi1 =
new FileInfo(Properties.Settings.Default.eseguibileRemoto);
FileInfo fi2 =
new FileInfo(Properties.Settings.Default.eseguibileLocale);
//Se la directory locale non esiste, la creo
if (!(Directory.Exists(Properties.Settings.Default.cartellaLocale)))
{
Directory.CreateDirectory(Properties.Settings.Default.cartellaLocale);
}
//Controllo che il timestamp di modifica del file eseguibile remoto sia
//più recente di quello locale
if (fi1.LastWriteTime.Ticks > fi2.LastWriteTime.Ticks)
{
if (Properties.Settings.Default.copiaTutto)
{
//Devo copiare tutto il contenuto della directory in locale
foreach (string fileTrovato in Directory.GetFiles(Properties.Settings.Default.cartellaRemota))
{
FileInfo FTEMP = new FileInfo(fileTrovato);
//Copio il file remoto in locale
File.Copy(fileTrovato, Properties.Settings.Default.cartellaLocale + FTEMP.Name, true);
}
}
else
{
//Procedura per la copia solo di alcuni files (ad esempio, eseguibili Visual Fox)
foreach (string nomeFile in Properties.Settings.Default.copiaSoloQuestiFiles)
{
FileInfo FTEMP = new FileInfo(nomeFile);
File.Copy(Properties.Settings.Default.cartellaRemota + nomeFile, Properties.Settings.Default.cartellaLocale + nomeFile, true);
}
}
}
}
catch (Exception ex) { }
}
</pre>
Appena terminato il lavoro (<span style="font-family: "Courier New",Courier,monospace;">RunWorkerCompleted</span>), l'applicazione verrà eseguita e il loader verrà terminato:<br />
<br />
<pre class="csharp" name="code"> private void backgroundWorker1_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
//Al termine della copia, mi preparo ad eseguire il programma
ProcessStartInfo p = new ProcessStartInfo();
p.FileName = Properties.Settings.Default.eseguibileLocale;
p.Arguments = Properties.Settings.Default.parametri;
//Se è impostato "eseguiDa" (ad esempio per JT) eseguo il programma dal percorso impostato
if (Properties.Settings.Default.eseguiDa.Length > 0) p.WorkingDirectory = Properties.Settings.Default.eseguiDa;
//Altrimenti uso la cartella locale
else p.WorkingDirectory = Properties.Settings.Default.cartellaLocale;
try
{
//Eseguo il programma
Process.Start(p);
}
catch (Exception exep)
{
MessageBox.Show("Impossibile avviare il programma richiesto\r\n" + exep.Message);
}
Application.Exit();
}
</pre>
Le impostazioni possibili nella configurazione del loader sono le seguenti:<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgujUV5bsws45KsjmlJ6ftOmolyZ5x4LaipUbp4ZxtORrSpU_Y3Rri0UjCT9oe9JIoPnHymnrGhVfVFoyHJqcQsBcxK3PhETrlMvAMEzzL3G0q57j0YmAg4P21VbIjHijIr01I8OVwiyz0/s1600/Immagine.PNG" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="157" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgujUV5bsws45KsjmlJ6ftOmolyZ5x4LaipUbp4ZxtORrSpU_Y3Rri0UjCT9oe9JIoPnHymnrGhVfVFoyHJqcQsBcxK3PhETrlMvAMEzzL3G0q57j0YmAg4P21VbIjHijIr01I8OVwiyz0/s400/Immagine.PNG" width="400" /></a></div>
<br />
<span id="goog_93750297"></span><span id="goog_93750298"></span> <br />
Il codice sorgente può essere liberamente scaricabile da <a href="https://github.com/bianchins/C-Sharp-Update-Loader" target="_blank">Github </a>(qui il link del <a href="http://www.stefanobianchini.net/un-programma-per-lupdate-automatico-degli-eseguibili-in-c/" target="_blank">progetto sul mio sito personale</a>)Unknownnoreply@blogger.com0tag:blogger.com,1999:blog-4120451589748876420.post-41629220941216124782013-07-27T12:05:00.000+02:002013-07-27T12:07:05.907+02:00Android: backup e restore di un database sqlitePer l'applicazione <a href="https://play.google.com/store/apps/details?id=net.stefanobianchini.ricette&hl=it" target="_blank">Mie Ricette</a> mi sono trovato a risolvere un problema: dare la possibilità all'utente di eseguire un backup del database delle ricette.<br />
Il database usato nello sviluppo Android è Sqlite, quindi è essenzialmente un file che può essere copiato in una posizione "sicura" come la scheda SD, o comunque un percorso accessibile collegando il telefono al computer.<br />
Nello script quindi sono presenti due funzioni, <span style="font-family: "Courier New",Courier,monospace;">backupDatabase </span>e <span style="font-family: "Courier New",Courier,monospace;">restoreDatabase</span>. Il primo l'ho trovato in qualche blog (perdonatemi, non mi ricordo quale). Entrambi utilizzano i <a href="http://docs.oracle.com/javase/1.5.0/docs/api/java/nio/channels/FileChannel.html" target="_blank">FileChannel</a>, ma mentre il cuore del funzionamento del primo si ha nella funzione nativa <span style="font-family: "Courier New",Courier,monospace;">FileChannel.transferFrom</span>, nel secondo specularmente si ha in <span style="font-family: "Courier New",Courier,monospace;">FileChannel.transferTo</span>.<br />
<br />
<pre class="java" name="code">package net.stefanobianchini.util;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.channels.FileChannel;
import net.stefanobianchini.ricette.R;
import android.app.Activity;
import android.content.res.Resources;
import android.os.Environment;
import android.util.Log;
import android.widget.Toast;
public class backupdb {
public static void backupDatabase(String dbPath, String nomeFileBackup,
Activity act, Resources res) {
try {
File currentDB = new File(dbPath);// path del db su telefono
File backupDB = new File(sd, nomeFileBackup);// file di destinazione
@SuppressWarnings("resource")
FileChannel src = new FileInputStream(currentDB)
.getChannel();// apriamo un filechannel sul db e sul
// file di destinazione
@SuppressWarnings("resource")
FileChannel dst = new FileOutputStream(backupDB)
.getChannel();
dst.transferFrom(src, 0, src.size());// trasferiamo il contenuto
src.close();
dst.close();
Toast.makeText(
act.getBaseContext(),
res.getString(R.string.msg_db_location) + "\n " +
backupDB.getAbsolutePath(),
Toast.LENGTH_LONG).show();
} catch (IOException e) {
Toast.makeText(act.getBaseContext(), e.toString(),
Toast.LENGTH_LONG).show();
}
}
@SuppressWarnings("resource")
private static void copyFile(File src, File dst) throws IOException {
FileChannel inChannel = new FileInputStream(src).getChannel();
FileChannel outChannel = new FileOutputStream(dst).getChannel();
try {
inChannel.transferTo(0, inChannel.size(), outChannel);
} finally {
if (inChannel != null)
inChannel.close();
if (outChannel != null)
outChannel.close();
}
}
public static void restoreDatabase(String dbPath, String percorsoFileBackup,
Activity act, Resources res) {
try {
File currentDB = new File(dbPath);// path del db su telefono
File backupDB = new File(percorsoFileBackup);// file di backup sorgente
copyFile(backupDB, currentDB);
Toast.makeText(
act.getBaseContext(),
String.format(res.getString(R.string.restore_done),
backupDB.toString()), Toast.LENGTH_LONG).show();
} catch (IOException e) {
Toast.makeText(act.getBaseContext(), e.toString(),
Toast.LENGTH_LONG).show();
}
}
}
</pre>
Ora siamo pronti per eseguire il backup. Da notare che prima di eseguire il backup viene chiuso per sicurezza il db mediante il <a href="http://developer.android.com/reference/android/database/sqlite/SQLiteOpenHelper.html" target="_blank">SQLiteOpenHelper </a>(nel mio caso chiamato db_help);
<br />
<pre class="java" name="code">//Backup del DB sqlite
db_help.close();
backupdb.backupDatabase(db_help.getReadableDatabase().getPath(), "backup_mie_ricette.db", HomeActivity.this, res);
</pre>
<br />Unknownnoreply@blogger.com0tag:blogger.com,1999:blog-4120451589748876420.post-22847898363352445922013-07-26T16:21:00.001+02:002013-07-26T16:22:27.512+02:00C# Reflection, ovvero come richiamare librerie DLL a runtime<div class="separator" style="clear: both; text-align: center;">
<a href="http://www.precisionit.co.uk/images/logos/visualcsharp.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="99" src="http://www.precisionit.co.uk/images/logos/visualcsharp.png" width="320" /></a></div>
<br />
Per lavoro ultimamente mi son trovato a sviluppare un programma per la vendita al dettaglio di cosmesi. Le tecnologie scelte sono state C# (serviva un qualcosa solo su sistema Windows a finestre) e MySql (varie postazioni, sistema distribuito).<br />
La sfida si è presentata quando ho dovuto progettare un sistema dinamico per gestire i registratori di cassa. Questo perché i registratori di cassa sono principalmente di due tipi, "Sweda" e "Ditron" con connessioni diverse e soprattutto comandi diversi. Lo Sweda ha una seriale rs232 mentre il Ditron è sempre collegato in seriale ma si appoggia su un programmino demone che fa il lavoro di comunicazione leggendo un determinato file (invio.txt) in una determinata cartella.<br />
Per riassumere: per uno il C# deve aprire una connessione seriale, per l'altro creare un file. Entrambi hanno una sintassi completamente diversa.<br />
Come fare per ottenere una progettazione "dinamica" che renda facile una successiva espansione, ad esempio l'inserimento di un altro tipo di registratore di cassa con un suo interfacciamento?<br />
<br />
La soluzione intrapresa si basa sulla "<a href="http://msdn.microsoft.com/it-it/library/ms173183%28v=vs.80%29.aspx" target="_blank"><i><b>Reflection</b></i></a>", ossia sulla capacità del C# di caricare una DLL esterna a runtime (e gli oggetti e metodi in essa contenuti, quindi senza che sia per forza inclusa nel progetto), tenendo così separati il progetto della vendita al dettaglio dai singoli progetti C# delle librerie di interfacciamento ai registratori di cassa.<br />
In questo modo, lo sviluppo delle suddette librerie dei registratori di cassa può essere anche lasciato ad un altra persona (o addirittura ditta).<br />
La soluzione è quindi scalabile e si basa sul creare librerie con un nome differente (es. <span style="font-family: "Courier New",Courier,monospace; font-size: small;">sweda.dll</span> e <span style="font-family: "Courier New",Courier,monospace; font-size: small;">ditron.dll</span>) e comportamento differente ma con all'interno un oggetto chiamato <span style="font-family: "Courier New",Courier,monospace; font-size: small;">RegistratoreFiscale</span>, le cui funzioni pubbliche sono identiche in entrambe le librerie.<br />
Vediamo un esempio di scheletro di libreria:<br />
<br />
<pre class="csharp" name="code">using System;
using System.Collections.Generic;
using System.Text;
using System.IO.Ports;
using System.Windows.Forms;
namespace Sweda
{
public static class RegistratoreFiscale
{
public static SerialPort serialPort = null;
public static void inizializza(string opzioni_di_inizializzazione) {}
public static void aggiungiVoceAcquisto(double prezzo, string reparto, string descrizione, double prezzo_originale) {}
public static void scegliOperatore(string operatore) {}
public static void chiusuraScontrino(double totale) {}
}
}
</pre>
A questo punto la libreria <span style="font-family: "Courier New",Courier,monospace;"><span style="font-size: x-small;"><span style="font-size: small;">Ditron</span> </span></span>sarà esattamente identica (o altre librerie in futuro che gestiranno altri registratori), quello che cambia è ovviamente l'implementazione dei metodi pubblici sopracitati.<br />
<br />
Quello che si deve predisporre ora nel progetto principale (quello di vendita al dettaglio) è un oggetto "standard" che riceverà le chiamate ai metodi e si preoccuperà di richiamare questi metodi sulla libreria opportuna (sweda per i pc che hanno uno sweda collegato, ditron per i pc che hanno un ditron collegato, ecc.). Fondamentale la direttiva <span style="font-family: "Courier New",Courier,monospace; font-size: small;">using System.Reflection</span> per usare la caratteristica chiave di questa soluzione progettuale:<br />
<br />
<pre class="csharp" name="code">using System;
using System.Collections.Generic;
using System.Text;
using System.Reflection;
namespace CosmeticSeller.oggetti
{
class RegistratoreFiscale
{
private Type oggettoRegistratoreRuntime;
public RegistratoreFiscale(string tipo_registratore)
{
Assembly SampleAssembly;
SampleAssembly = Assembly.LoadFrom(tipo_registratore + ".dll");
AppDomain.CurrentDomain.Load(SampleAssembly.GetName());
foreach (Type t in SampleAssembly.GetTypes())
{
if (t.Name.Equals("RegistratoreFiscale"))
{
oggettoRegistratoreRuntime = t;
}
}
}
public void aggiungiVoceAcquisto(double prezzo, string reparto, string descrizione, double prezzo_originale)
{
MethodInfo Method = oggettoRegistratoreRuntime.GetMethod("aggiungiVoceAcquisto");
List>object< listaParametri = new List>object<();
listaParametri.Add(prezzo);
listaParametri.Add(reparto);
listaParametri.Add(descrizione);
listaParametri.Add(prezzo_originale);
Method.Invoke(this, listaParametri.ToArray());
}
public void inizializza(string opzioni_di_inizializzazione)
{
MethodInfo Method = oggettoRegistratoreRuntime.GetMethod("inizializza");
List<object> listaParametri = new List<object>();
listaParametri.Add(opzioni_di_inizializzazione);
Method.Invoke(this, listaParametri.ToArray());
}
public void chiusuraScontrino(double totale)
{
MethodInfo Method = oggettoRegistratoreRuntime.GetMethod("chiusuraScontrino");
List<object> listaParametri = new List<object>();
listaParametri.Add(totale);
Method.Invoke(this, listaParametri.ToArray());
}
public void scegliOperatore(string operatore)
{
MethodInfo Method = oggettoRegistratoreRuntime.GetMethod("scegliOperatore");
List<object> listaParametri = new List<object>();
listaParametri.Add(operatore);
Method.Invoke(this, listaParametri.ToArray());
}
}
}
</pre>
Volendo descrivere questa classe in due parole, il suo compito è essere un wrapper tra il programma di vendita e la libreria da utilizzare. Il cuore del funzionamento è nel costruttore, dove viene caricata dinamicamente la dll a partire dal nome passato come parametro. Nella libreria dll viene quindi cercato l'oggetto RegistratoreFiscale (implementato in tutte le librerie) e caricato in memoria come oggetto <span style="font-size: x-small;"><span style="font-family: "Courier New",Courier,monospace; font-size: small;">Type</span> </span>(<span style="font-family: "Courier New",Courier,monospace; font-size: small;">oggettoRegistratoreRuntime</span>).<br />
L'oggetto deve essere di tipo Type poiché il compilatore non è in grado di capire che oggetto sia (sappiamo però quali metodi abbiamo implementato e possiamo richiamarli).<br />
Nelle varie funzioni infatti, le quali mappano le implementazioni nelle varie librerie, viene eseguita l'invocazione del metodo desiderato a runtime (<span style="font-family: "Courier New",Courier,monospace; font-size: small;">Method.Invoke</span>).<span style="font-size: x-small;"><span style="font-family: "Courier New",Courier,monospace;"> </span></span>Durante l'invocazione è possibile passare anche i parametri al metodo desiderato (sotto forma di array di object). <br />
<br />
A questo punto è possibile dal programma principale evocare il wrapper a seconda del registratore fiscale utilizzato (questa informazione nel mio caso è salvata su una tabella "impostazioni" su DB).<br />
<pre class="csharp" name="code">...
RegistratoreFiscale reg = new RegistratoreFiscale("Sweda");
reg.inizializza("COM1");
for( ... ) { //Ciclo tutti gli acquisti
reg.aggiungiVoceAcquisto(prezzoComplessivo, reparto, descrizione, totaleriga);
}
reg.chiusuraScontrino(importo_totale);
...
</pre>
Unknownnoreply@blogger.com0tag:blogger.com,1999:blog-4120451589748876420.post-15913661052240323802013-04-04T16:06:00.003+02:002013-04-04T16:08:30.091+02:00FitStadium - Un social network italiano per stare in formaOggi vi parlo di <a href="http://www.fitstadium.com/" target="_blank">FitStadium</a>, un vero e proprio social network sul fitness e l'attività fisica nato per aiutare gli utenti a migliorare la propria forma fisica. Tre ragazzi italiani, appassionati di cultura fisica e fitness, sono dietro quest'idea e stanno creando una startup appositamente per questo progetto.<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjt4e22XirnPKgiBvppEWwqFhhTCGrLATparsISxg39JTJWE5jfQz-MtvOiJ7Y7ETpsquu4GKLJCL1C1o8MDpiWylf1KJe5qpRuUnthQBZsMRGcd0EE5HgJ40I_-mUYTcXB5S3OaOdZe5s/s1600/In+forma+con+FitStadium_il+fitness+social+network+per+stare+in+forma_20130404-154314.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="211" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjt4e22XirnPKgiBvppEWwqFhhTCGrLATparsISxg39JTJWE5jfQz-MtvOiJ7Y7ETpsquu4GKLJCL1C1o8MDpiWylf1KJe5qpRuUnthQBZsMRGcd0EE5HgJ40I_-mUYTcXB5S3OaOdZe5s/s400/In+forma+con+FitStadium_il+fitness+social+network+per+stare+in+forma_20130404-154314.png" width="400" /></a></div>
<br />
Dal punto di vista tecnico, i creatori hanno deciso di usare una buona base open source "sociale" sviluppato in Php, ossia <a href="http://elgg.org/" target="_blank">Elgg</a>, stravolgendone però completamente il funzionamento, aggiungendo tutte le caratteristiche che rendono FitStadium un prodotto (gratuito!) di tutto rispetto:<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi3cHtDk_SAVJDCn5n7hhnnp20BAGveBLgBZa_h6S0RKJw8OpsUyUDzcyZVQvkUQg3N6XHytiYseBHdybpihYnVrwWg7cLie1iEbKF7I9HwWxcJQob64_Logm1mF4H6L8iHk7vCAzEjKAg/s1600/Dashboard_20130404-154753.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><br /></a></div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi3cHtDk_SAVJDCn5n7hhnnp20BAGveBLgBZa_h6S0RKJw8OpsUyUDzcyZVQvkUQg3N6XHytiYseBHdybpihYnVrwWg7cLie1iEbKF7I9HwWxcJQob64_Logm1mF4H6L8iHk7vCAzEjKAg/s1600/Dashboard_20130404-154753.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="266" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi3cHtDk_SAVJDCn5n7hhnnp20BAGveBLgBZa_h6S0RKJw8OpsUyUDzcyZVQvkUQg3N6XHytiYseBHdybpihYnVrwWg7cLie1iEbKF7I9HwWxcJQob64_Logm1mF4H6L8iHk7vCAzEjKAg/s400/Dashboard_20130404-154753.png" width="400" /></a></div>
<ul>
<li>Ha una bacheca tipica dei social network, dove un utente può interagire con gli altri utenti e confrontarsi</li>
<li>Permette agli utenti di inserire (e tenere traccia) dei propri allenamenti delle categorie più varie (attività cardio, fitness, bodybuilding, sollevamento pesi, attività fisiche all'aperto e tantissime altre). Per intenderci, <a href="http://www.fitstadium.com/exercises" target="_blank">questa pagina elenca tutto il loro database esercizi, costantemente in aggiornamento</a>.</li>
<li>Gli allenamenti inseriti possono essere mostrati agli altri utenti (a seconda delle proprie impostazioni privacy) per chiedere consigli e pareri</li>
<li>E' provvisto di un buon motore statistico per tener traccia dei propri miglioramenti (o peggioramenti :-D)</li>
<li>Se un utente accede con facebook, gli allenamenti possono essere pubblicati anche nella bacheca di facebook </li>
<li>Classifiche mensili per le varie categorie di allenamento </li>
<li>Ha una comunità interna molto attiva</li>
<li>Ha un sistema di chat e messaggistica interna </li>
<li>Si possono trovare degli speciali utenti Personal Trainer </li>
<li>Tra pochissimo pubblicheranno le app mobile (per gli utenti iscritti è già disponibile una beta, per Android e IOS), sviluppate con il framework <a href="http://phonegap.com/" target="_blank">PhoneGap</a> e <a href="http://jquerymobile.com/" target="_blank">Jquery Mobile </a></li>
</ul>
Oltre a tutto questo il portale è in continua evoluzione, e gli sviluppatori aggiungono molto spesso caratteristiche nuove.<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEinPz_Y7LLlBpGg9V8bNCTyPtkzGc6cLQJ81oahRPmw5jWyh7m-VGlqRiJXgkKwhqDQEeZeDToNal56tSfP1UgnzuoJKQ_3JAbSfLzkzgT5uI2kUwr7mTQAX29J7G7ge26hgZHaKQ4Ursk/s1600/FitStadium_20130404-160339.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="266" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEinPz_Y7LLlBpGg9V8bNCTyPtkzGc6cLQJ81oahRPmw5jWyh7m-VGlqRiJXgkKwhqDQEeZeDToNal56tSfP1UgnzuoJKQ_3JAbSfLzkzgT5uI2kUwr7mTQAX29J7G7ge26hgZHaKQ4Ursk/s400/FitStadium_20130404-160339.png" width="400" /></a></div>
<br />
<br />
Se siete alla ricerca di motivazioni, di un metodo (pseudo) ingegneristico per migliorare la vostra forma fisica, o semplicemente avete bisogno di una comunità attiva da cui attingere tante informazioni sul fitness... iscrivetevi! <br />
<br />
Cosa posso dire di più... in bocca al lupo e complimenti ai creatori!Unknownnoreply@blogger.com0tag:blogger.com,1999:blog-4120451589748876420.post-75015723914093403592012-11-28T17:51:00.001+01:002013-08-01T10:19:06.859+02:00Guida Codeigniter a modo mio (pdf) in italianoPer chi ha apprezzato i recenti post riguardanti Codeigniter, accoglierà (spero) con felicità questa guida in pdf che ho scritto (in italiano) sul mio framework php preferito. L'ho intitolata "Codeigniter: a modo mio" perché è così che io faccio le cose: a modo mio :-)<br />
<br />
La potete trovare a questo indirizzo:<br />
<a href="http://www.stefanobianchini.net/codeigniter-a-modo-mio/guida-codeigniter-a-modo-mio-1.0.pdf">http://www.stefanobianchini.net/codeigniter-a-modo-mio/guida-codeigniter-a-modo-mio-1.0.pdf</a><br />
<br />
E' la prima versione e sicuramente ho tralasciato tante cose che con il tempo vedrò di integrare. Ed è certo che avrò fatto qualche errorino, quindi siate clementi!Unknownnoreply@blogger.com7