Mittwoch, 22. März 2017

Managing Gigabytes of files

Vor einiger Zeit hatte ich ein Apex Projekt für die Verteilung von Dokumenten und Fotos.
Dabei sollten die Dokumente in einer Ordner Hierarchie angezeigt und zum Download bereitgestellt werden. Es sollten dann über 3GB an Dateien veröffentlicht werden. Da bot es sich an Zip-Archive für den Upload und Download zu verwenden.
Die open source library as_zip enthielt die dafür benötigten Funktionen.
https://technology.amis.nl/2010/06/09/parsing-a-microsoft-word-docx-and-unzip-zipfiles-with-plsql/

Bei Auslesen größerer Archive mit tausenden Dateien mit der Funktion as_zip.get_file versagte jedoch die Performance. Die Ursache war schnell gefunden: Die Funktion macht eine sequenziellen Scann durch das Archiv bis der übergebene Datei Pfadname gefunden wird. Vor dem Vergleich muss der binäre Inhalt auch noch nach Unicode konvertiert werden.
Es wird also ein Index für das Archiv benötigt, damit die Funktion as_zip.get_file direkt zugreifen kann. Nachdem ich mit der Prozedur as_zip.get_file_date_list schon eine Datumsliste aus dem Verzeichnis erstellt hatte, war es nicht schwer auch den Datei-Offset für jede enthaltene Datei in einem Array zu kalkulieren. Damit konnte as_zip.get_file nun richtig loslegen und große Archive in proportionaler Zeit zur Dateigröße einlesen. 

Um den Prozess weiter zu beschleunigen, habe ich die library unzip_parallel entwickelt. Diese library liest ein Zip-Archiv aus einer Sql-Tabelle und schreibt alle enthaltenen Dateien in zwei Tabellen.
Ein für die Dateien and die zweite für die Ordner Struktur als rekursive parent - child Relation.
(So kann die Ordner Struktur einfache in einer Tree-Region dargestellt werden.)
Für größere Archive verwendet die library das oracle package dbms_parallel_execute. 
Damit lies sich die Verarbeitungszeit noch einmal halbieren.

Die Funktionalität dieser library habe ich nun als Apex Plug-in veröffentlicht:

Process plug-in for reading a zip file from a table, storing all expanded files 
in one table and the folders for the files in a second table.
The table for the files has a least the two columns for file_name varchar2, file_content blob
and optionally file_date date, file_size number, mime_type varchar2(300), folder_id number.
The table for the folders has at least a folder_id number, parent_id number, folder_name varchar2.
When no folder definition is provided in the Folder Query attribute, full pathnames are stored in the file_name field of the files table.
Zip file larger than 5MB will be processed in parallel to reduce the processing time when parallel execution is enabled.

A demo of the plugin can be found here:

Can I have the Last-Modified Date - for that files?

In einem meiner letzten Projekte für Gebäude Management wurden Rechnungen und andere Dokumente gescannt und in die Datenbank geladen. Für die Geschäftsleute ist das Dokumenten-Datum neben dem Namen sehr wichtig. In der Apex 4 Umgebung gab es dafür keine vollständige Unterstützung. So wurde das Datum dann manuell eingegeben.

Das wollte ich ändern:

1. Beim Upload einer Datei in einer Webanwendung kann das letzte Änderungsdatum mit Javascript ausgelesen und so in ein Input Feld neben dem Namen übertragen werden.

function GetFileNameDate(p_FileItem, pNameItem, pDateItem) {
 $x(pNameItem).value = $x(p_FileItem).value;
 var input = $x(p_FileItem);
 if (input && input.files && input.files[0]) {
  var fdate = input.files[0].lastModifiedDate;
  var day = fdate.getDate();
  var mon = fdate.getMonth() + 1;
  var year = fdate.getFullYear();
  var hour = fdate.getHours();
  var min = fdate.getMinutes();
 
  if (day < 10) { day = '0' + day; }
  if (mon < 10) { mon = '0' + mon; }
  if (year < 10) { year = '200' + year; }
  else if (year < 100) { year = '20' + year; }
  if (hour < 10) { hour = '0' + hour; }
  if (min < 10) { min = '0' + min; }
 
  var datestr = day + "." + mon + "." + year + " " + hour + ":" + min;
  $x(pDateItem).value = datestr;
 }
}

2. Beim Download über einen normalen Link kann nur der Dateiname und die Größe im Header gesetzt werden. Wird aus dem Inhalt zunächst ein eine Zip-Datei erstellt und diese dann heruntergeladen, kann beim entpacken das richtige Änderungsdatum gesetzt werden.
Ich fand mit Netz eine coole open source library as_zip von Anton Scheffer.
https://technology.amis.nl/2010/06/09/parsing-a-microsoft-word-docx-and-unzip-zipfiles-with-plsql/

Diese library habe ich so angepasst, das nun das Dokument-Datum in der Funktion add1file übergeben werden kann.


3. Beim Upload eines Zip-Archivs kann das Datei-Datum jeder enthaltenen Datei aus dem Archiv extrahiert werden. Für das Auslesen mit Datei-Datum habe ich die Prozedur get_file_date_list  hinzugefügt.