sabato 10 giugno 2017

Creazione di un servizio Windows con Visual Studio

CREAZIONE DI UN SERVIZIO WINDOWS CON VISUAL STUDIO
In un sistema operativo Windows, un servizio è un’entità, a cui è associato un eseguibile, che effettua operazioni senza l’intervento di un utente specifico, perché il servizio “appartiene” al sistema operativo.
Solitamente è utilizzato per operazioni in cui non deve essere richiesto l’intervento dell’utente.
Ma vediamo come creare un servizio windows con Visual Studio.
Visual Studio solitamente ha tra i suoi Modelli di progetto anche il Servizio Windows, comunque è possibile creare direttamente una classe che implementa la classe base  System.ServiceProcess.ServiceBase. Questa classe contiene un metodo astratto OnStart(string[] args) , che verrà chiamato quando il servizio viene avviato.
Purtroppo però non è possibile eseguire il debug su Visual Studio come eseguiamo il debug con altri programmi che presentano il metodo main. Per provare il nostro programma dobbiamo compilare l’eseguibile exe che per funzionare dovrà essere installato sul sistema. Ecco che la sovrastruttura .NET ci agevola il tutto aggiungendo un installer al nostro progetto.
Un Installer per un servizio non è altro che una classe che eredita da una classe della piattaforma .NET i cui metodi verranno chiamati da un apposito tool (InstallUtil) per installare il servizio.
 Ecco i due modi possibili per aggiungere un installer alla nostra classe:
1.     Apriamo la nostra classe in Design Mode, e cliccando col tasto destro su uno spazio vuoto, scegliamo “Add Installer”
2.     Aggiungendo una classe che eredita da System.Configuration.Install.Installer
Seguendo il secondo metodo, aggiungiamo manualmente una classe e la facciamo ereditare da System.Configuration.Install.Installer.
A questa classe aggiungiamo due proprietà:
private System.ServiceProcess.ServiceProcessInstaller serviceProcessInstaller1;
private System.ServiceProcess.ServiceInstaller serviceInstaller1;
La prima proprietà è l’installer relativo al processo che verrà lanciato dal nostro servizio, mentre la secondo è l’installer relativo al processo vero e proprio.
Impostiamo i campi dell’oggetto serviceProcessInstaller1:
·        Parent: è il nome della classe di tipo System.Configuration.Install.Installer che contiene il serviceInstaller
·        Account: il tipo di “privilegio” che avrà il nostro processo. Sono 4 i tipi:
o   LocalService: Utente con i più bassi privilegi
o   NetworkService: Medio privilegio
o   LocalSystem: Utente coi più alti privilegi
o   User: Avvio del servizio con un utente in particolare
Per il nostro esempio, scegliamo il secondo, quindi this.serviceProcessInstaller1.Account = System.ServiceProcess.ServiceAccount.LocalSystem;
Impostiamo invece quelli dell’oggetto  serviceInstaller1:
·        L’unico campo rilevante è il ServiceName, ovvero il nome del nostro servizio, quello che comparirà tra i servizi del tool mmc.exe
Adesso che il nostro servizio base è impostato, aggiungiamo un qualcosa che ci dice che il nostro servizio funziona, altrimenti lo vedremo con lo stato “in esecuzione” non trovando nessuna evidenza del fatto che stia funzionando
APPLICAZIONE: AGGIUNTA DI UN TIMER
Aggiungiamo una proprietà di tipo System.Timers.Timer alla nostra classe relativa al servizio.
Facendo un override del metodo OnStart(string[] args), che verrà eseguito non appena viene avviato il processo, aggiungiamo il codice dove inizializziamo il nostro timer:

protected override void OnStart(string[] args)
        {
            timer1 = new Timer(5000);
            timer1.Elapsed += Tick;
            timer1.Enabled = true;
            timer1.Start();
        }
Il nostro è quindi partito, eseguendo il metodo Tick ogni 5000 millisecondi.
Nel metodo Tick metteremo del codice che scrive un file di testo nella root del nostro hard disk. E’ importante che per fare questo, il tipo di servizio venga lanciato coi privilegi di LocalSystem, come detto sopra.
Di seguito il metodo Tick:

protected override void Tick(object sender, ElapsedEventArgs e)
        {
            StreamWriter t = new StreamWriter("C:\\DMTService_log.txt", true);
            t.WriteLine(DateTime.Now + "-evento tick");
            t.Close();
        }

Adesso che il nostro servizio è pronto, compiliamo il tutto e passiamo all’installazione del servizio.

INSTALLAZIONE DEL SERVIZIO
L’installazione del servizio verrà fatta col metodo installutil.exe, un tool della piattaforma che troviamo nella directory della nostra installazione .Net.
Per esempio, aprendo cmd.exe, scriviamo sulla console:
C:\Windows\Microsoft.NET\Framework\v4.0.30319\installutil.exe C:\Users\Alberto\Documents\DMTDocumentService\DMTDocumentService\bin\Release\DMTDocumentService.exe
A questo punto il servizio è installato e quindi basta andare su Gestione Attività di Windows, tab Servizi, cercare il nostro servizio e attivarlo.

Basta andare sulla root “C:\” e vedremo il file DMTService_log.txt con il log che si aggiorna ogni 5 secondi. 

domenica 8 gennaio 2017

Wastats, app Android per le statistiche whatsapp

Avete mai avuto la "curiosità" di fare le statistiche dei vostri messaggi whatsapp? Fare una ricerca per periodi, mittente, testo, argomento ecc.? Ebbene, io si. Dopo varie soluzioni avallate per creare una app del genere, sono arrivato a una soluzione concreta per farlo.

Si sa, il database whatsapp è criptato, prima aveva una chiave di cifratura che si era conosciuta, ma con i nuovi aggiornamenti whatsapp era diventata obsoleta. Ho cercato più volte di decriptare quel database, ma non ci sono mai riuscito con una soluzione duratura nel tempo. Oltretutto, ultimamente le conversazioni sono criptate end-to-end, quindi è ancora più difficile.

La soluzione che ho adottato è quella di esportare una singola conversazione, gruppo o broadcast tramite testo (è possibile inviarlo tramite e-mail in formato testo).
La piccola app che ho scritto non fa altro che leggere questo file di testo, fare il parsing e caricarlo tutto su un database interno della app, in modo da fare query. La versione attuale permette di interrogare il database manualmente con le query, ma sto già lavorando a un'interfaccia più user-friendly con i filtri. 

Per ora Wastats è fortemente in BETA, l'interfaccia è molto spartana, perché finora mi sono dedicato principalmente alla part funzionale.
Attualmente ci sono essenzialmente due funzioni: quella per caricare il database con nuovi dati di una nuova conversazioni e quella per interrogare il database. 

Aggiorna database

Questa è la funzione per caricare nuovi dati. "Seletta" è un tasto che non fa nulla, l'ho messo all'inizio pensando mi dovesse servire invece no, stessa cosa per "Crea database". Basta premere il tasto aggiorna e il programma cercherà nella memoria esterna (o nella cartella download, non ricordo) i file di tipo testo che iniziano con il filtro "Chat Whatsapp". Dopo che vengono elencati, basta premere il file che si vuole caricare, e vi verrà chiesto un "argomento", ovvero una stringa che identifica la conversazione esempio "famiglia", oppure "gruppo amici", per facilitare l'estrazione dei dati. Dopo di che premere OK e una progress bar ci mostrerà l'avanzamento del processo. 









Interroga database

Da qui invece si fanno le query sqlite vere e proprie sul database, come da screenshot:
 

Qui c'è il link con lo zip che contiene l'apk:
https://drive.google.com/file/d/0B2K4nLFPcS-obTZBTzdMNU5RYUE/view?usp=sharing



giovedì 9 giugno 2016

JMusicMan versione 5.0

Dopo il primo post su JMusicMan, adesso ho migliorato il programma sotto diversi aspetti. 

La funzione principale rimane invariata, ma adesso grazie al supporto di un'altra libreria, il programma gestisce diversi file audio.

Ecco le migliorie principali:

  • La lettura dei tag non presenta più bug; anzi è prevista la funzione "Reset tag" per creare un nuovo tag e non entrare in contrasto con tag creati precedentemente da programmi diversi (la cui compatibilità purtroppo, viene a cadere)
  • Eliminazione delle cartelle vuote e notifica nel caso ci siano cartelle contenenti esclusivamente materiale non-audio
  • Gestione delle ambiguità: se due o più file hanno lo stesso tag si presentano due casi:
    • I file hanno la stessa durata quindi probabilmente sono uguale; si chiede all'utente se eliminare i doppi o crearne una copia rinominata
    • I file hanno durata diversa: alla fine della procedura di aggiornamento viene chiesto di modificare i tag dei file audio, pena l'esclusione della libreria. Il messaggio si ripresenterà al successivo aggiornamento.
Tra le caratteristiche che tuttora sono in via di sviluppo (anzi sono già state implementate, ma la versione è beta) figurano:
  • Ricerca di canzoni nella libreria
  • Pannello laterale per visualizzare alcune informazioni
  • Vari automatismi fra cui numerazione automatica, riconoscimento traccia e riconoscimento tracce album tramite il servizio musicbrainz
  • Modifica titoli massivamente (in sviluppo)
Questo è uno screenshot della versione beta:

Panoramica

All'avvio il programma cercherà automaticamente file audio nella cartella "musica" del computer e comincia la catalogazione:

Se si sono verificati errori (ambiguità di tag o altro) allora una finestra di dialogo mostrerà le tracce "problematica" e chiederà all'utente la modifica dei tag:


L'utente può modificare le informazioni direttamente dentro il form. E' possibile selezionare anche più tracce. Titolo e numero traccia si memorizzano dinamicamente scrivendo nei relativi campi, Artista e Album invece si memorizzando premendo i tasti coi tre puntini sulla destra.

È anche possibile correggere le tracce anche quando sono taggate, sia singolarmente che massivamente (in quest'ultimo caso è possibile modificare solo Artista, Album e Immagine):

La sincronizzazione dei dispositivi

È una funzione che non ho curato molto ultimamente, ma comunque ricordo funzionava a dovere. Dopo aver impostato il dispositivo contenente il file .is_audio_player, il programma sincronizza la libreria del PC con quella del dispositivo trovando le tracce da copiare e quelle da eliminare semplicemente comparando due file XML che sono quelli che si trovano nella cartella Musica ("JMusicManLibrary.xml")

domenica 17 aprile 2016

Flacreader, lettore di commenti (tag metadata) dei file flac

Come un surplus per JMusicMan, il progetto che avevo iniziato in java per la catalogazione della musica sul mio PC e che avevo abbandonato per l'impossibilità di gestire i file .flac, ho sviluppato questa classe, ancora da migliorare, per leggere e modificare i commenti (si chiamano così, più precisamente: vorbis comments) di questi file lossless. 

Però è una classe scritta in C#, dovrei convertirla di nuovo in java, o creare un binding. 

Da questa pagina ho consultato come è formato l'header dei file flac, dunque con delle operazioni sui file binari bit a bit sono riuscito a creare questa classe che attualmente legge bene i file, modifica bene anche i file che hanno già una sezione vorbis comment, ma ancora non ho testato la modifica dei file che non hanno la suddetta sezione, e quindi sarà la caratteristica che implementerò al prossimo commit su git. 

La pagina git è questa: https://github.com/standuptall/Flacreader, con due branch, uno quello principale (la classe è Flacreader.cs) e un'altro branch (grafica) che include una piccola interfaccia grafica. Anche se con git devo prenderci ancora la mano. 
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.IO;

namespace it.albe
{
    public class FlacReader
    {
        public static string vendor_string;
        public static Int32 user_comment_list_length;
        private string filepath;
        private bool data_written = false;
        private class _metadata {
            public struct _streaminfo
            {
                public bool presente;
                public const int code = 0x0;
                public bool ultimo;
            }
            public struct _padding
            {
                public bool presente;
                public const int code = 0x1;
                public bool ultimo;
            }
            public struct _application
            {
                public bool presente;
                public const int code = 0x2;
                public bool ultimo;
            }
            public struct _seektable
            {
                public bool presente;
                public const int code = 0x3;
                public bool ultimo;
            }
            public struct _vorbis_comment
            {
                public bool presente;
                public const int code = 0x4;
                public bool ultimo;
            }
            public struct _cuesheet
            {
                public bool presente;
                public const int code = 0x5;
                public bool ultimo;
            }
            public struct _picture
            {
                public bool presente;
                public const int code = 0x6;
                public bool ultimo;
            }

            public _streaminfo streaminfo;
            public _padding padding;
            public _application application;
            public _seektable seektable;
            public _vorbis_comment vorbis_comment;
            public _cuesheet cuesheet;
            public _picture picture;
            public const int ending_metadata_code = 0xF0;
            const int NUM_METADATA = 7;
            public _metadata()
            {
                streaminfo.presente = false;
                streaminfo.ultimo = false;
                padding.presente = false;
                padding.ultimo = false;
                application.presente = false;
                application.ultimo = false;
                seektable.presente = false;
                seektable.ultimo = false;
                vorbis_comment.presente = false;
                vorbis_comment.ultimo = false;
                cuesheet.presente = false;
                cuesheet.ultimo = false;
                picture.presente = false;
                picture.ultimo = false;
            }
            public void setMetadata(int code)
            {
                switch ((code<<1)>>1)  //tolgo il bit più significativo
                {
                    case _streaminfo.code: streaminfo.presente = true; break;
                    case _padding.code: padding.presente = true; break;
                    case _application.code: application.presente = true; break;
                    case _seektable.code: seektable.presente = true; break;
                    case _vorbis_comment.code: vorbis_comment.presente = true; break;
                    case _cuesheet.code: cuesheet.presente = true; break;
                    case _picture.code: picture.presente = true; break;
                }
            }
            public int getFlag(int flag)  //devo controllare se è l'ultimo, così setto il bit più significativo a 1
            {
                flag = ((flag << 1) >> 1); //tolgo il bit più significtivo
                if (picture.presente)
                    picture.ultimo = true;
                else if (cuesheet.presente)
                    cuesheet.ultimo = true;
                else if (vorbis_comment.presente)
                    vorbis_comment.ultimo = true;
                else if (seektable.presente)
                    seektable.ultimo = true;
                else if (application.presente)
                    application.ultimo = true;
                else if (padding.presente)
                    padding.ultimo = true;
                else if (streaminfo.presente)
                    streaminfo.ultimo = true;
                switch (flag)
                {
                    case _streaminfo.code: if (streaminfo.presente) flag += ending_metadata_code; break;
                    case _padding.code: if (streaminfo.presente) flag += ending_metadata_code; break;
                    case _application.code: if (streaminfo.presente) flag += ending_metadata_code; break;
                    case _seektable.code: if (streaminfo.presente) flag += ending_metadata_code; break;
                    case _vorbis_comment.code: if (streaminfo.presente) flag += ending_metadata_code; break;
                    case _cuesheet.code: if (streaminfo.presente) flag += ending_metadata_code; break;
                    case _picture.code: if (streaminfo.presente) flag += ending_metadata_code; break;
                }
                return flag;
            }
        } ;
        _metadata metadata;
        private int metadata_comments_length;
        public Dictionary comments;
        public FlacReader(string filepath)
        {
            metadata_comments_length = 0;
            metadata = new _metadata();
            comments = new Dictionary();
            this.filepath = filepath;
            user_comment_list_length = 0;
            FileStream stream;
            stream = File.OpenRead(filepath);
            if (stream == null)
                throw new FileNotFoundException("Il file non esiste");
            byte[] array = {0,0,0,0};
            stream.Read(array,0,4);
            if (!((array[0]==0x66)&&(array[1]==0x4C)&&(array[2]==0x61)&&(array[3]==0x43)))   //fLaC
                throw new Exception("Il file non è un file flac!");
            byte flag;
            do 
            {
                stream.Read(array,0,1);  //mi sposto di 1 byte
                flag = array[0];
                metadata.setMetadata(flag);
                stream.Read(array, 0, 3);  //leggo tre byte per la lunghezza del metadata
                Int32 metadata_length = array[0] * 65536 + array[1] * 256 + array[2];
                if (flag == 4 || flag == 132)   //se è un vorbis comment cioè 00000100 oppure 10000100
                {
                    metadata_comments_length = metadata_length;
                    caricaCommenti(stream);
                }
                else
                    stream.Seek(metadata_length, SeekOrigin.Current); //mi sposto della lunghezza del metadata
            } while (!((flag>>7)==0x1)); //se il bit più significativo è uguale a uno vuol dire ch enon ci sono più metadata
            stream.Close();
        }
        public void setVendor(string vendorName)
        {
            int vendor_string_length_old = vendor_string.Length;  
            metadata_comments_length -= vendor_string_length_old;//tolgo la lunghezza iniziale
            vendor_string = vendorName;
            metadata_comments_length += vendor_string.Length;  //e metto la nuova lunghezza del vendor string
        }
        public string getVendor()
        {
            return vendor_string;
        }
        public void addComment(string field, string value)
        {
            try
            {
                if (comments[field] != null)   //se il commento già esiste
                {
                    string val = comments[field];
                    metadata_comments_length -= (field.Length + val.Length + 1);   //tolgo la lunghezza originaria
                    metadata_comments_length -= 4;
                }
            }
            catch(KeyNotFoundException e)
            {
                comments[field] = value;
                user_comment_list_length++;
                metadata_comments_length += 4; //aggiungo 4 byte per memorizzare la lunghezza del comment sul file
                metadata_comments_length += field.Length + value.Length + 1;  //aggiungo la lunghezza del commento più il simbolo uguale
            }
            
            
        }
        public void writeAll()  //file esistente quindi riverso il contenuto su un file _temp
        {
            FileStream stream,streamWrite;
            stream = File.OpenRead(filepath);
            streamWrite = File.OpenWrite(filepath + "_temp");
            byte[] array = { 0, 0, 0, 0 };
            stream.Read(array, 0, 4);
            streamWrite.Write(array, 0, 4);
            byte flag;
            do
            {
                stream.Read(array, 0, 1);  //mi sposto di 1 byte
                streamWrite.Write(array, 0, 1);  //mi sposto di 1 byte
                flag = array[0];
                stream.Read(array, 0, 3);  //leggo tre byte per la lunghezza del metadata
                Int32 metadata_length = array[0] * 65536 + array[1] * 256 + array[2];
                if (flag == 4 || flag == 132)  //se è un vorbis comment cioè 00000100 oppure 10000100
                {
                    stream.Seek(metadata_length, SeekOrigin.Current); //mi sposto sul lettore della lunghezza del metadata
                    array[0] = (byte) (metadata_comments_length >> 16);
                    array[1] = (byte) (metadata_comments_length >> 8);
                    array[2] = (byte)(metadata_comments_length);
                    streamWrite.Write(array, 0, 3);  //scrivo la nuova lunghezza del metadata
                    scriviCommenti(streamWrite);
                }
                else
                {
                    streamWrite.Write(array, 0, 3);  //scrivo la lunghezza del metadata
                    int i = 0;
                    for (i = 0; (i+4)< metadata_length; i+=4)
                    {
                        stream.Read(array, 0, 4);
                        streamWrite.Write(array, 0, 4);
                    }
                    /* scrivo i byte rimanenti */
                    stream.Read(array, 0, metadata_length-i);
                    streamWrite.Write(array, 0, metadata_length -i);
                }
            } while (!((flag >> 7) == 0x1)); //se il bit più significativo è uguale a uno vuol dire ch enon ci sono più metadata
            /* scrivo tutto il rimanente */
            byte[] chunk = new byte[10240];
            
               
            int num = 0;
            while (true)
            {
                num = stream.Read(chunk, 0, 10240);
                streamWrite.Write(chunk, 0, num);
                if (num < 10240)
                {
                    stream.Close();
                    streamWrite.Close();
                    return;
                }
            }
            
        }
        public void writeAll(String filename)  //nuovo file
        {
        }
        private void caricaCommenti(Stream stream)
        {
            byte[] array = { 0, 0, 0, 0 };
            stream.Read(array, 0, 4);
            uint vendor_length = (uint)((array[0]));  //non capisco perché ma conta solo il primo byte
            byte[] stringa = new byte[1024];
            stream.Read(stringa, 0, (int)vendor_length);
            vendor_string = System.Text.Encoding.UTF8.GetString(stringa,0,(int)vendor_length);
            stream.Read(array, 0, 4); //leggo il numero dei commenti
            uint number_of_comments = (uint)array[0];
            for (uint i = 0; i < number_of_comments; i++)
            {
                stream.Read(array, 0, 4); //leggo il numero di caratteri da leggere
                uint comment_length = (uint)array[0];
                stream.Read(stringa, 0, (int)comment_length);
                String comment = System.Text.Encoding.UTF8.GetString(stringa, 0,(int) comment_length);
                comments[comment.Split('=')[0]] = comment.Split('=')[1];
                Array.Clear(stringa, 0, stringa.Length);
            }
        }
        private void scriviCommenti(Stream stream)
        {
            Int32 length = vendor_string.Length;
            byte[] array = { 0, 0, 0, 0 };
            array[0] = (byte)(length);
            array[1] = 0;
            array[2] = 0;
            array[3] = 0;
            stream.Write(array, 0, 4);
            byte[] arrayName = System.Text.Encoding.UTF8.GetBytes(vendor_string.ToCharArray());
            stream.Write(arrayName, 0, length);
            byte numcommenti = (byte)comments.Count;
            array[0] = (byte)(numcommenti);
            stream.Write(array, 0, 4);
            /* scrivo i commenti */
            foreach (KeyValuePair entry in comments)
            {
                string comment = entry.Key + "=" + entry.Value;
                length = comment.Length;
                arrayName = System.Text.Encoding.UTF8.GetBytes(comment.ToCharArray());
                array[0] = (byte)length;
                array[1] = 0;
                array[2] = 0;
                array[3] = 0;
                stream.Write(array, 0, 4);
                stream.Write(arrayName, 0, length);
            }
            data_written = true;
        }
    }
}

venerdì 26 febbraio 2016

Turbo Vision, la visione dell'era delle interfacce grafiche secondo la Borland

Era il 1990, e la Borland, con un po' di ritardo (diciamo, un ritardo spaventoso) lancia il framework Turbo Vision, quando già la Apple e la Microsoft rivaleggiavano con le interfacce grafiche in VGA e la modalità testuale del DOS era un vecchio ricordo. 

Si tratta di un framework object-oriented (lanciato in concomitanza con l'aggiornamento del linguaggio Turbo Pascal 7.0, con l'implementazione dell'object orienting) che fornisce una interfaccia grafica (o meglio, una imitazione di una interfaccia grafica) in modalità testuale, con l'uso di diversi widget (finestre, tasti, menu, barra di scelta veloce ecc.). Purtroppo non ebbe molto seguito per ovvi motivi, giusto un anno dopo veniva rilasciato Windows 3.1, il primo Microsoft OS completamente grafico. 

Però. Però.
Non nego che ha un fascino innegabile. Quello schermo pieno di pixel e quei movimenti scattosi, gli stratagemmi per le ombreggiature e le animazioni per rendere tridimensionale l'ambiente sono molto belli da vedere, e mi piacciono ancora oggi. E devo dire che non sono l'unico.

L'angolo della nostalgia

Ho iniziato a programmare in Pascal, e tengo sempre un posto particolare nel mio cuore di programmatore per questo linguaggio. Era il 2001 circa, la prima versione che scaricai era quella che il libro su cui studiavo i principi della programmazione consigliava. 
Oggi ho scaricato il simulatore DOS DOSBox e rivisto alcuni dei miei vecchi programmi scritti usando il Turbo Pascal 4.0. Questo è un esempio:

e qui mi viene da sorridere facendo il critico dopo 13 anni. A parte la pixel art che è vergognosa, l'area di calcolo è così piccola che viene a decadere tutta l'utilità del programma. E poi non c'era motivo di mettere una pixel art (Alby III G, la mia classe d'Istituto) che prendesse tutto lo schermo. 
Questa versione è rimasta 1.1. Flash Utility lasciò il posto a un esperimento del 2009 usando appunto Turbo Vision. 

Questa versione (chiamata FXMath o FixMath) è un'applicazione più matura ma comunque, piena zeppa di bug, anche perché non l'ho curata più di tanto. Consiste in un calcolatore di espressioni dove il risultato viene dato analizzando (parsing) la stringa inserita carattere per carattere, con la casella di testo "personalizzata" ad hoc per l'editing. E' un progetto più complesso, che presenta le caratteristiche di ereditarietà e polimorfismo tipiche della programmazione ad oggetti. Questo progetto verrà sviluppato più avanti in ambiente linux usando le gtk+. Più avanti dedicherò il post. 
Riporto solamente la dichiarazione degli oggetti usati nel programma:
TYPE
    TipodiOggetto = (Numero,Operatore);

    PCasellaTesto = ^TCasellaTesto;
    TCasellaTesto = OBJECT (TInputLine)
                      PUBLIC
                      Expression: ARRAY[0..1024] OF PMathObject;{L'espressione.}
                      IndiceOggetto:word;                     {Il numero     }
                                                              {ordinale.     }
                      Counter:Word;                           {Numero oggetti}
                      ame:PCasellaTesto;
                      CONSTRUCTOR Init(TR:TRect;TMaxLen:Word);
                      PROCEDURE Draw;Virtual;                 {Override      }
                      PROCEDURE SetExpression(Oggetto:PMathObject);{Memorizza}
                      PROCEDURE Riordina(Indice:Word;Direzione:boolean;lung:byte);
                      PUBLIC                                     {nell'arra}
                      FUNCTION  IsCursorShown:boolean;
                      FUNCTION  FormattaNumero(n:real):string;
                      FUNCTION  CatturaNumero(dir:boolean;Indice:word;VAR P:word):real;
                      PROCEDURE ToArray(Indice:word;num:real);
                      FUNCTION  CancellaOggetto(Indice:Word):byte;
                      PROCEDURE CalcolaEspressione;           {in una MsgBox }
                      PROCEDURE HandleEvent(var Event:TEvent);VIRTUAL;
                    END;
    PEspresBox = ^TEspresBox;
    TEspresBox = OBJECT (TDialog)                     {La finestra di dialogo}
                     PUBLIC
                     Field:PCasellaTesto;
                     CONSTRUCTOR Init;
                     PROCEDURE HandleEvent(var Event: TEvent); VIRTUAL;
                  END;
    TMyApp = OBJECT (TApplication)
                 EspresBox :PEspresBox;
                 PROCEDURE NuovaFinestra;
                 PROCEDURE HandleEvent(var Event:TEvent); VIRTUAL;
                 PROCEDURE InitMenuBar; VIRTUAL;
                 PROCEDURE ApriFinestraEspressione;
                 FUNCTION GetFileMenuItems(mah: PmenuItem) : PMenuItem;
                 FUNCTION GetEditMenuItems(mah: PMenuitem) : PMenuItem;
                 FUNCTION GetInsertMenuItems(mah: PMenuItem) : PMenuItem;
                 FUNCTION GetHelpMenuItems(mah: PMenuItem) : PMenuItem;
              END;

giovedì 25 febbraio 2016

L'applicativo scritto per la mia tesi di laurea

Per la mia tesi di laurea ho scritto un software in java per automatizzare un calcolo che, anche fatto con un foglio di calcolo, richiedeva della manualità e quindi più tempo. 
Il programma sviluppato non è certo chissà cosa di complicato, ma essendo un programmatore, ho voluto inserire qualcosa che riguardasse la programmazione nella mia tesi di laurea. 

Schermata del software
Piccolo incipit.
La mia tesi di laurea "Produzione, gestione e smaltimento dei rifiuti citotossici e citostatici
nell’AOUP “Paolo Giaccone” di Palermo", redatta con uno stage dove ho raccolto dati, riguarda lo smaltimento dei rifiuti citotossici e citostatici. I dati che avevo raccolto riguardavano le quantità di rifiuti relativi a certi farmaci prodotti ogni giorno, e ogni farmaco è caratteristico di un reparto. Il mio programma non fa altro che calcolare le percentuali del numero di farmaci effettivamente utilizzati per ogni reparto, su scala settimanale. Posso decidere se utilizzare i dati di 1, 2 o 3 settimane, come si nota sullo screenshot. Inseriti i file .csv, formati in questo modo:


poi basta premere sul tasto Elabora, che nel log del programma vengono mostrati i dati in output, che verranno comunque esportati in altrettanti file .csv. 

Dando un'occhiata al codice, ho utilizzato la libreria esterna opencsv versione 3.4 per gestire i file csv in maniera veloce. 

public class Farmaci {

    public static double oncologia=0;
    public static double ematologia=0;
    public static double neurologia=0;
    public static double urologia=0;
    public static double reumatologia=0;
    public static double onco=0;
    public static double ema=0;
    public static double neuro=0;
    public static double uro=0;
    public static double reuma=0;
    public static Integer[] ID;
    public static int primo=0, secondo=0, terzo=0;
    /*numero di farmaci*/
    public static int count;
    public static Frame frame = new Frame();
    
    
    public static void main(String[] args) {
        ID = new Integer[67];
        for (int i=0;i<67;i++)
            ID[i] = new Integer(0);
        frame.setVisible(true);
    }
}

Questa è la classe principale dove dichiaro le variabili principali. Nel metodo main inizializzo l'array di Integer di 67 elementi, tanti quanto il numero di farmaci. 
Riporto dalla mia tesi:

Il seguente codice scritto in linguaggio Java è la funzione principale del software che ho scritto. Essa riceve in input un intero da 1 a 3 che denota la casella contenente il percorso del file .csv da elaborare, quindi apre il file, legge iteratamente ogni record ed effettua le opportune operazioni, per poi infine scrivere le percentuali nelle 5 label relative a ogni reparto, e riportare altre informazioni nella casella di testo “sum”.
private voidelaboraFile(intindiceCasellaDiTesto){
try{
CSVReadercsvReader;
/*  Controllo quale casella del file esaminare */
if (indiceCasellaDiTesto==1)
csvReader = new CSVReader(new java.io.FileReader(new File(this.file1textfield.getText())),';');
else if (indiceCasellaDiTesto==2)
csvReader = new CSVReader(new java.io.FileReader(new File(this.file2textfield.getText())),';');
else if (indiceCasellaDiTesto==3)
csvReader = new CSVReader(new java.io.FileReader(new File(this.file3textfield.getText())),';');
else //DevoinizializzareobbligatoriamentecsvReader
csvReader = new CSVReader(new java.io.FileReader(new File(this.file1textfield.getText())),';');
String[] row=null;
/* Leggo dal file .csv */
while ((row = csvReader.readNext())!=null){
if (!row[0].equals("0")){ //controllo se è la riga terminale
Farmaci.ID[Integer.parseInt(row[0])-1]=Farmaci.ID[Integer.parseInt(row[0])-1]+Integer.parseInt(row[2]);      //memorizzo il farmaco con il numero delle volte che è stato utilizzato
/* Da qui in poi verifico a quale reparto “appartiene” ed eseguo le dovute operazioni
if (row[1].equals("oncologia"))
Farmaci.onco=Farmaci.onco+Integer.parseInt(row[2]);
else if (row[1].equals("oncoema")){
Farmaci.onco=Farmaci.onco+0.5*Integer.parseInt(row[2]);
Farmaci.ema=Farmaci.ema+0.5*Integer.parseInt(row[2]);
}
else if (row[1].equals("oncoemaneuo")){
Farmaci.onco=Farmaci.onco+0.3333*Integer.parseInt(row[2]);
Farmaci.ema=Farmaci.ema+0.3333*Integer.parseInt(row[2]);
Farmaci.neuro=Farmaci.neuro+0.3333*Integer.parseInt(row[2]);
}
else if (row[1].equals("ema")){
Farmaci.ema = Farmaci.ema+Integer.parseInt(row[2]);
}
else if (row[1].equals("oncoemaurol ")){
Farmaci.onco=Farmaci.onco+0.3333*Integer.parseInt(row[2]);
Farmaci.ema=Farmaci.ema+0.3333*Integer.parseInt(row[2]);
Farmaci.uro=Farmaci.uro+0.3333*Integer.parseInt(row[2]);
}
else if (row[1].equals("oncoemaurol")){
Farmaci.onco=Farmaci.onco+0.3333*Integer.parseInt(row[2]);
Farmaci.ema=Farmaci.ema+0.3333*Integer.parseInt(row[2]);
Farmaci.uro=Farmaci.uro+0.3333*Integer.parseInt(row[2]);
}
else if (row[1].equals("reumatologia")){
Farmaci.reuma=Farmaci.reuma+Integer.parseInt(row[2]);
}
/* Aumento il contatore del numero di flaconi utilizzati */
Farmaci.count = Farmaci.count+Integer.parseInt(row[2]);
}
else{
/* Operazioni da effettuare quando si arriva a fine giornata.
   ovvero il calcolo delle percentuali */
Farmaci.oncologia = (Farmaci.onco/Farmaci.count)*100;
Farmaci.ematologia = (Farmaci.ema/Farmaci.count)*100;
Farmaci.urologia = (Farmaci.uro/Farmaci.count)*100;
Farmaci.neurologia = (Farmaci.neuro/Farmaci.count)*100;
Farmaci.reumatologia = (Farmaci.reuma/Farmaci.count)*100;
oncologia.setText(String.format("%.2f",Farmaci.oncologia)+"%");
ematologia.setText(String.format("%.2f",Farmaci.ematologia)+"%");
urologia.setText(String.format("%.2f",Farmaci.urologia)+"%");
neurologia.setText(String.format("%.2f",Farmaci.neurologia)+"%");
reumatologia.setText(String.format("%.2f",Farmaci.reumatologia)+"%");
}
}
/* Trovo i tre farmaci più utilizzati */
int primo=0, secondo=0, terzo=0;
for (int i=0;i<67;i++){
if (Farmaci.ID[i]>Farmaci.ID[primo])
primo = i;
}
for (inti=0;i<67;i++){
if ((Farmaci.ID[i]>Farmaci.ID[secondo])&&(Farmaci.ID[i]<Farmaci.ID[primo]))
secondo = i;
}
for (inti=0;i<67;i++){
if ((Farmaci.ID[i]>Farmaci.ID[terzo])&&(Farmaci.ID[i]<Farmaci.ID[secondo]))
terzo = i;
}
/* Scrivo il tutto nella casella di testo per mostrare i dati. */
sum.append("Numero di farmaci utilizzati: "+Farmaci.count+"\n");
sum.append("I tre farmaci più utilizzati (per ID): \n");
sum.append("#"+Integer.toString(primo)+": "+Integer.toString(Farmaci.ID[primo])+" usi\n");
sum.append("#"+Integer.toString(secondo)+": "+Integer.toString(Farmaci.ID[secondo])+" usi\n");
sum.append("#"+Integer.toString(terzo)+": "+Integer.toString(Farmaci.ID[terzo])+" usi\n");
}
/* Controllo degli errori */
catch(java.io.IOException e){
sum.append("File non trovato\n");
}
catch(Exception e){
sum.append("Si è verificato un errore: "+e.getMessage()+”\n”);
}
}