Blog: Latest Entries (15):




Cronjob für Zertifikate

Da es nicht gerade eine tolle Lösung ist, alle paar Tage die Let's Encrypt Zertifikate per Hand zu aktualisieren, sollte man sich einen Cron-Job erstellen.


Per Hand kann man hier mit alle Zertifikate aktualisieren, die bald ablaufen werden:

sudo letsencrypt renew


Den Cronjob kann auch ganz einfach anlegen.


sudo crontab -e


Und dann den Eintrag hinzufügen. Ich hab es mit vim gemacht.

Also mit den Pfeiltasten ans Ende der Datei. i drücken.


30 2 * * 1 /usr/bin/letsencrypt renew >> /var/log/le-renew.log


hinzufügen. ESC drücken und mit :w speichern und dann mit :q schließen.

Das war es auch schon.

Nach der Anleitung auf digitalocean.com

Downloads und File-Saving

Manchmal möchte man einfach eine am Server erzeugte Datei dem Benutzer zum Download anbieten. Dafür könnte man einen einfachen Link erzeugen.

Wenn die Datei aber aber auf einer größeren JSON Datenstruktur basiert, die man erst einmal mit an den Server schicken muss, ist man mit einem Link und GET-Parametern schlecht beraten.

Am liebsten würde man die Daten ganz normal per Angular an den Server senden, dann den Datei-Inhalt erhalten und den am Client für den Benutzer als Download anbieten.

Manchmal hat man auch schon alle Daten schon am Client vorliegen oder kann diese dort einfach erzeugen oder konvertieren.

Hier hilft eine Objct-URL und der Blob-Objekt von JavaScript. Damti erstellt man sich einen Link den man über ein Event auslöst und so den Klick des Benutzers auf den Link simuliert.


var funcSaveFile = function(csvData){
//create link
var url = URL.createObjectURL(new Blob([csvData]));
var a = document.createElement('a');
a.href = url;
a.download = 'file.csv';
a.target = '_blank';

//triger link with an event
var event = document.createEvent("MouseEvents");
event.initMouseEvent("click", true, true, window,
0, 0, 0, 0, 0,
false, false, false, false,
0, null);
a.dispatchEvent(event);
};


Damit lassen sich relativ einfach Daten aus einer AngularJS oder anderen JavaScript Anwendung zum lokalen Download anbieten, was man für Speichern Methoden und so etwas ja doch häufiger braucht.

Shopware REST-API Artikel speichern mit Preisen


\Shopware\Models\Article\Price by id X not found


Wenn man bei Shopware über die REST-API einen Artikel lädt und dann wieder speichern möchte, kann es dazu kommen, dass ein Fehler auftritt, dass der Preis mit der Id X unbekannt sei. Das scheint wohl so gewollt sein. Deswegen sollte man beim Mappen auf Klassen die Id des Prices aus dem mainDetail des Artikeln einfach unterlassen.


//persist failed if a id is present
@JsonIgnore
private int id = 0;


Hier findet meine Post im Shopware-Forum zu dem Thema

ORMs und Performance

Und das ist dann der Punkt an dem ich wieder anfange daran zu zweifeln, dass ORMs wirklich für mehr als load-by-id, update, insert oder delete brauchbar sind.

Gefühlt liefern ORMs bei Performance-Fällen immer nur Lösungen, die man mit nativen Queries schon von sich aus hätte und das ORM nur zusätzlichen Overhead ohne Vorteil verursacht.

bbcode-image


https://de.slideshare.net/AndreyYatsenco/doctrine-internals-unitofwork

Unerwartete Histogramm-Probleme

Histogramm: Man nimmt sich einen Wertebereich und teilt diesen in gleich große Bereich. Nun werde alle Werte in die passenden Bereich einsortiert. Am Ende erhält man normalisierte Daten die aus Bereichen und der Anzahl der in dem Bereich liegen Werte bestehen.

Dies hat bei Bar-Charts bei der Darstellung viele Vorteile. Für Slider braucht man so etwas nicht, da man dort nur den Min- und Max-Wert braucht.

Wenn man nun viele dicht bei einander liegende Werte hat, bekommt man ein geringere Datenmenge als es die Einzelwerte wären.

Als Beispiel nehmen wir diese Werte


105
107
115
115
116
121
144


Wenn wir nun Bereiche mit der Weite von 10 definieren erhalten wir


100:2
110:3
120:1
130:0
140:1


Die Berechnung für die Normalisierung ist sehr ein einfach und daher auch schnell.

Wir sparen an der Datenmenge, je mehr Werte pro Bucket einsortiert werden können. Zusätzliche Daten entstehen aber dann wenn man leere Buckets hat. Hätten wir noch zusätzlich die Werte 1 und 1.000.000 hätten wir nicht mehr 5 Buckets sondern 1.000.000 / 10 also 100.000 Buckets für 9 Werte.

Würden wir PHP mit einer Max-Size 64M für ein Script können wir schon schnell Probleme mit dem Speicher bekommen, wenn zur Speicherung der Bucket Objekte oder eine HashMap verwendet wird.

bbcode-image


Wenn man Histogramme verwendet sollte man immer an diese Problematik denken und wenn möglich nur gefüllte Buckets laden, also ein Min-Count von 1 setzen
und die leeren Buckets erst bei Bedarf rekonstruieren.

CMS und Pages mit selben Layout

Gehen wir mal davon aus wir erstellen eine Homepage für eine Restaurant-Kette mit 3 Restaurants. Die Seite soll so gebaut sein, dass Mitarbeiter der Restaurants einfache Datenänderungen vornehmen können, ohne dafür um Hilfe fragen zu müssen. Auch sollen Änderungen im Layout einfach und schnell möglich sein, dass aber nicht durch die Mitarbeiter, da diese wohl kein HTML beherrschen.

Jedes Restaurant hat eine eigene Unterseite, die vom Aufbau aber immer gleich sind.

Das Problem ist, dass wenn es Änderungen im Layout dieser 3 Seiten geben soll, müssen die Änderungen auch in allen 3 Seiten getätigt werden. Ein einfaches Kopieren geht natürlich nicht, da man sonst ja die individuellen Texte und Bilder überschreiben würde. Die speziellen Öffnungszeiten für Feiertage einzutragen ist nicht ganz so kompliziert, wenn man einen WYSIWYG-Editor hat. Aber einfacher wäre es natürlich, wenn diese Angaben atomar eingegeben werden können und so auch nicht die Gefahr besteht, durch Fehler die gesamte Seite durcheinander zu bringen.

Ich habe für mein Framework/CMS diese Lösung entwickelt. In dem Code der Seite sind Platzhalter eingebaut, die heraus geparst werden und mit individuellen Werten gefüllt werden können. Daher kann man einfach eine vorhandene Seite kopieren und muss nur die im Formular ausgewiesenen Platzhalter neu füllen. Das Layout zu Ändern geht auch einfach weil man den Code wirklich mit Copy und Paste in alle Seiten übernehmen kann, ohne Werte zu überschreiben.

bbcode-image


bbcode-image


Man hätte natürlich auch Entitäten und Controller mit Templates bauen können. Aber für solche Zwecke ist das oft vom Aufwand her nicht gerechtfertigt, weil die verwendete Zeit nicht entsprechend bezahlt werden würde und mit dieser Lösung es wohl auch schneller geht.

bbcode-image


Und für die einfachen Benutzer ist es so auch viel übersichtlicher und sie können dann bestimmt mehr alleine Ändern, weil sie nicht der Content der gesamten Seite erschlägt.

bbcode-image

vi für den absoluten Anfänger

Wer sich einmal aus versehen in den vi verirrt hatte, weiß wie verwirrend dieser Text-Editor für die Befehlszeile sein kann. Die meisten nutzen vi meistens nur für einen Zweck.. das Bearbeiten von Config-Dateien auf einem Server über eine SSH-Session oder schnelle Anpassungen an Dateien, wenn man Dinge auf der lokalen Dev-Workstation nicht nachvollziehen kann. Dazu reichen zumeist Datei öffnen, Datei bearbeiten, Datei speichern und vi wieder verlassen.

Öffnen ist ganz einfach:


vi file.conf


Dann öffnet sich der vi und man kann über die Pfeil-Tasten schon mal die Position wechseln.
wenn man nun schreiben möchte drückt man i für insert-mode.
Nun nimmt man seine Änderungen vor.
Mit ESC wechselt man zurück in den Command-mode. Speichern geht über :w für write.

Danach kann man entweder mit i weiter bearbeiten oder gibt :q für quit ein.

An sich eine sehr logische Bedienung aus dem insert-mode und dem command-mode zwischen denen man hin und her schalten kann.


JavaScript Facet-Filter

Heute mal wieder etwas mehr Theorie und Forschung und nicht nur Einrichtung und Administration von Servern.

Manchmal hat man einen Satz und Daten den filtern möchte und dann hätte man noch zusätzlich gerne Ansichten auf das Ergebnis von verschieden Facetten aus. Anders gesagt, man möchte wissen, welches Ergebnis zu welchen Filter gehört.

Ich hab hier einen Service in JavaScript geschrieben der genau so etwas macht und der auch mit AngularJS oder Node.js verwendet werden kann. Für kleine Datensätze erspart man sich damit Systeme wie ElasticSearch, die natürlich sehr viel besser und ausgereifter sind, als dieses kleine Beispiel, aber es gibt einen guten Einblick in das Thema. Auch sieht man daran, dass man alles gut parallelisieren kann.

Einmal kann man beim allgemeinen Filter-Durchlauf die zu filternde Menge aufteilen und jeden Teil einzeln in einem Worker/Thread filtern lassen und dann die Ergebnisse wieder zusammen führen. Danach kann man auch jedes Facet wiederum durch einen eigenen Worker/Thread bearbeiten lassen. Beides habe ich hier aber noch nicht umgesetzt.

Der Service:


/*
{
filter:[
{
field:"",
values:[]
op:"eq"
}
]
facetFields:[
{
field:"",
value:""
}
]
}
*/

function FacetsFilterService(){
this.filterArray=function(field, values, items, op){
var result = [];
for(var i=0;i<items.length;i++){
var item=items;
if(item[field]){
if((!op || op=="ep") && values.indexOf(item[field]) > -1){
result[result.length] = item;
}
else if(op=="gt"){
var found=false;
for(var v=0;v<values.length && !found;v++){
if(values[v] > item[field]){
found = true;
}
}
if(found){
result[result.length] = item;
}
}
else if(op=="gt"){
var found=false;
for(var v=0;v<values.length && !found;v++){
if(values[v] < item[field]){
found = true;
}
}
if(found){
result[result.length] = item;
}
}
else if(op=="bw"){

}
}
}
return result;
}

this.filter=function(request, items){
var fullResult = items;
//TODO this to a worker and split items
for(var i=0; i< request.filter.length; i++){
var filter = request.filter;
if(filter.values && filter.values.length>0){
fullResult = this.filterArray(filter.field, filter.values, fullResult);
}
}

var result = {
request : request,
full : fullResult,
facets: []
};

//TODO worker per facet-field
for(var j=0; j< request.facetFields.length;j++){
var facetField = request.facetFields[j];
result.facets[result.facets.length] = {
field:facetField.field,
value:facetField.value,
hits: this.filterArray(facetField.field, facetField.value, fullResult)
};
}

return result;
}

this.createFilterList = function(items, ignoreList){
var result=[];
for(var i=0; i< items.length; i++){
var item = items;
var keys = Object.keys(item);
for(var k=0;k<keys.length;k++){
var key=keys[k];
if(!ignoreList || !ignoreList.indexOf(key) == -1){
if(!result[key]){
result[key] = {
name:key,
values:[]
}
}

var value = item[key];
if(result[key].indexOf(value) == -1){
result[key].values.push(value);
}
}
}
}

var tmp=[];
for(var index in result){
tmp[tmp.length] = result[index];
}

return result;
}
}


Beispiel Daten:


var values=[
{
id:1,
type:"80486",
postfix:"SX",
mhz:25,
socket:"Socket 3"
},
{
id:2,
type:"80486",
postfix:"DX",
mhz:66,
socket:"Socket 3"
},
{
id:3,
type:"80486",
postfix:"DX",
mhz:100,
socket:"Socket 3"
},
{
id:4,
type:"P5",
postfix:"",
mhz:60,
socket:"Socket 5"
},
{
id:5,
type:"P54",
postfix:"C",
mhz:90,
socket:"Socket 5"
},
{
id:6,
type:"P54",
postfix:"C",
mhz:100,
socket:"Socket 5"
},
{
id:7,
type:"P54",
postfix:"C",
mhz:133,
socket:"Socket 5"
},
{
id:8,
type:"P55",
postfix:"C",
mhz:166,
socket:"Socket 7"
},
{
id:9,
type:"P55",
postfix:"C",
mhz:200,
socket:"Socket 7"
}
];



Und nun wollen wir alle CPUs mit 66MHz, 100MHz, 133MHz und 166MHz. Zusätzlich wollen wir aber noch wissen, wie sich diese Ergebnisse auf den Socket 3,4 und 5 verteilen:


var request={
filter:[
{
field:"mhz",
values:[66,100,133,166]
}
],
facetFields:[
{
field:"socket",
value:"Socket 3"
},
{
field:"socket",
value:"Socket 4"
},
{
field:"socket",
value:"Socket 5"
}
]
};


Das wenden wir nun auf die DAten an:


var service=new FacetsFilterService();
var result = service.filter(request, values);
console.log(result);


Und unser Ergebnis sieht wie erwartet aus:

* 486 mit 66MHz
* 486 und Pentium mit 100MHz
* Pentium mit 133MHz
* Pentium MMX mit 166MHz

bbcode-image


und der Pentium MMX 166MHz fällt bei den Facets wieder raus weil er zum Sockel 7 gehört, der aber nicht durch die Facets abgedeckt sind:

* Sockel 3: 66Mhz und 100Mhz
* Sockel 4: keine Ergebnisse
* Sockel 5: 100MHz und 133MHz

bbcode-image


Man kann die Facets natürlich auch ohne vorgeschalteten Filter nutzen und so damit Daten für die Anzeige aufbereiten.

Auch kann man dieses ohne Probleme auch auf PHP oder Java portieren und gerade bei Java kann man mit Threads sicher eine sehr performante Lösung damit bauen.

JavaScript WebWorkerPool

Ich hab mir mal einen allgemeinen und für alle Zwecke geeigneten WebWorkerPool. Man kann die Anzahl der Worker skalieren, was auch bei den Test sich spürbar auswirkte. Auch eine ähnliche Weise sind nun auch die WebWorker auf MP4toGIF.com implementiert, was auch da alles spürbar beschleunigt hat.

Beim eigentlichen WebWorker muss man nur das Antwort-Format einhalten:


{
result:{...},
index:0
}


Index ist der Index des Workloads und wurde in event.data.index übergeben wären der eigentliche Workload in event.data.workload zu finden
ist.


function WebWorkerPool(){
this.workerScript = "";
this.workerData = []; // {msg:{},callback:function}

this.callback = null; //global callback for workerData
this.finishCallback = null;

this.finishCounter=0;

this.worker = null;
this.workers = [];

this.callbackWrapper=function(controller){
return function(event){
controller.finishCounter--;
if(event.data.index && controller.workerData[event.data.index].callback){
controller.workerData[event.data.index].callback(event.data.result);
}
else if(controller.callback){
controller.callback(event.data.result);
}

if(controller.finishCounter == 0){
controller.finishCallback();
try{
controller.worker.close();
for(var iW=0;iW < controller.workers.length; iW++){
controller.workers[iW].close();
}
}
catch(e){

}
}
};
};

this.startSerial = function(){
this.startParallel(1);
};

this.startParallel = function(max){
if(!max){
max = 4;
}

this.finishCounter = this.workerData.length;
this.worker = new Worker(this.workerScript);
for(var iW=0; iW<max; iW++){
var worker = new Worker(this.workerScript);
worker.addEventListener('message', this.callbackWrapper(this), false);
this.workers.push(worker);
}

var factor = this.workerData.length / max;
for(var i=0;i<this.workerData.length;i++){
var workerIndex = Math.floor(i/factor);
this.workers[workerIndex].postMessage(
{
workload: this.workerData.msg,
index:i
});
}
};
}


man kann theoretisch jedem Workload eine eigene Callback-Function mit geben. Wobei eine allgemeine für alle wohl meistens reichen wird.
Wenn alle Workloads abgearbeitet sind wird die finale Callback-Function aufgerufen, um die Ergebnisse weiter zu verarbeiten.


Lumia 950

Nach ungefähr 1,75 Jahren wechsel ist jetzt von meinem Lumia 640 auf ein Lumia 950. Nachdem das Lumia 950 meiner Frau durch ein Moto Z Play ersetzt wurde, weil es nur noch Probleme machte, wurde es eingeschickt. Der Akku lief heiß, es lies sich nicht mehr Ausschalten ausschalten sondern startete immer gleich neu. Laden war ein Glücksspiel und auch das Anschließen an den PC klappt oft nicht.
Dann kam es mit neuen Mainboard wieder und ich habe mich dann entschieden, mein 640 durch das 950 zu ersetzen.
Der Umstieg war relativ einfach, weil die wichtigen Dinge über das MS-Konto synchronisiert wurden und auch Whatsapp fand ein Backup vom Vortag auf der SD-Karte.

bbcode-image


Aber es gibt auch andere Vorteile:

* Schnellere Kamera
* Extra Auslöse-Taste für die Kamera, wo durch man das Smartphone stabiler halten kann
* Bessere Low-Light Bilder
* 4K Video
* 2 SIM-Slots, wo durch ich Privat- und möglicher weise Freelancing-Nummer haben könnte, falls doch noch mal neue Aufträge kommen sollten
* USB 3.0 wo durch alles doch etwas schneller geht beim Bilder kopieren

Insgesamt ist der Vorteil, dass ich jetzt auch wenn ich keine DSLR dabei habe, ganz gute Fotos machen kann. 4K Video werde ich bei Zeiten mal Ausprobieren und mal gucken wie scharf und gut die Qualität wird, wenn man es dann auf FullHD runter rechnet.


Older posts:

Möchtest Du AdSense-Werbung erlauben und mir damit helfen die laufenden Kosten des Blogs tragen zu können?