Rhythmbox Remember the Rhythm ohne Autoplay
Tuesday, 11. December 2012
Dass Rhythmbox standardmäßig vergisst, was für Musik ausgewählt war, ist nervig. Abhilfe schafft das Plugin "Remember the Rhythm". Das aber erinnert sich nicht nur an die Musik, sondern setzt diese auch automatisch fort, sobald man den Player startet. Auch nicht gerade angenehm.
Um Remember the Rhythm das automatische Starten der Musik abzugewöhnen, editiert man die Datei /usr/lib/rhythmbox/plugins/remember-the-rhythm/remember-the-rhythm.py fügt in Zeile 77 self.shell_player.pause()
ein. Also so:
try: self.shell_player.pause() self.shell_player.set_playing_time(self.playback_time) except: pass
Javascript Canvas skalieren
Monday, 3. December 2012
Ein per Javascript gezeichnetes Canvas soll neben ein Element gesetzt werden und die gleiche Höhe bekommen. Dafür wird das Canvas mit beliebigen Werten (hier in einem Koordinatensystem von 0 bis 100) gezeichnet, aber vorher die y-Skalierung mit context.scale(x,y) angepasst:
var neighbour = document.querySelector('#neighbour'); var canvas = document.createElement("canvas"); var context = canvas.getContext('2d'); canvas.height = clientHeight; canvas.width = 25; var yScale = neighbour.clientHeight / 100; context.scale(1, yScale); context.beginPath(); context.moveTo(25, 0); context.lineTo(0, 50); context.lineTo(25, 100); context.closePath(); context.fillStyle = "rgb(78, 193, 243)"; context.fill();
Geht leider nicht nachträglich.
Javascript Tooltips
Saturday, 17. November 2012
Diese Javascript-Tooltips (Demo) machen auf mich einen guten Eindruck. Formatierbar mit HTML, ist es nicht zu schwer sie so zu erweitern, dass sie bei einem Klick auf das Element offen bleiben - denn darum geht es mir, Tooltips mit klickbaren Links.
Nutzen würde ich sie derzeit so:
dot.onmouseover=function(e) { tooltip.show(tooltipText, e, false); }; dot.onmouseout=function() { tooltip.hide(false); }; dot.onclick=function(e) { tooltip.hide(false); tooltip.show(tooltipText, e, true); document.querySelector("#ttclose").onclick = function() { tooltip.hide(true); } }
Und hier der von mir abgeänderte Code (rein experimentell und noch mit Formatierungsfehlern):
var tooltip=function(){
var id = 'tt';
var top = 3;
var left = 3;
var maxw = 300;
var speed = 10;
var timer = 20;
var endalpha = 95;
var alpha = 0;
var tt,c,h;
var x;
var perma = false;
var ie = document.all ? true : false;
return {
show:function(tooltipText, e, permanent){
if(tt == null){
tt = document.createElement('div');
tt.setAttribute('id',id);
c = document.createElement('div');
c.setAttribute('id',id + 'cont');
tt.appendChild(c);
document.body.appendChild(tt);
tt.style.opacity = 0;
tt.style.filter = 'alpha(opacity=0)';
}
tt.style.display = 'block';
c.innerHTML = tooltipText;
tt.style.width = e ? e + 'px' : 'auto';
if(!e && ie){
tt.style.width = tt.offsetWidth;
}
if(tt.offsetWidth > maxw){tt.style.width = maxw + 'px'}
h = parseInt(tt.offsetHeight) + top;
clearInterval(tt.timer);
tt.timer = setInterval(function(){tooltip.fade(1)},timer);
perma = permanent
if (permanent && tt.querySelector("#"+id + "close") == null) {
x = document.createElement('div');
x.innerHTML = "x";
x.setAttribute('id',id + "close");
tt.insertBefore(x,c);
}
this.pos(e);
},
pos:function(e){
var u = ie ? event.clientY + document.documentElement.scrollTop : e.pageY;
var l = ie ? event.clientX + document.documentElement.scrollLeft : e.pageX;
tt.style.top = (u - h) + 'px';
tt.style.left = (l + left) + 'px';
},
fade:function(d){
var a = alpha;
if((a != endalpha && d == 1) || (a != 0 && d == -1)){
var i = speed;
if(endalpha - a < speed && d == 1){
i = endalpha - a;
} else if(alpha < speed && d == -1){
i = a;
}
alpha = a + (i * d);
tt.style.opacity = alpha * .01;
tt.style.filter = 'alpha(opacity=' + alpha + ')';
}else{
clearInterval(tt.timer);
if(d == -1){tt.style.display = 'none'}
}
},
hide:function(permanent){
if (perma) {
if (permanent) {
clearInterval(tt.timer);
tt.timer = setInterval(function(){tooltip.fade(-1)},timer);
}
} else {
clearInterval(tt.timer);
tt.timer = setInterval(function(){tooltip.fade(-1)},timer);
}
}
};
}();
Stylesheet:
#tt { position: absolute; display: block; } #ttclose { border-top-left-radius: 3px; border-top-right-radius: 3px; background: #666; color: #fff; } #ttclose span { cursor: pointer; display: flex; display: -moz-flex; display: -webkit-flex; justify-content: flex-end; -moz-justify-content: flex-end; -webkit-justify-content: flex-end; margin-right: 0.5em; } #ttcont { display: block; padding: 2px 12px 3px 7px; margin-left: 5px; background: #666; color: #fff; text-align: left; border-bottom-left-radius: 3px; border-bottom-right-radius: 3px; } #ttcont a { color: white; } #ttcont ul { margin: 0; padding-left: 1em; } #ttcont h5 { margin-top: 0.1em; margin-bottom: 0.3em; }
Ergebnis:
CSS: Aus einer Checkbox einen Slider-Button machen
Monday, 12. November 2012
Aus einer einfachen Checkbox mit zwei Bildern einen Slider-Button (bei dem Begriff herrscht Verwirrung: Ist das nun ein Slider-Button? Oder ein Toggle-Switch? Ein Umschalter eben) machen, das ist hier das Ziel. Es soll ohne Javascript funktionieren, und erst recht ohne jQuery, daher fielen die [http://www.larentis.eu/bootstrap_toggle_buttons/ Bootstrap toggle buttons] weg. Und deswegen geht es auch rein um die Optik, den Umschalter wirklich ziehbar zu machen hielt ich für unangemessen aufwändig bis unmöglich.
[http://www.onli-blogging.de/uploads/slider.html Demo]
Thema Optik: Es werden [/uploads/on.jpg zwei] [/uploads/off.jpg Grafiken] benutzt, beide stammen von [http://www.chrisnorstrom.com/2012/11/invention-multiple-choice-windowed-slider-ui/ Chris Norström].
Die HTML-Struktur ist eine normale Checkbox in einem Formular, nur dass hinten ein span angehängt wird.
<form> <label> <input type="checkbox" checked/> <span></span> </label> </form>
Warum mit dem leeren span den sonst minimalen Code verschandeln? Firefox ist schuld. Im Folgenden wird mit :before das Bild des Buttons über die Checkbox gelegt. Das könnte man auch direkt mit dem input-Element machen, in Webkit-Browsern geht das auch, aber Firefox versteht die Spezifikation anders und lässt daher keinen Pseuso-Inhalt mittels :before zu. Daher das span als Behelfsziel. Man könnte auch mit dem Label arbeiten.
Da das span in dem label der checkbox ist, zählen Klicke auf das span als Klicks für die checkbox.
Deswegen geben wir dem span erstmal display: inline-block mit und setzen Breite und Höhe.
input[type="checkbox"] + span { width: 84px; height: 26px; display: inline-block; }
Wie gesagt, nun wird mit :before das Bild hineingelegt:
input[type="checkbox"] + span:before { content:""; display: inline-block; width: 100%; height: 100%; background: url("/uploads/off.jpg") no-repeat 0 0; background-size: 100% 100%; }
Und der gedrückte Schalter, wenn die checkbox aktiviert ist:
input[type="checkbox"]:checked + span:before { content:""; display: inline-block; width: 100%; height: 100%; background: url("/uploads/on.jpg") no-repeat 0 0; background-size: 100% 100%; }
Die Checkbox kann man dann ausblenden:
input[type="checkbox"] { display: none; }
Das schöne an der Lösung: Das skaliert mit. Ändert man Breite und Höhe des span, wird das Bild passend skaliert. Deswegen ist das Button-Bild auch als Hintergrundbild eingebunden statt als Pseudo-Content.
Cache für Sinatra
Sunday, 28. October 2012
Ich habe eine ziemlich funktionsfähige Blogsoftware mit Sinatra gebaut. Ein absolutes Pflichtfeature war ein Cache, also dass einmal generierte Seiten irgendwo gespeichert und nachher ohne Neuerstellung genutzt werden können. Caches bei Blogsoftware sind unumgänglich, werden Blogs doch viel öfter gelesen als beschrieben. In meinem Fall ist nicht memcached oder ähnliches der dafür geeignete Ort, sondern die auch für alles andere genutzte SQLite-Datenbank.
Grundlage
Ein Cache ist im einfachsten Fall ein einfacher Key-Value-Speicher. Soll er automatisch nicht unbegrenzt speichern, braucht er irgendein Mittel zur automatischen Cache-Invalidierung, also wird noch ein Time-To-Live-Feld (ttl) eingebaut, das automatisch befüllt wird. Insgesamt:
@db.execute "CREATE TABLE IF NOT EXISTS cache( key TEXT PRIMARY KEY, value TEXT, ttl INTEGER DEFAULT (strftime('%s','now') + 604800) );"
Die Funktionen zum Befüllen und Holen von Werten:
def cache(key, value) begin @db.execute("INSERT OR IGNORE INTO cache(key, value) VALUES(?, ?)", key, value) @db.execute("UPDATE cache SET value = ?, ttl = (strftime('%s','now') + 604800) WHERE key = ?", value, key) rescue => error puts error end end def getCache(key) begin return @db.execute("SELECT value FROM cache WHERE key = ? AND ttl > strftime('%s','now') LIMIT 1;", key)[0]['value'] rescue => error puts error end end
In der restlichen Sinatraanwendung kann man nun alle GET-Requests durch den Cache laufen lassen: Nur wenn der keinen Inhalt hat wird der eigentliche Code ausgeführt und dessen Ergebnis dann gecached. Dafür ideal geeignet sind die before() und after() Funktionen von Sinatra, die vor und nach jedem Seitenaufruf ausgeführt werden.
Vor jedem Aufruf wird der Cache geprüft:
before do @cacheContent = Database.new.getCache(request.path_info) end after do Database.new.cache("request.path_info", body) end
In den eigentlichen Funktionen muss nun vor der eigentlichen Codeausführung auf den Cache reagiert werden:
get '/' do if @cacheContent != nil return @cacheContent end ... end
Und, wichtig: Damit body während after() schon gesetzt ist, muss er explizit gesetzt werden:
get '/' do if @cacheContent != nil return @cacheContent end ... # do stuff body erb :index # instead of erb :index end
Schon steht ein Cache.
Optimierungen
So wie oben reicht das natürlich noch nicht. Zum einen darf natürlich nicht jeder Request gecached werden, sondern nur GET-Requests:
before do @cacheContent = nil if request.request_method == "GET" @cacheContent = Database.new.getCache(request.path_info) end end after do if @cacheContent == nil && request.request_method == "GET" Database.new.cache(request.path_info, body) end end
Außerdem soll der Cache invalidiert werden, wenn mit einem POST-Request schreibend der Bloginhalt verändert wurde, sei es durch einen Kommentar oder einen neuen Artikel. Je granularer man hier arbeitet, desto besser wird der Cache funktionieren, aber im Zweifel muss eben der ganze Cache invalidiert werden. Entweder man führt dazu einen Cache-Instanzvariable ein, die dann verändert wird, sodass der Cacheinhalt nicht mehr gefunden wird. Oder man löscht einfach den gesamten Cacheinhalt:
def invalidateCache() begin return @db.execute("DELETE FROM cache WHERE key LIKE '/%'") rescue => error puts error end end
Da der Cache in meinem Fall noch andere Elemente beinhaltet, wird hier das Löschen über /% auf Elemente mit führendem /, wie eben alle request.path_info-Keys aussehen, eingeschränkt.
Auf der Sinatra-Ebene wird das bei jedem POST ausgelöst (alternativ sicherer auf Datenbankebene bei jedem relevanten Write):
after do if @cacheContent == nil && request.request_method == "GET" Database.new.cache(request.path_info, body) else if request.request_method == "POST" Database.new.invalidateCache end end end
Ganz reicht das noch nicht. In meinem Blog gibt es zwei Nutzergruppen, die unterschiedliche Seiten präsentiert bekommen: Einfache Besucher und solche mit Schreibrechten, Admins. Derzeit würden noch beide die gleichen Seiten gecached bekommen, je nachdem, welche Gruppe die Seite zuerst aufruft. Um da zu unterscheiden wird am einfachsten der key angepasst:
before do @cacheContent = nil if request.request_method == "GET" @cacheContent = Database.new.getCache("#{request.path_info}#{isAdmin?}") end end after do if @cacheContent == nil && request.request_method == "GET" Database.new.cache("#{request.path_info}#{isAdmin?}", body) else if request.request_method == "POST" Database.new.invalidateCache end end end
Das ist dann auch die derzeit finale Version.
Performance
Bringt das was? Benchmarken soll man zwar nie auf dem PC, auf dem der Server läuft, hier aber geht es nur um den Unterschied, daher halte ich das für legitim.
ab -n 1000 -c 5 http://localhost:4567:
Ohne Cache:
Requests per second: 23.58 [#/sec] (mean) Time per request: 212.042 [ms] (mean) Time per request: 42.408 [ms] (mean, across all concurrent requests) Transfer rate: 131.44 [Kbytes/sec] received
Mit Cache:
Requests per second: 124.77 [#/sec] (mean) Time per request: 40.074 [ms] (mean) Time per request: 8.015 [ms] (mean, across all concurrent requests) Transfer rate: 695.50 [Kbytes/sec] received
Grafisch:
Bei einem simulierten Benutzeransturm hilft der Cache definitiv (und mit 500% weit mehr als ich erhofft hatte).
Ohne Last sinkt die Wartezeit auf den Server, bei gefülltem Cache und laut Chrome, von 54ms auf 27ms. Auch nicht schlecht.
Bash: Convert to UTF-8
Saturday, 19. May 2012
Ein einfaches Problem, das im Grunde schwer ist: Eine beliebige kodierte Datei zu UTF-8 umwandeln. Das Programm iconv kann das Umwandeln, aber die momentane Kodierung nicht automatisch erkennen. Enca klang perfekt, unterstützt aber (zumindest in der Lucid-Version) nur östliche Sprachen. Was funktionierte, war überraschenderweise file -bi
:
local tmp=$(mktemp) local charset="$(file -bi "$inputfile"|awk -F "=" '{print $2}')" if [ "$charset" != "utf-8" ]; then iconv -f "$charset" -t utf8 "$inputfile" -o $tmp mv "$tmp" "$inputfile" fi
Bash: Assoziative Arrays
Friday, 18. May 2012
Bash ist großartig, und assoziative Arrays (Hashes in manchen Sprachen) braucht man immer wieder. Mein Spickzettel:
Erstellen
declare -A array #automatisch lokal
- Füllen
array["a"]="abc"
array["b"]="bcd"
array["b"]+="e"
array["c"]=1
let array["c"]++- Zugriff
echo ${array["a"]} #=> "abc"
echo ${array["b"]} #=> "bcde"
echo ${array["c"]} #=> 2- Iterieren
for key in "${!array[@]}";do
echo $key #=> a b c
doneFüllen
array["a"]="abc"
array["b"]="bcd"
array["b"]+="e"
array["c"]=1
let array["c"]++- Zugriff
echo ${array["a"]} #=> "abc"
echo ${array["b"]} #=> "bcde"
echo ${array["c"]} #=> 2- Iterieren
for key in "${!array[@]}";do
echo $key #=> a b c
doneZugriff
echo ${array["a"]} #=> "abc"
echo ${array["b"]} #=> "bcde"
echo ${array["c"]} #=> 2- Iterieren
for key in "${!array[@]}";do
echo $key #=> a b c
doneIterieren
for key in "${!array[@]}";do
echo $key #=> a b c
done
declare -A array #automatisch lokal
array["a"]="abc" array["b"]="bcd" array["b"]+="e" array["c"]=1 let array["c"]++
echo ${array["a"]} #=> "abc" echo ${array["b"]} #=> "bcde" echo ${array["c"]} #=> 2
for key in "${!array[@]}";do echo $key #=> a b c done
Insbesondere ${!array[@]}, also das Bash-Äquivalent zu getKeys(), musste ich erstmal finden.
JS: Submit einer Form per Enter sinnvoll verhindern
Tuesday, 24. April 2012
Das Formular soll noch per Enter auf den Submit-Button gesendet werden, auch die Textareas sollen noch Umbrüche bekommen, aber ein Enter in einer Input-Box nichts mehr bewirken. Deswegen kann man nicht generell den keypress-Event der ganzen Form fangen und da das Enter rausfiltern, wie es sonst vorgeschlagen wird.
Stattdessen kann man so zielgerichtet nur die Texteingabefelder entschärfen:
$('input').each(function() { if ($(this).prop("type") == "text") { $(this).keypress(function (e) { if (e.which == 13) { return false; } }); } });
Wäre verbesserbar, wenn jemand den passenden Selektor für diese Inputelemente findet (input[type=text] funktioniert nicht). Aber eigentlich wünsch ich mir ein HTML-Attribut von form dafür. Vll gibts da ja was?
visibleIf.js
Sunday, 22. April 2012
Ich arbeite momentan an einem Projekt mit Fokus auf HTML5-Formularelementen. Dabei sollen manche nur angezeigt werden, wenn andere einen bestimmten Wert haben. Genau das kann visibleIf.
Anfangs funktionierte das großartig, doch dann lief ich in einige Probleme mit dem Originalcode. Daher passte ich das Skript etwas an:
- Removed EventHelper.js, use jQuery instead
- Removed visibleIf.css, use javascript instead -> now it works without adding display: block, which killed layouts relying on e.g. display: cell
- fix regex to not choke on input-elements with [] in them
- support input type number
- work properly with select-boxes
- dont disable input-elements with no connection to visibleIf
Was das Smartifizieren bringt
Friday, 2. March 2012
Es wird öfter geschrieben, dass Serendipity sauberen Code hätte und ein gutes Stück Software sei. Letztes bestreite ich keinesfalls. Aber über den sauberen Code kann man streiten: es ist halt ursprünglich ein PHP4-Programm, und das sieht man.
Im Backend sieht es besonders übel aus. Gerne wurde da mit ?> gearbeitet und daraufhin der HTML-Code direkt ausgegeben - und dabei die Einrückung aufgegeben. Dadurch wird der Code richtig schwer lesbar. Ein gutes Beispiel ist dieser Abschnitt der plugins.inc.php:
if ($serendipity['GET']['only_group'] == 'UPGRADE') { serendipity_plugin_api::hook_event('backend_pluginlisting_header_upgrade', $pluggroups); } ?> <table cellspacing="0" cellpadding="0" border="0" width="100%"> <?php $available_groups = array_keys($pluggroups); foreach($pluggroups AS $pluggroup => $groupstack) { if (empty($pluggroup)) { ?> <tr> <td colspan="2" class="serendipity_pluginlist_header"> <form action="serendipity_admin.php" method="get"> <?php echo serendipity_setFormToken(); ?> <input type="hidden" name="serendipity[adminModule]" value="plugins" /> <input type="hidden" name="serendipity[adminAction]" value="addnew" /> <input type="hidden" name="serendipity[type]" value="<?php echo htmlspecialchars($serendipity['GET']['type']); ?>" /> <?php echo FILTERS; ?>: <select name="serendipity[only_group]"> <?php foreach((array)$available_groups AS $available_group) { ?> <option value="<?php echo $available_group; ?>" <?php echo ($serendipity['GET']['only_group'] == $available_group ? 'selected="selected"' : ''); ?>> <?php echo serendipity_groupname($available_group); ?> <?php } ?> <option value="ALL" <?php echo ($serendipity['GET']['only_group'] == 'ALL' ? 'selected="selected"' : ''); ?>> <?php echo ALL_CATEGORIES; ?> <option value="UPGRADE" <?php echo ($serendipity['GET']['only_group'] == 'UPGRADE' ? 'selected="selected"' : ''); ?>> <?php echo WORD_NEW; ?> </select> <input class="serendipityPrettyButton input_button" type="submit" value="<?php echo GO; ?>" /> </form> </td> </tr> <?php if (!empty($serendipity['GET']['only_group'])) { continue; } } elseif (!empty($serendipity['GET']['only_group']) && $pluggroup != $serendipity['GET']['only_group']) { continue; } else { ?><td colspan="2" class="serendipity_pluginlist_section"><strong><?php echo serendipity_groupname($pluggroup); ?></strong></td> </tr> <?php } ?> <tr> <td><strong>Plugin</strong></td> <td width="100" align="center"><strong>Action</strong></td> </tr> <?php foreach ($groupstack as $plug) { Die gleiche Stelle in der smartifizierten Version:
if ($serendipity['GET']['only_group'] == 'UPGRADE') { serendipity_plugin_api::hook_event('backend_pluginlisting_header_upgrade', $pluggroups); } $available_groups = array_keys($pluggroups); $data['available_groups'] = $available_groups; $groupnames = array(); foreach($available_groups as $available_group) { $groupnames[$available_name] = serendipity_groupname($available_group); } $data['groupnames'] = $groupnames; $data['pluggroups'] = $pluggroups; $data['formToken'] = $formToken; $data['only_group'] = $serendipity['GET']['only_group']; $requirement_failures = array(); foreach($pluggroups AS $pluggroup => $groupstack) { foreach ($groupstack as $plug) {Man spart zwar nicht insgesamt an Zeilen:
wc -l include/admin/plugins.inc.php 583 include/admin/plugins.inc.php wc -l /var/www/include/admin/plugins.inc.php /var/www/include/admin/tpl/plugins.inc.tpl 448 /var/www/include/admin/plugins.inc.php 209 /var/www/include/admin/tpl/plugins.inc.tpl 657 insgesamtEs wurden zusammengerechnet sogar mehr. Aber man bekommt weniger und besser lesbaren Code auf der Logikebene, und sowieso: Eine Trennung zwischen Logikebene und Sicht.
Eine sehr kleine Datei fehlt noch, bis jetzt bin ich mit dem Ergebnis unserer Arbeit schon sehr zufrieden.
Gettext - export TEXTDOMAIN
Sunday, 12. February 2012
Mit gettext Skripte zu übersetzen war mir immer etwas unangenehm. Eigentlich finde ich es unschön, die kompilierten .mo-Dateien mit den ansonsten komplett lesbaren Skripten zu vermischen. Andererseits ist $"labelId" schon ein guter Weg, Übersetzungen im Code zu haben. Für izulu wollte ich das mal richtig machen, anstatt wie früher wieder ein eigenes System zu nutzen (je nach Sprache eine andere Datei mit Variablen per source einlesen). Wenn ich direkt an das export denke wird es das nächste mal auch wesentlich angenehmer .
Der Guide erwähnt es zwar, aber verschluckt die Befehle:
Place the resulting localized.sh.mo file in the /usr/local/share/locale/fr/LC_MESSAGES directory, and at the beginning of the script, insert the lines:TEXTDOMAINDIR=/usr/local/share/locale TEXTDOMAIN=localized.sh....
The TEXTDOMAIN and TEXTDOMAINDIR variables need to be set and exported to the environment.Also:
TEXTDOMAIN="izulu" TEXTDOMAINDIR="/usr/share/locale/" export TEXTDOMAIN export TEXTDOMAINDIRIch hatte mit der Syntax gekämpft, um den Inhalt einer Variable als msgid zu nutzen. Letzten Endes bin ich bei dem expliziten gettext-Aufruf gelandet:
notify-send "izulu" "$(gettext "$1")"Durch das vergessene export funktionierte dies anfangs nicht.
Java: Externe Programme starten
Wednesday, 10. August 2011
Ich mag ja Bash. Auch, weil man dort einfach alles kombinieren kann, was auf dem System liegt. Bei echten Sprachen wie Java geht das dann plötzlich nicht mehr so einfach, bei Java ist es sogar besonders kompliziert.
Aufrufen
Der Aufruf selbst ist gar nicht so ganz absurd. Lässt man das Fangen der Exceptions weg sieht es so aus:
Process child = Runtime.getRuntime().exec(command); child.waitFor();Ein eigener Prozess mit dem Systembefehl wird gestartet, auf den dann gewartet wird. Nur reicht das nicht.
Ausgabe fangen
Mit obigem Code läuft der Prozess eine Weile und stirbt dann. Denn es müssen die Ausgaben abgefangen werden. Das ist zwar völlig bescheuert und ich wollte es erst auch nicht glauben, aber es ist wirklich so. Dann sieht es so aus:
Process child = Runtime.getRuntime().exec(command); StreamGobbler errorGobbler = new StreamGobbler(child.getErrorStream(), "ERROR"); StreamGobbler outputGobbler = new StreamGobbler(child.getInputStream(), "OUTPUT"); errorGobbler.start(); outputGobbler.start(); child.waitFor();StreamGobbler ist dabei eine von Java World kopierte Klasse, die nichts anderes macht als in einem Thread die Ausgaben zu fangen:
class StreamGobbler extends Thread { InputStream is; String type; StreamGobbler(InputStream is, String type) { this.is = is; this.type = type; } public void run() { try { InputStreamReader isr = new InputStreamReader(is); BufferedReader br = new BufferedReader(isr); String line = null; while ( (line = br.readLine()) != null) { System.out.println(type + ">" + line); } } catch (IOException ioe) { ioe.printStackTrace(); } } }Java: Simpler Map-Cache mit Lebenszeit
Friday, 5. August 2011
Bei mir waren es SQLite-Datenbankabfragen, die zu lange dauerten. Nach dem Neubau von pc-kombo war die Seite dadurch erstmal langsamer als vorher. Das lag aber nicht wirklich an der neuen Architektur oder Datenbank an sich, sondern dass durch nicht übernommene Optimierungen weniger direkt im Speicher behalten wurde. Also musste ein Cache her.
Ich will hier als Beispiel zeigen, wie man so etwas aufbauen kann. Anmerkungen zum Code sind gern gesehen. So sind mir im cacheControl-Thread beim Schreiben dieses Eintrags direkt ein paar Logikfehler aufgefallen, die ich jetzt korrigiert habe. War wohl ungenügend getestet...
Ein Singleton, eine Hashmap und einen Thread zum Aufräumen abgelaufener Cacheeinträge, mehr braucht es eigentlich nicht. Beliebige Objekte werden hereingegeben, Strings als ids, zurückgegebene Objekte muss die darüberliegende Programmmlogik casten.
Beispielnutzung
try { return (Vector<Gpu>) Cache.getInstance().get("Warehouse:Gpus"); } catch (CacheIdNotFoundException cinfe) { Vector<Gpu> gpus = Database.getInstance().getGpus(); Cache.getInstance().put("Warehouse:Gpus", gpus.clone(), 3600*24L); return gpus; }Implementierung
Cache.java:
import java.util.HashMap; import java.util.Map; import java.util.TreeMap; public class Cache { private static Cache instance = new Cache(); private Cache() { Thread cacheControl = new Thread(new Runnable() { public void run() { while(true) { try { Thread.sleep(3600); } catch (InterruptedException ie) { } long now = System.currentTimeMillis() / 1000L; Long time = null; String key = ""; while ((time = ttl.floorKey(now)) != null) { key = ttl.get(time); cache.remove(key); ttl.remove(time); } } } }); cacheControl.start(); } public static Cache getInstance() { return instance; } /** * The memory-cache */ private Map<String, Object> cache = new HashMap<String, Object>(256); /** * moment-to-die for the values corresponding to the given keys */ private TreeMap<Long, String> ttl = new TreeMap<Long, String>(); /** * Put an objekt in the cache * @param key * @param value * @param seconds Time to live */ public void put(String key, Object value, Long seconds) { long now = System.currentTimeMillis() / 1000L; cache.put(key, value); ttl.put(now + seconds, key); } /** * Get a value from the cache * @param key * @return * @throws CacheIdNotFoundException if key not found */ public Object get(String key) throws CacheIdNotFoundException { Object value = cache.get(key); if (value == null) { throw new CacheIdNotFoundException(); } else { return value; } } }Die CacheIdNotFoundException.java ist natürlich witzlos:
class CacheIdNotFoundException extends Exception {}Statistik mit Bash
Wednesday, 6. July 2011
Mittelwert, Median und Varianz, diese Kenngrößen von Messwerten brauchte ich. Da ich sowieso im Terminal unterwegs war schrieb ich mir schnell diese (teilweise ineinandergreifenden) Skripte zusammen - und schrieb sie selbst, weil die Messwerte aus negativen und Kommazahlen bestanden (und ich auf die schnelle keine damit zurechtkommende Implementierung fand).
Mittelwert
mittelwert.sh
#!/bin/bash #Calculate the mean of the arguments #Formula: sum_1_to_n(x_i)/n if [[ $# -eq 0 ]];then echo "No arguments found. Exiting." >&2 exit 1 fi sum=0 n="$#" while [[ "$#" -gt 0 ]];do value="$1" sum=$(echo "$sum + $value" | bc -l) shift done echo "$sum / $n" | bc -lMedian
median.sh
#!/bin/bash #Calculate the median of the arguments #Formula: Var(X)=x_(n/2) if n even # Var(X)=x_((n+1)/2) else if [[ $# -eq 0 ]];then echo "No arguments found. Exiting." >&2 exit 1 fi sorted_args=($(sort.pl $*)) n="$#" if [[ $((n%2)) -eq 0 ]];then mid=$((n / 2)) else mid=$(( (n+1) / 2)) fi mid=$((mid-1)) echo ${sorted_args[$mid]}sort.pl
#!/usr/bin/perl #Sort given arguments use strict; use warnings; my @sorted_numbers = sort {$a <=> $b} @ARGV; print "@sorted_numbers\ ";Für den Median habe ich Perl zuhilfe genommen, statt einen Sortieralgorithmus selbst zu implementieren. sort -n kam mit den negativen Kommazahlen nicht zurecht.
Varianz
varianz.sh
#!/bin/bash #Calculate the sample variance (empiristische Varianz) of the arguments #Formula: (1/n-1)*(sum_1_to_n(x_i²)-n*mittelwert(X)²) if [[ $# -eq 0 ]];then echo "No arguments found. Exiting." >&2 exit 1 fi n="$#" mittelwert=$(mittelwert.sh $*) factor=$(echo "1 / ($n -1)" | bc -l) sum=0 for i in $*;do sum=$(echo "$i*$i+$sum" | bc -l) done echo "$factor*($sum-($n*($mittelwert*$mittelwert)))" | bc -l« vorherige Seite (Seite 3 von 8, insgesamt 117 Einträge) » nächste SeiteSuche
Kategorien
Blog abonnieren