CakePHP en gigabyte vs gibibyte

Iedereen kent het wel. Je koopt een harde schijf van 3 TB, je stopt hem in je PC en ineens is hij nog maar +/-2.7 TB. Wanneer fabrikanten / besturingssystemen de juiste eenheden zouden gebruiken zou dit al een stuk duidelijker zijn.  Voor meer informatie hierover zie Wikipedia.

CakePHP haalt GB en GiB helaas ook door elkaar in de CakeNumber en de NumberHelper class en ik stoorde me daaraan. De oplossing is simpel maar het werkt perfect.

In app/Lib/ van je CakePHP project maak je een nieuw bestand aan genaamd FixedCakeNumber.php. Hierin plaats je de volgende code:

<?php
App::uses('CakeNumber', 'Utility');

class FixedCakeNumber extends CakeNumber {
    
    public static function toReadableSize($size, $si = false) {
        $units = array(1 => 'K', 'M', 'G', 'T', 'P', 'E', 'Z', 'Y');
        
        // defaut values
        $divider = ($si) ? 1000: 1024;
        $binString = ($si) ? '' : 'i';
        
        // exception for byte(s)
        if ($size < pow($divider, $power)) {
            return __dn('cake', '%d Byte', '%d Bytes', $size, $size);
        }

        // caculate size
        $size = max($size, 0); 
        $power = floor(($size ? log($size) : 0) / log($divider)); 
        $power = min($power, count($units) - 1);
        $size = round($size / pow($divider, $power), 2);

        return __d('cake', '%s ' . $units[$power] . $binString . 'B', $size);
    }
}

Nu moeten we een nieuwe Helper aanmaken die gebruikt maakt van de bovenstaande class. Maak een nieuw bestand aan in app/View/Helper/ genaamt FixedNumberHelper.php. Plaats daar de volgende code in:

<?php
App::uses('FixedCakeNumber', 'Lib');
App::uses('NumberHelper', 'View/Helper');

class FixedNumberHelper extends NumberHelper {

    public function __construct(View $View, $settings = array()) {
        parent::__construct($View, $settings);
        $this->_engine = new FixedCakeNumber($settings);
    }
    
    public function toReadableSize($size, $si = false) {
        return $this->_engine->toReadableSize($size, $si);
    }
}

Vervolgens kun je in je controller de helper aanroepen met de volgende regel:

<?php
public $helpers = array('FixedNumber', 'Html', 'Etc');

In je view blijft niets veranderd en kun je de (Fixed)NumberHelper op de normale manier blijven gebruiken. Eventueel kun je, wanneer je dat zou willen. Gebruik maken van de SI eenheid door als tweede parameter true mee te geven.

<?php 
$size = 1000 * 1000 * 1000 * 1000; // 1 TB
echo $this->Number->toReadableSize($size); // output: 931.32 GiB
echo $this->Number->toReadableSize($size, true); // output: 1.00 TB

Automatisch SASS compilen

SASS heeft veel weg van LESS wat vooral door Twitter gebruikt wordt maar het schijnt iets beter te zijn.  Het is een manier van CSS code schrijven waarbij de CSS syntax gevolgd wordt maar waarmee je ineens toegang krijgt tot variabelen, functies en meer. Het enige nadeel is dat je SASS (.scss) bestanden wel nog moet omzetten naar CSS.  Daar is een handig programmaatje voor maar die moet je dan nog steeds elke keer dat je een aanpassing doet handmatig uitvoeren. Om dat op te lossen heb ik een klein Bash script gemaakt.

Dit script maakt gebruik van inotify en sassc en is getest onder Ubuntu 14.04. Om inotify te installeren voer je het volgende uit:

sudo apt-get install inotify-tools

sassc installeren is iets moeilijker want die moet je zelf compilen. Voor zover ik me herinner heb ik daar deze tutorial voor gevolgd.

Dit is het script wat ik gemaakt hebt. Deze kun je in principe overal neerzetten. Sla dit script op als scss2css.sh en voer een "chmod +x scss2css.sh" uit. Pas de dir= regel aan naar de map waar je sass/css code in staat.

#!/bin/bash
dir='/var/www/http_docs/'
sassc='/usr/local/bin/sassc'

inotifywait -m -r -e modify,close_write,create,moved_to,move --format %w%f $dir | while read scss_filepath; do 
    filename=$(basename "$scss_filepath")
    extension="${filename##*.}"

    if [[ "$extension" == "scss" ]]; then
        if  [[ ! $filename =~ ^_ ]]; then
            css_filepath=${scss_filepath/.scss/.css}
            $("$sassc" "$scss_filepath" "$css_filepath")
        else
            filename=${filename:1}
            filename=${filename/.scss/}
            files=$(grep -clRE --include=*.scss "import.*$filename" "$dir")

            for scss_filepath in $files; do
                css_filepath=${scss_filepath/.scss/.css}
                $("$sassc" "$scss_filepath" "$css_filepath")
            done

        fi
    fi
done

exit 0

Als laatste is het natuurlijk handig als het script tijdens het booten gestart word. Dit kan door een aanpassing in /etc/rc.local. Voeg daar deze regel aan toe:

su www-data --shell=/bin/bash -c '/var/www/scss2css.sh  > /dev/null 2>&1 &'

Backup m.b.v. rsync

Ik wilde al langer een offsite backup oplossing waarvan ik zeker kon zijn dat deze altijd zijn werk deed. Zelfs al zou er een ip adres wijzigen of de verbinding wegvallen tijdens het maken van de backup.

Op een gegeven moment vond ik BitTorrent Sync wat bijna precies deed wat ik wilde. BTSync is een volledig decentrale synchronisatie dienst. Je kan data dus zonder problemen naar verschillende devices toe synchroniseren. Het is alleen nog steeds geen echte backup oplossing omdat bestanden gewoon overschreven kunnen worden door een nieuwe versie.

Om dit toch voor elkaar te krijgen heb ik een simpel bash script gemaakt. Dit script moet twee argumenten meekrijgen. De eerste is de naam van het interval (hour, day, week, month, …) en de ander het maximum aantal kopieën die bewaard moeten blijven van dat interval. Het script word uitgevoerd via crontab en stuurt je een e-mail met de resultaten.

#!/bin/sh

# stuff to add to crontab:
# 1 1     * * 1   root    /etc/rsyncbak/rsyncbak.sh weekly 15
# 1 8     1 * *   root    /etc/rsyncbak/rsyncbak.sh monthly 24
# 1 16    2 1 *   root    /etc/rsyncbak/rsyncbak.sh yearly 100

# directory settings
BTSYNC_DIR='/mnt/backup/btsync/'
BACKUP_DIR='/mnt/backup/backups/'
PREV_LINK='/mnt/backup/backups/previous'
EXCLUDE_FILE='/etc/rsyncbak/exclude.txt'
EMAIL='youremail@domain.com'

# create vars for backup
NOW=$(date +'%Y-%m-%d_%H-%M-%S')
INTERVAL=$1
KEEP_X_COPIES=$(($2+1))
FOLDER="${BACKUP_DIR}${INTERVAL}_${NOW}"

# create backup using rsync
RSYNC_OUT=$(rsync -av --delete --link-dest="$PREV_LINK" "$BTSYNC_DIR" "$FOLDER/" --exclude-from="$EXCLUDE_FILE")
RSYNC_EC=$?

# create a new symlink
if [ -L "$PREV_LINK" ]; then
    rm "$PREV_LINK"
fi
ln -s "$FOLDER" "$PREV_LINK"

# remove obsolete backups
TO_REMOVE=$(ls -rd "${BACKUP_DIR}${INTERVAL}"_* | tail -n +"${KEEP_X_COPIES}")
for dir in $TO_REMOVE; do
    rm -rf "$dir"
done

printf "rsyncbak completed, exit code: %s\n\n%s" "$RSYNC_EC" "$RSYNC_OUT" | mail -s "rsyncbak completed" "$EMAIL"