Digicted

Archief voor web

Een patch voor het cache path probleem in PHPDelicious

Wednesday, December 24th, 2008

Voor mijn stage bij Bemba was ik bezig met de del.icio.us API in PHP, waardoor ik natuurlijk koos voor de PHPDelicious library. Helaas zat er een foutje in de originele library, waardoor er niet gecached kon worden als het cache path niet expliciet werd meegegeven aan de constructor.

De fix voor dit probleem is gelukkig erg simpel. In cache.inc.php zoek je naar de volgende regel:

public function __construct($sKey, $iCacheTime, $sPrefix='', $sCachePath = CACHE_PATH) {

Dit zou normaal regel 44 moeten zijn. Het probleem zit ‘m in CACHE_PATH, wat eigenlijk PHP_DELICIOUS_CACHE_PATH zou moeten zijn. De nieuwe regel wordt dus:

public function __construct($sKey, $iCacheTime, $sPrefix='', $sCachePath = PHP_DELICIOUS_CACHE_PATH) {

Na deze kleine wijziging is alles ok. Ik heb een patch gestuurd naar de auteur, en naar mijn ervaring wordt de library wel gepatched.

De patch kan toegepast worden door de patch-file in dezelfde directory te plaatsen als PHPDelicious en dan patch -p0 -i fix_default_cache_path.patch te typen op je terminal.

UPDATE: ik bedacht me dat wij intern eigenlijk nog een extra patch gebruiken. Als er geen verbinding gemaakt kan worden met del.icio.us (zoals in de trein kennelijk.. duurde even voordat ik dat door had) is een duidelijke foutmelding wel handig. Hiervoor heb ik deze patch gemaakt.

Als de curl_exec() faalt, krijg je nu een PHP_DELICIOUS_ERR_CONNECTION_FAILED te zien.

UPDATE 2: inmiddels heb ik een email gehad van de auteur, waarin staat dat de CACHE_PATH-patch zal mee worden genomen in de nieuwe versie van PHPDelicious.

Nieuwe Wordpress plugin: Title Defaults to Date

Sunday, December 14th, 2008

Voor een nieuwe experimentje heb ik een lokale Wordpress blog geïnstalleerd. Deze blog ga ik schrijven in een soort dagboek-vorm, wat betekent dat de titels eigenlijk altijd gewoon simpelweg de datum en tijd van vandaag zullen zijn.

Aangezien ik niet de eerste persoon ben die Wordpress gebruikt alsof het een dagboek is, heb ik gezocht naar een plugin waarmee ik automatisch de huidige datum als titel kon gebruiken. Enige verbazing was dan ook wel aanwezig toen ik opmerkte dat deze functionaliteit wel gewenst was door andere gebruikers, maar nog niet geïmplementeerd.

En toen was Title Defaults to Date geboren. Deze plugin zet de titel automatisch naar de huidige datum als er geen titel is ingevuld.

Het datum formaat is gemakkelijk in te stellen in de plugin, via de instellingen onder ’settings’ -> ‘Title Defaults to Date’.

Als deze instelling niet wordt ingevuld wordt het datumformaat van Wordress gebruikt (in te stellen onder ’settings’ -> ‘general’).

Veel doet deze plugin dus niet, maar het doet wel genoeg om aan mijn wensen te voldoen. Hij is vrij te gebruiken onder de Creative Commons 3.0 license en te downloaden op de scripts-pagina.

Feedback is altijd welkom op martijn@digicted.nl of in de comments onder deze post.

Shared items van Google Reader automatisch naar del.icio.us sturen

Monday, January 14th, 2008

De shared items feature van Google Reader is de laatste tijd veel in het nieuws geweest. En hoewel niet alle verhalen positief waren, zijn er nog steeds genoeg mensen die dankbaar gebruik maken van de shared items.

Zelf ben ik er nooit echt mee aan de slag geweest, toen ik er gisteren naar keek zag ik dat ik in totaal twee items geshared heb. Dit was waarschijnlijk meer om te testen, dan dat ik het echt nuttig vond.

Meestal bookmark ik links gewoon naar del.icio.us, waar ze ook voor iedereen beschikbaar zijn.

Ook @Polledemaagt en @frankmeeuwsen delen hun items via del.icio.us, maar Polle gaf zaterdag op Twitter aan op zoek te zijn naar een methode om zijn shared items in Google Reader naar del.icio.us te krijgen.

Mijn eerste idee was het gebruik van een Greasemonkey script, maar dit betekent nog steeds handmatig werk - het maakt het proces hooguit iets gemakkelijker.

Daarom besloot ik om een eigen script te schrijven met de RSS-feed van Google Reader shared items en de API van del.icio.us, het resultaat: gr2dic.

Het principe is simpel: je vult je Google Shared items feed URL in (zie deze screenshot), je del.icio.us-username en je del.icio.us-password. Vervolgens worden al je shared items elke dag om 9 uur naar del.icio.us gepost en automatisch getagged met “gr2dic”.

De source code zal ik waarschijnlijk binnenkort beschikbaar maken in een andere post. Wel wil ik alvast een patch delen die ik geschreven heb voor PhpDelicious: addpost_and_deletepost_fix.patch. Zonder deze patch zullen addPost() en deletePost() altijd false returnen en een notice geven.

De auteur van PhpDelicious heb ik ook op de hoogte gesteld van deze patch, dus wellicht zal het binnenkort ook goed zijn in de officiële release.

Inmiddels een email terug van de developer, hij gaat de patch toepassen en opnieuw uploaden.

Update: Op lifehacking.nl schreef Frank meeuwsen al over mijn “oude oplossing” in het artikel Google Reader en del.icio.us Samenvoegen. De methode hierboven beschreven is dus een handigere methode dan op LH.nl gepost is.

Update 2: Er zijn vandaag wat problemen geweest met het script, maar die zouden inmiddels opgelost moeten zijn.

Nieuwe pagina: scripts

Thursday, January 3rd, 2008

Om dingen wat overzichtelijk te maken heb ik een extra pagina toegevoegd aan deze website: scripts.

Hier komen verschillende scripts te staan die ik door de tijd heen heb geschreven voor deze blog. Alles is onder een open source licensie en mag daardoor vrij gebruikt worden.

Nieuwe scripts zullen aan de lijst toegevoegd worden en ook updates van de huidige scripts zullen daar vermelden worden.

Binnenkort een nieuwe toevoeging!

Wordpress Plugin: Bulk Tag Renamer

Sunday, December 9th, 2007

Naar aanleiding van een gesprek met een vriendin heb ik mijn eerste Wordpress-plugin geschreven.

Het was haar opgevallen dat het niet mogelijk is de naam van een tag te veranderen. Bijvoorbeeld alles wat getagged is met “appel” wijzigen naar “apple”.

Omdat ik al langer een Wordpress-plugin wilde schrijven en deze plugin niet al te moeilijk leek (dat viel nog wat tegen, omdat ik eerst moest leren hoe Wordpress-plugins werken) nam ik de taak op mij. Het resultaat is een plugin waarmee je snel de naam van een tag kunt wijzigen en nog een klein beetje andere functionaliteit.

Aangezien ik er een voorstander van ben om dingen één ding te laten doen, en dat ene ding dan gelijk goed, wordt het geheel mogelijk nog gesplitst naar twee plugins.

De huidige feature-list:

  • Tag hernoemen
  • “Slug” (permalink) wel of niet updaten
  • Categorieën met dezelfde naam wel of niet updaten
  • Bestaande tags aanklikken en zo in het “tagveld” zetten bij post schrijven

Installatie is simpel:

  1. Extract het zipbestand
  2. Upload de map naar wp-content/plugins/
  3. Activeer de plugin via het plugins-menu in de admin

Dat is alles! Vervolgens kun je het een link vinden in het menu manage.

Download de plugin.

Wat voor blogger ben jij?

Thursday, October 18th, 2007

What Kind of Blogger Are You?

Zelf ben ik in ieder geval een Purist Expert Socialite:

Of ik echt een expert ben weet ik niet, maar een purist ben ik zeker. En sociaal? Ik gebruik sites als Reddit, Digg, del.icio.us e.d., dus volgens de test wel.

De grote vraag is nu natuurlijk: wat voor blogger ben jij? Take the test!

Via Gigadesign.

Problemen met RSS-feed

Tuesday, September 25th, 2007

Gisteren ontdekte ik dat er een probleem was met de link van de RSS-feed en sommige mensen nog steeds een oude versie in de pagina kregen. Dit is nu opgelost en dat was ook gelijk te zien aan het aantal subscribers op de feed in Feedburner. Excuses voor mensen die hier last van ondervonden hebben.

Tellen in SQL of PHP: wat is sneller?

Tuesday, August 21st, 2007

Afgelopen zondag plaatste Nick een blogpost over MySQL en de fouten die hij mensen hier mee ziet maken.

In de reacties kwam er toen even een discussie of je COUNT() in je query moest gebruiken of een mysql_num_rows in je PHP-code als je het aantal rijen wilt weten.

Ik heb toen even een heel simpel scriptje gedraaid om gewoon snel te checken of mijn stelling klopte en inderdaad: COUNT() leek sneller te zijn.

Gisteren heb ik toen met een database met 1 miljoen rijen (testdata) een paar keer een script gedraaid en de resultaten opgeslagen in een database. Het testsysteem is een Intel PentiumD 930 met 2GB geheugen. PHP 5 en MySQL 5, draaiend op Windows Vista.

Eerst draaide ik het script op de MacBook Pro (wat voor de nodige load zorgde), maar toen bedacht ik me dat de PHP dan werd uitgevoerd op de laptop, terwijl ik de MySQL-server op de Windows-PC draaide. Totaal geen eerlijke vergelijking dus, dus verplaatst.

Het script haalt 1.000.000 rijen op uit een tabel met twee velden: id en name. ID is de primary key met een auto_increment en name is een tinytext-veld met willekeurige strings van willekeurige lengte. Na het ophalen worden de rijen geteld.

De resultaten (in seconden) zijn uiteindelijk als volgt:

PHP SQL
5866.500 503.085
1813.99 1237.07
917.166 490.60
476.952 442.147
562.155 459.487

Gemiddeld duurt het dus 626.47846069336 seconden om het aantal rijen te tellen in MySQL. In PHP duurt het gemiddeld 1927.3516174316 seconden.

De snelste run in PHP duurde 476.952 seconden, de snelste in SQL duurde slechts 442.147 seconden.

De traagste was voor PHP 5866.5 seconden en voor MySQL 1237.07 seconden.

Uit deze getallen kunnen we concluderen dat een COUNT() in MySQL sneller is dan mysql_num_rows() in PHP.

Echter zijn er nadelen verbonden aan het gebruik van COUNT(). Zo heb je waarschijnlijk meer code nodig wat niet altijd even goed is voor de leesbaarheid. Ook in gevallen dat je mogelijk geen rijen terugkrijgt kun je dit vaak niet gebruiken.

Is je applicatie echt zo performance-kritisch en heb je een grote dataset, kan dit dus iets zijn om naar te kijken. Maar in het algemeen zou ik gewoon mysql_num_rows (of een dergelijk alternatief) aanraden.

Ten slotte: voor de geïnteresseerden is alles wat ik gebruikt hebt gewoon beschikbaar. De source van mijn benchmarkscript (met een kleine wijziging naar aanleiding van een tip van Daan) en de SQL voor de tabel met de statistieken De dataset wil ik op aanvraag sturen naar mensen die de SQL-file willen hebben.

Rubyscript om Google-positie te zien

Saturday, August 18th, 2007

Gisteren was ik in Amsterdam met Sanne Roemen en Daan Kortenbach, voor een erg leuk gesprek.

We hebben het ook even over zoekmachines gehad, waarbij Daan vertelde dat hij op pagina 1 kwam voor een goed trefwoord. Uiteraard heb ik zelf ook even gekeken of ik op één van de eerste pagina’s kom, wat helaas niet zo is.

Maar dit gaf me wel een idee: het komt best veel voor dat ik even wil weten of ik voor bepaalde trefwoorden in de eerste 3 of 5 pagina’s sta. Nu zijn er natuurlijk programma’s om dit te doen, maar voor mij betekent dit toch meestal het trefwoord intypen in Google en dan pagina 1, 2, 3, 4 en 5 aanklikken, zoeken naar mijn domeinnaam en hopen dat ik er bij sta.

Nu had ik natuurlijk op zoek kunnen gaan naar zo’n programma, maar in plaats daarvan automatiseerde dit proces zelf door een stukje Ruby.

Zoals gewoonlijk beginnen we eerst met de volledige code en gaan we het vervolgens analyseren:

#!/usr/bin/env ruby -w
require 'net/http'

class GooglePage
  attr_reader :keyword, :pageNumber, :url, :googleResultPage, :maxPages, :position

  def initialize(keyword, url, googleUrl = "www.google.com", maxPages = 10, perPage = 10 )
    @keyword = keyword
    @googleUrl = googleUrl
    #om te voorkomen dat het script niet werkt als mensen de www. vergeten te vermelden in de URL zetten we deze er zelf in
    @googleUrl = "www." + googleUrl unless @googleUrl =~ /^www\.$/
    @url = url
    @perPage = perPage
    @maxPages = 2
    @pageNumber = 1
  end

  def checkGoogle
    getPage
    if @body.include?( @url )
      @position = getPosition
      return @pageNumber
    else
      @pageNumber += 1
      checkGoogle unless @pageNumber >= @maxPages
    end
  end

  def getPage
    @body = ""
    Net::HTTP.start( @googleUrl, 80 ) {|http|
      start = (@pageNumber - 1) * @perPage
      resultPage = "/search?q=#{@keyword.gsub( "\s", "+" )}&start=#{start}"
      @googleResultPage = "http://" + @googleUrl + resultPage
      req = Net::HTTP::Get.new( resultPage )
      @body += http.request( req ).body
    }
  end

  def getPosition
    position = 0
    @body.scan( /span class=a>(.*?)<\/span>/ ).each{ |w|
      position += 1
      return position if w[ 0 ] =~ /^#{@url}/
    }
  end
end

Goed, laten we eens gaan kijken wat dit allemaal betekent.

We beginnen met het maken van een class, GooglePage. Daarna gebruiken we attr_reader, om als het ware automatisch getters te genereren. Waarom dit handig is zullen we later zien.

Met def initialize maken we de constructor aan, deze methode wordt dus aangeroepen als we een nieuw object maken met deze klasse door GooglePage.new.

De rest van deze methode is weinig over te zeggen denk ik, gewoon wat variablen toewijzen lijkt me aardig duidelijk.

De methode checkGoogle wordt al wat spannender. Eerst vragen we de inhoud van de pagina op via getPage (hier over later meer), en daarna kijken we of de opgegeven URL voorkomt in de resultatenpagina. Zo ja, zoeken we de positie binnen de pagina op met getPosition wat ook gelijk de returnvalue is.

Als het niet zo is, gaan we naar de volgende pagina kijken en beginnen we gewoon overnieuw totdat we de site in de resultaten gevonden hebben of we voorbij pagina 10 zijn. Als je niet in de eerste 10 pagina’s staat, is het vaak toch al niet interessant meer. Dit nummer is echter aan te passen bij het aanmaken van het object.

getPage, deze methode opent een connectie met Google.nl en haalt de code op. Eventuele spaties in de keywords worden vervangen met een + om errors te voorkomen. ;)

Ten slotte getPosition. Hier proberen we te bepalen op welke positie op de pagina je ongeveer staat, het is wat buggy (met name als er meer resultaten van één site zijn), maar het geeft wel een indruk of je bovenin of onderin staat. We doen dit door te zoeken naar een bepaald stukje HTML (<span class a>) en de URL binnen deze span te bekijken. Komt deze niet overeen met de URL die we zoeken tellen we 1 op bij de positie en gaan we door tot we ‘m gevonden hebben.

Dan nog even een volledig voorbeeld met een paar leuke keywords genomen en ze in een hash gezet waar we vervolgens één voor één doorheen lopen. Ik zoek naar de URL digicted.nl, en gebruik Google.nl.

#!/usr/bin/env ruby -w
require 'net/http'

class GooglePage
  attr_reader :keyword, :pageNumber, :url, :googleResultPage, :maxPages, :position

  def initialize(keyword, url, googleUrl = "www.google.com", maxPages = 10, perPage = 10 )
    @keyword = keyword
    @googleUrl = googleUrl
    #om te voorkomen dat het script niet werkt als mensen de www. vergeten te vermelden in de URL zetten we deze er zelf in
    @googleUrl = "www." + googleUrl unless @googleUrl =~ /^www\.$/
    @url = url
    @perPage = perPage
    @maxPages = maxPages
    @pageNumber = 1
  end

  def checkGoogle
    getPage
    if @body.include?( @url )
      @position = getPosition
    else
      @pageNumber += 1
      checkGoogle unless @pageNumber >= @maxPages
    end
  end

  def getPage
    @body = ""
    Net::HTTP.start( @googleUrl, 80 ) {|http|
      start = (@pageNumber - 1) * @perPage
      resultPage = "/search?q=#{@keyword.gsub( "\s", "+" )}&start=#{start}"
      @googleResultPage = "http://" + @googleUrl + resultPage
      req = Net::HTTP::Get.new( resultPage )
      @body += http.request( req ).body
    }
  end

  def getPosition
    position = 0
    @body.scan( /span class=a>(.*?)<\/span>/ ).each{ |w|
      position += 1
      return position if w[ 0 ] =~ /^#{@url}/
    }
  end
end

["macbook pro review", "ruby", "macbook", "samsung 205bw", "er is er een jarig", "martijn engler", "weblog" ].each do |keyword|
  gp = GooglePage.new( keyword, "digicted.nl", "google.nl", 10 )
  if gp.checkGoogle
    puts "#{gp.url} gevonden op pagina #{gp.pageNumber} (positie #{gp.position}) voor keyword '#{gp.keyword}': #{gp.googleResultPage}"
  else
    puts "#{gp.url} staat niet in de eerste #{gp.maxPages} pagina's van Google voor het keyword '#{gp.keyword}'"
  end
end

De output is op het moment van schrijven als volgt:

digicted.nl gevonden op pagina 1 (positie 6) voor keyword 'macbook pro review': http://www.google.nl/search?q=macbook+pro+review&start=0
digicted.nl staat niet in de eerste 10 pagina's van Google voor het keyword 'ruby'
digicted.nl gevonden op pagina 5 (positie 6) voor keyword 'macbook': http://www.google.nl/search?q=macbook&start=40
digicted.nl gevonden op pagina 1 (positie 11) voor keyword 'samsung 205bw': http://www.google.nl/search?q=samsung+205bw&start=0
digicted.nl gevonden op pagina 4 (positie 11) voor keyword 'er is er een jarig': http://www.google.nl/search?q=er+is+er+een+jarig&start=30
digicted.nl gevonden op pagina 1 (positie 1) voor keyword 'martijn engler': http://www.google.nl/search?q=martijn+engler&start=0
digicted.nl staat niet in de eerste 10 pagina's van Google voor het keyword 'weblog'

Hyves opgeruimd

Monday, August 13th, 2007

Geïnspireerd door Social Networking, een update (volgens mij ben je echt een onuitputtelijke bron van inspiratie Sanne ;)) heb ik mijn Hyves eens goed opgeruimd. Andere sociale netwerken als Facebook, LinkedIn, Xing en Ecademy ben ik niet te vinden. Nou ja, Facebook wel, maar dat was eigenlijk alleen maar om één en ander over de API te kunnen bekijken.

De mensen die nu dus nog in mijn vriendenlijst op Hyves staan zijn mensen die ik ook echt daadwerkelijk volg (door face to face meetings, weblogs, IM-gesprekken etc.) en waarvan ik dus weet dat ze “OK” zijn.

Als je dus niet meer in mijn vriendenlijst staat, terwijl dat eerst wel zo was, kun je jezelf eens afvragen wanneer we elkaar voor het laatst gesproken hebben. Hebben we nog wel contact? Geef je me anders andere mogelijkheden om te weten dat je nog “OK” bent?

Dingen hoeven niet van één kant te komen, dus ik verwacht niet dat iedereen het contact met mij onderhoudt zonder dat ik er iets voor doe. Maar er waren gewoon veel mensen van wie ik niks meer hoor, gewoon omdat we bijvoorbeeld uit elkaar gegroeid zijn. Van deze mensen weet ik vaak ook niet waar ze tegenwoordig mee bezig zijn en of ze (nog) “OK” zijn.

Vanaf nu blijft mijn vriendenlijstje dan ook “clean” en komen er geen mensen in die ik niet kan “controleren” (dat klinkt enger dan het is, ik ga niemand stalken).

P.S. Kennelijk ben ik gelukkig wel “OK”, want ik zie mezelf nog in het lijstje van Sanne Roemen… ;)