Category: Technology
Git push -varmuuskopiointi
Versiohallinnan varmuuskopiointi on perinteisesti hankalaa. Esimerkiksi Subversion käyttää keskitettyjä repositoryja, jotka muistuttavat ominaisuuksiltaan tietokantoja. Tietokantojakin on vaikea varmuuskopioida niiden ollessa jatkuvassa käytössä. Tätä varten Subversionissa on erillinen komento "svnadmin hotcopy", jolla repositorysta voi tehdä kopion varmuuskopiointia varten.
Git on puolestaan luonteeltaan hajautettu, joten varmuuskopiointi on helppo toteuttaa normaalilla push-toiminnallisuudella. Itse rakensin hiljattain järjestelmän, joka muodostuu seuraavista osista:
- Keskitetty varmuuskopiointipalvelin, jossa on yhteinen käyttäjätunnus ja hakemisto varmuuskopioiduille repositoryille.
- Joukko muita palvelimia, joilla sijaitsee paikallisia Git-repositoryja erilaisten projektien tarpeiden mukaan.
Kuhunkin varmuuskopioitavaan Git-repositoryyn lisätään vakioitu backup-remote:
git remote add backup ssh://xxx@yyy.zzz/backup/project.git
Sen jälkeen kullakin projektipalvelimella ajetaan säännöllisesti cronjobia, joka puskee repositoryjen uusimmat muutokset backup-remoteen:
git push backup --mirror
Näin on toteutettu keskitetty varmuuskopiointi ilman sen kummempia rsync- tai hotcopy-virityksiä. Uusia repositoryja on helppo lisätä varmuuskopioitavaksi samaa keskitettyä käyttäjätunnusta käyttäen. Varmuuskopiointipalvelin hoitaa ne sitten talteen nauhalle tai jonnekin muualle.
Twitterin OAuth käytössä
Otin vihdoinkin oman blogini Twitter-integraatiossa käyttöön OAuth-autentikoinnin. OAuth on aika monimutkainen, mutta onneksi Pythonille löytyy valmis python-oauth2-kirjasto. Vielä helpommin Twitter-integraatio onnistuu python-twitter-kirjastolla. Molemmat voi asentaa kätevästi easy_installilla.
Nyt status-päivityksen lähettäminen on tällainen operaatio Pythonilla:
import twitter twitter.Api(username='xxx', password='yyy').PostUpdate('Hello World')
Twitter on deprekoinut aiemman HTTP Basic -autentikointiin perustuvan API-rajapinnan, joka oli yksinkertainen mutta turvaton. Basic-autentikointia käyttäessä palvelussa on aina välillä katkoja, jotka aiheuttavat ikävästi virheitä, jos integraation rakentaa sillä. Jossain vaiheessa Basic-autentikointi poistunee käytöstä kokonaan.
JavaScript on luokaton kieli
Lueskelin juuri läpi mielenkiintoisen ja sopivan lyhyen kirjan nimeltä JavaScript: The Good Parts (Amazonin Kindle-versio). Se suorittaa ytimekkään ruumiinavauksen JavaScript-kielelle, nostaa esiin sen hyvät puolet sekä varoittaa käyttämästä huonoja ominaisuuksia.
Ajattelin käydä tässä läpi muutaman asian, jotka olivat minulle uusia tai ainakin harmaata aluetta omassa JavaScript-tuntemuksessa.
Prototyypit vs. luokat
JavaScriptissä ei ole luokkia, eikä sitä kannata ajatella luokkiin perustuvana ohjelmointikielenä. Monen ohjelmoijan ensireaktio tuntuu olevan se, että otetaan käyttöön tai kehitetään oma framework, joka simuloi luokkien toimintaa.
Erityisen hämäävää on JavaScriptin new-syntaksi:
var dog = new Dog(...);
New tarkoittaa JavaScriptissä ihan eri asiaa kuin esimerkiksi Javassa tai C++:ssa. Esimerkin Dog ei ole "luokan" nimi, vaikka niin helposti ajattelisi, koska JavaScriptissä ei ole luokkia. Dog on itse asiassa sellaisen funktion nimi, jonka tehtävänä on alustaa uusi objekti. Uuden objektin perintöhierarkia taas muodostuu Dog.prototype -jäsenen kautta. Kaikki Dog-objektit voisi olla määritelty perimään animal-objekti näin:
var Animal = function() { this.noise = 'Unknown'; }; var animal = new Animal(); var Dog = function() { this.noise = 'Bark!'; }; Dog.prototype = animal;
Tällöin "var dog = new Dog()" muodostaa uuden objektin, joka perii kaikki animal-objektin jäsenet, ja jonka omat jäsenet alustetaan Dog-funktiossa.
Perintöhierarkia ei siis lainkaan perustu luokkiin (joita ei ole olemassakaan), vaan prototyypin täytyy aina olla jokin toinen objekti. Jos animal-objektiin nyt lisätään uusia ominaisuuksia, ne näkyvät heti kaikissa dog-objekteissa.
Tämä lisäys tehdään nimenomaan animal-objektiin eikä Animal-funktioon. Tämän eron ymmärtäminen on vaikeaa, jos asiaa yrittää hahmottaa luokkahierarkiana.
Funktiot ja closuret muuttujien piilottajina
Luokattomuuden myötä JavaScriptissä ei myöskään ole perinteistä public/protected/private-erottelua objektien jäsenmuuttujille. Kaikki jäsenet ovat "public"-tyyppisiä, eli niitä voi tarkastella objektien ulkopuolelta vapaasti.
Tämä on omasta mielestäni ihan hyvä asia, koska koodia voi silloin muokata ja virittää aika vapaasti. Joskus suojaamiselle on kuitenkin tarvetta, kun halutaan pakottaa esimerkiksi tietty API-rajapinta tai välttää joitakin tietoturvaongelmia.
Onneksi JavaScriptin funktiot ovat closureihin yhdistettynä erittäin tehokkaita apuvälineitä muuttujien piilottamisessa. Funktion sisällä määritelty muuttuja on täysin suojassa ulkopuoliselta maailmalta.
Aiempaa esimerkkiä jatkaakseni Dog-objektilla voi olla oma "my"-muuttujansa, joka ei näy objektin ulkopuolelle, mutta jota kaikki sen metodit voivat käyttää:
var Dog = function() { var my = {}; return function() { this.setName = function(value) { my.name = value; }; this.getName = function() { return my.name; }; }; }();
Tässä on siis jippona käyttää yhtä ylimääräistä function()-määritelyä, jonka sisällä kaikki muut funktiot määritellään. Viimeisellä rivillä tuota ylimääräistä funktiota kutsutaan ()-operaattorilla, jolloin Dog saa arvokseen sen palauttaman varsinaisen funktion.
Funktioiden käyttäminen tällä tavoin monitasoisesti lienee tuttua Lisp-sukuisten funktionaalisten kielten ohjelmoijille, mutta C++/Java-taustan omaaville tämä voi vaatia hieman pohtimista, ennen kuin idea avautuu kunnolla.
Puolipisteet rivien lopussa
Puolipisteet ovat JavaScriptissä periaatteessa vapaaehtoisia rivien lopuissa. Kieltä ei ole kuitenkaan suunniteltu niin, että sääntö niiden poisjättämisestä olisi kovin helppo hahmottaa yksiselitteisesti.
Esimerkiksi return-lause voi yllättäen toimia eri tavalla kuin kuvittelisi:
return { name: '...', noise: '...' }
Tuossa esimerkissä funktio palauttaa arvon undefined, koska returnin perään ilmestyy automaattinen puolipiste rivinvaihdon yhteydessä. Lause pitää siis kirjoittaa tässä muodossa:
return { name: '...', noise: '...' }
Itse olen päättänyt olla luottamatta automaattisiin puolipisteisiin ja laitan mieluummin puolipisteen jokaisen rivin loppuun. Silloin on ainakin selkästi nähtävissä, mitä lauseen on tarkoitus tehdä, ja virhetilanteessa se on helppo korjata.
If your Chromium OS image doesn't boot
So you built a Chromium OS image and copied it to a USB stick, but it only boots to a blank screen? Try this advice to override the root disk:
- While booting, hit Esc to bring up the Linux boot menu.
- Choose an overridden root disk by entering:
boot: chromeos-usb.A root=/dev/sdc3
Depending on your hardware and disk configuration, you can try sdb3, sdc3, sdd3, etc. to find the right disk. For instance, Asus EeePC 901 has two built-in SSD drives (sda and sdb) so the correct USB device is sdc3.
[Update]
You can also specify the correct root disk manually when building the USB image (inside chroot):
./build_image --usb_disk /dev/sdc3
OpenGL:ää aloittelemassa
Olen tänä kesänä lueskellut OpenGL SuperBibleä ja opiskellut ihan ensimmäisiä perusteita siitä, miten 3D-sovelluksia rakennetaan. Ajattelin tiivistää tähän lyhyesti toimintaperiaatteita alkuun pääsemiseen.
Mac OS X:ssä ohjelmoidessa on helpointa aloittaa käyttämällä NSOpenGLView-näkymää. Kyseinen näkymä lisätään normaaliin tapaan Interface Builderissa omaan sovellukseen. Oletusarvoja on syytä heti muuttaa kahdesta propertystä:
- Depth: 24-bit (tarvitaan syvyystarkistukseen, joka piilottaa objektien näkymättömissä olevat pinnat)
- Buffer: [x] Double Buffer (normaali kaksoispuskurointi sulavaa animointia varten)
Tämän jälkeen on vielä tarpeen tehdä oma luokka, joka periytyy NSOpenGLView-luokasta. Sen nimi voi olla vaikka MyOpenGLView. Periytyminen tehdään normaaliin tapaan luomalla Xcodeen uusi luokka .h- ja .m-tiedostoineen ja määrittelemällä Interface Builderissa lisätyn NSOpenGLView-näkymän luokaksi MyOpenGLView.
Oman luokan .h-tiedoston pitäisi näyttää suunnilleen tältä. Alkuun on lisätty muutama #import-direktiivi, joilla otetaan käyttöön glu- ja glut-apukirjastot.
#import <Cocoa/Cocoa.h> #import <OpenGL/OpenGL.h> #import <OpenGL/glu.h> #import <GLUT/GLUT.h> @interface MyOpenGLView : NSOpenGLView { } @end
Maailman alustaminen
Sitten päästään itse asiaan eli lisäämään 3D-toiminnallisuutta varsinaiseen .m-tiedostoon. Ensimmäiseksi kannattaa lisätä prepareOpenGL-metodi, jota kutsutaan automaattisesti sovelluksen käynnistyessä valmistelemaan ympäristö.
Alustuksessa on kolme tärkeää asiaa:
- Viewport määrittelee näkyvän ikkunan koon. Sen on syytä olla sama kuin ruudulla näkyvän ikkunan.
- Projektiomatriisi (GL_PROJECTION) määrittelee, millaisella projektiolla 3D-maailma näytetään kaksiuloitteisella monitorilla. Yleisin on perspektiiviprojektio, joka on ihmiselle luonnollisin.
- Model-matriisi (GL_MODEL) määrittelee miten 3D-objektit sijoittuvat maailmaan. Se alustetaan yleensä kameran sijainnilla, jonka jälkeen objektit sijoitellaan omille paikoilleen.
Kameraa määritellessä on mahdollisuus valita myös se, miten päin koordinaatiston x-, y-, ja z-akselit asettuvat ruudulle. Oletuksena x ja y kattavat monitorin pinnan ja z taas liikkuu monitorin "sisään". Omissa kokeiluissani tuntui luonnollisemmalta, että x ja y muodostavat vaakatasoisen "maanpinnan" ja z taas määrittelee objektien korkeuden pystysuunnassa.
- (void)prepareOpenGL { CGSize size = self.frame.size; GLfloat aspectRatio = (GLfloat)size.width / (GLfloat)size.height; // Pehmentämättömät värit ja syvyystestaus. glShadeModel(GL_FLAT); glEnable(GL_DEPTH_TEST); // Asetetaan viewport koko ikkunan kokoiseksi. glViewport(0, 0, self.frame.size.width, self.frame.size.height); // Otetaan käyttöön projektiomatriisi. glMatrixMode(GL_PROJECTION); // Alustetaan projektio identiteettimatriisilla. glLoadIdentity(); // Lisätään perspektiivi, jossa silmän näkökenttä on 60 astetta. gluPerspective(60.0, aspectRatio, 1.0, 500.0); // Näkyvä alue on silmästä etäisyydellä 1.0-500.0. // Otetaan käyttöön model-matriisi. glMatrixMode(GL_MODELVIEW); // Alustetaan maailma identiteetimatriisilla. glLoadIdentity(); // Asetetaan kamera koordinaatteihin x=50 y=-200 z=100. gluLookAt(50.0, -200.0, 100.0, 0.0, 0.0, 0.0, // Kamera katsoo origoon (x=0 y=0 z=0) 0.0, 0.0, 1.0); // Ruudulla "ylös" osoittaa z-akselin suuntaan. }
Maailman piirtäminen
Kun maailma on luotu, siihen voidaan alkaa piirtää 3D-objekteja. Tämä tapahtuu drawRect-metodissa, jota Mac OS X kutsuu automaattisesti aina, kun ikkunan sisältö pitää päivittää.
Metodin alussa ollaan siinä OpenGL-matriisitilassa, johon viimeksi jäätiin. Tämän vuoksi alkuperäinen matriisitila on syytä tallentaa heti glPushMatrix()-kutsulla pinoon ja palauttaa se lopuksi sieltä glPopMatrix()-kutsulla. Vaihtoehtoisesti voi tietysti myös alustaa matriisin glLoadIdentity()-kutsulla joka kerta piirrettäessä. Tällöin kamerakin pitää määritellä aina uudelleen gluLookAt()-kutsulla.
Piirtämisen alussa on myös tarpeen tyhjentää ruutu aiemmista objekteista. Tämä tehdään määrittelemällä taustaväri ja kutsumalla sitten glClear()-funktiota. Kutsulla on syytä tyhjentää samalla kertaa sekä piirrospuskuri että syvyystestauspuskuri.
- (void)drawRect:(CGRect)rect { // Alkuperäinen matriisi (kameran sijainti) talteen. glPushMatrix(); // Ruudun ja syvyyspuskurin tyhjennys. glClearColor(0.0, 0.0, 0.0, 0.0); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); // Piiretään objektit ruudulle. // ... // Alkuperäisen matriisin palautus lopuksi. glPopMatrix(); // Päivitetään kaksoispuskuri ruudulle. [self.openGLContext flushBuffer]; }
Kuution piirtäminen ruudulle
Edellinen esimerkki vasta tyhjensi ruudun eikä vielä piirtänyt mitään. Objekti piirretään määrittelämällä ensin mitä piirretään (kolmioita, neliöitä, polygoneja, jne.) ja antamalla sitten joukko koordinaatteja.
Tämä esimerkki piirtää vihreän kuution wireframe-moodissa (GL_LINE). Paksummat viivat (glLineWidth) helpottavat ruutukaappausten ottamista.
glColor3f(0.0f, 1.0f, 0.0f); glPolygonMode(GL_FRONT_AND_BACK, GL_LINE); glLineWidth(5.0); glBegin(GL_QUADS); // Top glVertex3f(40.0, -40.0, 40.0); glVertex3f(-40.0, -40.0, 40.0); glVertex3f(-40.0, 40.0, 40.0); glVertex3f(40.0, 40.0, 40.0); // Bottom glVertex3f(40.0, -40.0, -40.0); glVertex3f(-40.0, -40.0, -40.0); glVertex3f(-40.0, 40.0, -40.0); glVertex3f(40.0, 40.0, -40.0); // Back glVertex3f(40.0, 40.0, 40.0); glVertex3f(-40.0, 40.0, 40.0); glVertex3f(-40.0, 40.0, -40.0); glVertex3f(40.0, 40.0, -40.0); // Front glVertex3f(40.0, -40.0, 40.0); glVertex3f(-40.0, -40.0, 40.0); glVertex3f(-40.0, -40.0, -40.0); glVertex3f(40.0, -40.0, -40.0); // Right glVertex3f(40.0, 40.0, -40.0); glVertex3f(40.0, 40.0, 40.0); glVertex3f(40.0, -40.0, 40.0); glVertex3f(40.0, -40.0, -40.0); // Left glVertex3f(-40.0, 40.0, -40.0); glVertex3f(-40.0, 40.0, 40.0); glVertex3f(-40.0, -40.0, 40.0); glVertex3f(-40.0, -40.0, -40.0); glEnd();
Kuution pyörittely
Viimeiseksi esimerkiksi otan vielä aiemmin piirretyn kuution pyörittämisen ruudulla z-akselin (pystysuunnan) ympäri. Pyörittely tehdään glRotatef()-funktiolla, joka muuttaa model-matriisia halutun kulman verran. Oletetaan, että käytössä on globaali muuttuja GLfloat zRot, joka määrittelee rotaation asteina (0-360). Lisätään tämä rivi ennen kuution piirtämistä. Huomaa, että rivin täytyy tulla vasta glPushMatrix()-kutsun jälkeen, jotta matriisi ei sekoa.
glRotatef(zRot, 0.0f, 0.0f, 1.0f);
Sitten luodaan vielä prepareOpenGL()-metodin lopussa ajastin, joka päivittää rotaatiota 1/25 sekunnin välein ja piirtää ruudun uudelleen.
[NSTimer scheduledTimerWithTimeInterval:1.0/25.0 target:self selector:@selector(tick:) userInfo:nil repeats:YES];
Itse ajastusmetodi:
- (void)tick:(NSTimer *)timer { if (zRot < 360) zRot += 0.5f; else zRot = 0.0f; [self setNeedsDisplay:YES]; }
Apple Magic Mouse paremmaksi BetterTouchToolilla
Applen nykyinen Magic Mouse on kiva tuote. Se yhdistää perinteisen langattoman hiiren tuntuman kosketuslevyjen multitouch-ominaisuuksiin. Webbisivujen skrollaaminen pelkästään hiiren pintaa koskettelemalla tuntuu miellyttävältä.
Sisäänrakennetuista ominaisuuksista puuttuu kuitenkin hiiren keskinappi. Oikean hiirinapin saa kyllä konfiguroitua käyttöön yhdellä rastilla asetuksista, mutta keskinappi on unohtunut kokonaan. Itse käytän kyseistä painallusta webbilinkkien avaamiseen uuteen tabiin, joten en oikein tule toimeen ilman sitä.
Kaikeksi onneksi asiaan löytyy korjaus. BetterTouchTool on ilmainen apuohjelma, jolla Magic Mouseen voi määritellä vapaasti erilaisia painalluksia ja eleitä. Itse käytin sitä lisätäkseni yksinkertaisen mappauksen "Single Finger Middle Click => Middleclick".
Keskinapin simulointiin liittyy kuitenkin pieni ongelma. Magic Mousen pinta on täysin tasainen ja eri ihmisten sormet asettuvat siinä luonnostaan eri kohtiin. Itse painan keskinappia etusormella, ja painallus menee luonnostaan hiukan vasemmalle hiiren keskikohdasta.
Asian voi korjata määrittelemällä BetterTouchToolin asetuksista itselleen luontevimmat rajat hiiren virtuaalisille painikkeille (ks. kuva). Reaaliaikainen multitouch-näyttö kertoo visuaalisesti, mihin kohtaan sormi (tai sormet) asettuvat hiiren päällä, ja rajat voi vetää sen mukaan.
Ihan täydellinen tämä ratkaisu ei ole, sillä hiiren pinnassa edelleenkään mikään ei kerro sormille, missä kohtaa rajat menevät. Pienellä hienosäädöllä ja totuttelulla käyttö tuntuu kuitenkin luonnistuvan.
PS. Kokeilin samaan tarkoitukseen aluksi erästä toista sovellusta nimeltä MagicPrefs. Se kuitenkin sekoitti hiiren kiihtyvyyden eikä huvittanut alkaa selvittelemään mistä ongelma johtui. BetterTouchTool ei ole toistaiseksi aiheuttanut vastaavaa.
Google lopettaa Waven
Official Google Blog kirjoittaa:
Wave has not seen the user adoption we would have liked. We don’t plan to continue developing Wave as a standalone product, but we will maintain the site at least through the end of the year and extend the technology for use in other Google projects.
Näyttäisi siis siltä, että koska Wavesta ei tullut hittiä, Google sulkee sen ja yrittää hyödyntää muissa palveluissaan ne osat, joille on käyttöä.
Itse olin innokkaana kokeilemassa Wavea heti alussa ja koodasin muutamia laajennuksiakin sille. Aika pian into kuitenkin hiipui, eikä Wave oikein löytänyt paikkaansa kommunikaatiossa.
Tähän vaikutti paljon se, että Google Docs tarjoaa hyvät toiminnot dokumenttien ja kaaviokuvien työstämiseen usean kehittäjän kesken. Docsissa dokumentit pysyvät sitten vähän perinteisemmin organisoituina kansioihin ja integroitu chatti mahdollistaa nykyään reaaliaikaisen jutustelunkin.