31. Oktober 2008

Einzelne Cursor invalidieren mit DBMS_SHARED_POOL

English title: Purging Cursors from the shared pool with DBMS_SHARED_POOL

Die Tage bekam ich die Frage, ob und wenn ja, wie man denn einen Cursor im Shared Pool invalidieren könne. Es wurde also eine SQL-Abfrage in der Datenbank ausgeführt - diese wurde (natürlich) geparst, ein Ausführungsplan wurde erstellt und diese Daten sind nun im Shared Pool gecacht. Wenn die gleiche SQL-Abfrage nun nochmals ankommt, wird der gecache Ausführungsplan wiederverwendet. Im Zusammenhang mit Bind Variablen hatten wir das Thema auch schonmal ...
The last days I was asked the question how to invalidate one particular cursor in the shared pool. So there was a SQL query executed, it was (of course) parsed, an execution plan was generated and all this is now cached in the shared pool. When this query comes again the cached information is being used. There was a posting about this regarding the discussion of bind variables.
Nun tritt ab und zu die Situation auf (Tuning-Maßnahmen), dass man diesen Cursor eben nicht nutzen möchte. Obwohl die SQL-Abfrage "schon da" ist, möchte man, dass sie neu geparst, also ein neuer Ausführungsplan erstellt wird. Der erste (und einfachste Ansatz) wäre, einen SQL-Kommentar in die Abfrage einzubauen - dann ist es ein neuer SQL-Text und die Abfrage passt nicht mehr zur gecachten - sie ist dann tatsächlich "neu".
Sometimes there is the situation where you just don't want the database to use that cache. Now - the easiest way is then to add some comment to the SQL query - the SQL text changes, so you have a new query.
Und wenn man an die Abfrage nicht herankommt? Tja ... dann könnte man ...
  • ... den Shared Pool komplett leeren (alter system flush shared_pool) ...
    ... was aber alle Inhalte löscht, also auch den Buffer Cache.
  • ... die Tabelle ändern (bspw. einen Kommentar hinzufügen) ...
    ... würde aber alle Cursor invalidieren, die mit der Tabelle arbeiten.
But you can't always change the query; if you don't have access to the applications' code you can't change any SQL ... So you might ...
  • ... flush the shared pool completely (alter session flush shared_pool)...
    ... but this elimiates all cached information - including the buffer cache.
  • ... change the table definition (add a table comment) - this invalidates the cursor ...
    ... but it invalidates all other cursors selecting this table also.
Eigentlich soll ja nur der einzelne Cursor aus der SGA elimiert werden. Und das geht in Oracle11g (und lt. Metalink Note 457309.1 auch in 10.2.0.4) mit dem PL/SQL-Paket DBMS_SHARED_POOL. Getestet habe ich bislang nur auf Oracle11g.
The goal is just to eliminate one particular cursor. And in Oracle11g this is possible with DBMS_SHARED_POOL (Metalink note 457309.1 states that it is also possible with 10.2.0.4 but I tested only on 11g).
Wenn DBMS_SHARED_POOL nicht vorhanden ist, muss es mit dem Skript $ORACLE_HOME/rdbms/admin/dbmspool.sql eingespielt werden.
If DBMS_SHARED_POOL is not present in your database you have to run the script $ORACLE_HOME/rdbms/admin/dbmspool.sql.
Zuerst also ein SQL absetzen - mit dem Kommentar finden wir es später leichter wieder ...
First we issue a SQL query ... the comment makes it easier to find again ...
SQL> select /* SQL 1 !!!*/ sal from scott.emp where empno=7839;

       SAL
----------
      5000
Dann den Cursor in der View V$SQLAREA heraussuchen ...
Then look it up in V$SQLAREA...
SQL> select address, hash_value, executions, invalidations, parse_calls, sql_text from v$sqlarea where sql_text like 'select /* SQL 1 !!!*/%';

ADDRESS  HASH_VALUE EXECUTIONS INVALIDATIONS PARSE_CALLS SQL_TEXT
-------- ---------- ---------- ------------- ----------- --------------------------------------------------
4158E358 2329752635          1             0           1 select /* SQL 1 !!!*/ sal from scott.emp where
                                                         empno=7839
Nun DBMS_SHARED_POOL.PURGE aufrufen ... Ein Cursor wird durch seine ADDRESS und HASH_VALUE identifiziert.
Now we call DBMS_SHARED_POOL.PURGE - to purge a cursor we identify it by concatenating its ADDRESS and HASH_VALUE.
begin 
  dbms_shared_pool.purge('4158E358 2329752635', 'C');
end;
/

PL/SQL procedure successfully completed.
Nochmal in der V$SQLAREA nachgucken ...
Check in V$SQLAREA...
SQL>  select address, hash_value, executions, invalidations, parse_calls, sql_text from v$sqlarea where sql_text like 'select /* SQL 1 !!!*/%';

no rows selected
Der Cursor ist also weg ... führen wir das Original-SQL nochmals aus, dann wird es neu geparst.
It's gone. We now want to check further and execute the original query again.
SQL> select /* SQL 1 !!!*/ sal from scott.emp where empno=7839;

       SAL
----------
      5000
Und ein drittes Mal in der V$SQLAREA nachgucken ...
And look into V$SQLAREA - the third time.
SQL> select address, hash_value, executions, invalidations, parse_calls, sql_text from v$sqlarea where sql_text like 'select /* SQL 1 !!!*/%';

ADDRESS  HASH_VALUE EXECUTIONS INVALIDATIONS PARSE_CALLS SQL_TEXT
-------- ---------- ---------- ------------- ----------- --------------------------------------------------
4158E358 2329752635          1             1           1 select /* SQL 1 !!!*/ sal from scott.emp where
                                                         empno=7839
Wir haben nun eine Invalidation. Mit DBMS_SHARED_POOL kann man aber noch mehr machen. Die SIZES-Prozedur zeigt bspw. an, welche Objekte sich im Shared Pool befinden. Der folgende Aufruf listet alle Objekte, die mehr als 500 Kilobyte im Shared Pool belegen.
Now we have one invalidation - the SQL query was parsed like a new query - the cursor object was infact purged from the shared pool. But DBMS_SHARED_POOL can do more: the SIZES procedure shows the objects currently residing in the shared pool. The following call lists all objects greater than 500 kilobytes.
SQL> set serveroutput on size 2000000
SQL> exec dbms_shared_pool.sizes(500);
SIZE(K) KEPT   NAME
------- ------ ----------------------------------------------
2870 YES    XDB.XDNsht+pGJQ9jgQESYGWQVVg==(XDB)
1717 YES    XDB.XDbD/PLZ01TcHgNAgAIIegtw==(XDB)
1274        XDB.XDNsht+plHQ9jgQESYGWQVVg==(XDB)
1210        XDB.XDNsht+o5NQ9jgQESYGWQVVg==(XDB)
835        XDB.XDNsht+o3tQ9jgQESYGWQVVg==(XDB)
650 YES    SYS.oracle/i18n/text/OraMapTable(JAVA CLASS)
531 YES    SYSMAN.MGMT_JOB_ENGINE        (PACKAGE BODY)

PL/SQL-Prozedur erfolgreich abgeschlossen.
Die Ausgabe via DBMS_OUTPUT ist zwar etwas unübersichtlich (eine Table Function wäre mir lieber gewesen) ... aber immerhin ...
The output via DBMS_OUTPUT is a little bit cumbersome (a table function would fit better, IMHO) - but its better than nothing ...

23. Oktober 2008

Aktuelle Session Tracedatei ansehen: Mit SQL und PL/SQL

English title: Viewing Tracefiles with SQL and PL/SQL

Man kommt als Entwickler des öfteren in die Situation, sich eine Tracedatei ansehen zu müssen ... zwei Beispiele:
  • Wenn man einen SQL Trace aktiviert, um ein SQL-Kommando zu analysieren, werden die Informationen in die Tracedatei geschrieben
  • Wenn man eine Java Stored Procedure laufen lässt und diese eine Exception auslöst, landet der Java-Fehler-Stack im Tracefile
Und in diesen Fällen bedeutet das, dass man die Datenbankumgebung verlassen, sich an der Datenbankmaschine anmelden, die Tracedatei suchen und ggfs. mit tkprof aufbereiten muss - erst dann kann man sich die Inhalte ansehen. Das ist zumindest mal aufwändig und manchmal hat man auch gar keine Login-Daten für das Betriebssystem der Datenbankmaschine.
Sometimes there are situations where developers have to look into the database's tracefiles. Here are two examples ...
  • If you activate a SQL trace to analyze a particular SQL command then this information is being written into a tracefile
  • If a java stored procedure (java in the database) throws an exception the java error stack is by default written to a tracefile
... and this means that you have to log into the operating system of the database machine, change to the tracefile directory, lookup the file, process it with tkprof (when necessary) and finally view the results. This costs at least time and in some cases you don't even have credentials for the operating system.
In Oracle11g gibt es eine nette, sehr hilfreiche Kleinigkeit: die View V$DIAG_INFO. Diese gibt den Namen des für die aktuelle Session gültigen Tracefiles wie folgt heraus:
In Oracle11g there's a nice very helpful new view: V$DIAG_INFO shows the name of the current sessions' tracefile as follows:
SQL> select value from v$diag_info where name='Default Trace File'
  2  /

VALUE
--------------------------------------------------------------------------------
/oracle/u01/app/oracle/diag/rdbms/orcl/orcl/trace/orcl_ora_6328.trc
Damit entfällt das Suchen auf dem Server - das Tracefile-Verzeichnis kann durchaus mal viele Dateien enthalten. Man kann die View aber auch ganz anders nutzen ...
Ok - with this you don't have to search for the tracefile - you already know its name ... but this information can furthermore be used to simplify the whole process.
Ich nehme dazu mal wieder das Package zum Ausführen von Betriebssystem-Kommandos zur Hilfe - damit kann man die Datei direkt auslesen und sogar das tkprof-Utility direkt aus der Datenbank starten. Das folgende PL/SQL-Paket TRACE_HELPER macht genau das. Das Skript kann nur in eine 11g-Datenbank eingespielt werden, da es V$DIAG_INFO erst in 11g gibt. Für frühere Versionen muss ich noch ein wenig Code zum Ermitteln der aktuellen Tracedatei zusammenstellen - das werde ich dann in einem späteren Posting veröffentlichen.
I'll -again- take the package for operating system interaction in order to create a PL/SQL package which does all the stuff automatically. This package TRACE_HELPER does only run in an Oracle11g database - previous versions don't have the V$DIAG_INFO view). To run in previous versions I have to write some code which determines the current session's tracefile ... this will be posted here when finished.
create or replace package trace_helper is
  function get_session_trace_file return clob;
  function get_session_tkprof_trace(
    p_recursive_sql  in number default 1,
    p_explain        in varchar2 default null,
    p_sort           in varchar2 default null
  ) return clob;
  procedure set_output_tempfile_prefix(p_prefix in varchar2);
end trace_helper;
/
sho err

create or replace package body trace_helper is
  C_TKPROF_COMMAND constant varchar2(200) := '/oracle/u01/app/oracle/product/11.1.0/bin/tkprof';
  C_OUTFILE_PREFIX constant varchar2(200) := '/tmp/tkprof_out';

  g_outfile_prefix varchar2(200) := C_OUTFILE_PREFIX;

  procedure set_output_tempfile_prefix(p_prefix in varchar2) is
  begin
    g_outfile_prefix := p_prefix;
  end set_output_tempfile_prefix;
  function get_trc_file_name return varchar2 is 
    v_filename varchar2(32767);
  begin
    SELECT value into v_filename FROM v$diag_info WHERE name = 'Default Trace File';
    return v_filename;
  end get_trc_file_name;

  function get_session_trace_file return clob is
  begin
    return file_pkg.get_file(get_trc_file_name).get_content_as_clob('iso-8859-1');
  end get_session_trace_file;

  function get_session_tkprof_trace(
    p_recursive_sql  in number default 1,
    p_explain        in varchar2 default null,
    p_sort           in varchar2 default null
  ) return clob is
    v_tkprof_command varchar2(32767) := C_TKPROF_COMMAND;
    v_tkprof_success number;
    v_tkprof_content clob;

    v_output_file    file_type;
  begin
    v_output_file := file_pkg.get_file(
      g_outfile_prefix || substr(get_trc_file_name, instr(get_trc_file_name, '/', -1) + 1)
    );
    v_tkprof_command := v_tkprof_command || ' ' || get_trc_file_name || ' ' || v_output_file.file_path;

    if p_explain is not null then
      v_tkprof_command := v_tkprof_command || ' explain=' || p_explain;
    end if;
    if p_recursive_sql = 0 then 
      v_tkprof_command := v_tkprof_command || ' sys=no';
    end if;
    if p_sort is not null then
      v_tkprof_command := v_tkprof_command || ' sort=' || p_sort;
    end if;  

    v_tkprof_success := os_command.exec(v_tkprof_command);
    v_tkprof_content := v_output_file.get_content_as_clob('iso-8859-1');
    v_output_file := v_output_file.delete_file();

    return v_tkprof_content;
  end get_session_tkprof_trace; 
end trace_helper;
/
sho err
Das entstandene Package sieht so aus ...
The resulting package looks like this:
FUNCTION GET_SESSION_TKPROF_TRACE RETURNS CLOB
 Argument Name                  Typ                     In/Out Defaultwert?
 ------------------------------ ----------------------- ------ --------
 P_RECURSIVE_SQL                NUMBER                  IN     DEFAULT
 P_EXPLAIN                      VARCHAR2                IN     DEFAULT
 P_SORT                         VARCHAR2                IN     DEFAULT
FUNCTION GET_SESSION_TRACE_FILE RETURNS CLOB
PROCEDURE SET_OUTPUT_TEMPFILE_PREFIX
 Argument Name                  Typ                     In/Out Defaultwert?
 ------------------------------ ----------------------- ------ --------
 P_PREFIX                       VARCHAR2                IN
ACHTUNG: Die Zeile mit dem Pfad zum tkprof Executable müsst Ihr an euere Umgebung anpassen ...
Attention: The line containing the path to the tkprof executable must be adjusted to your environment before using the package.
  C_TKPROF_COMMAND constant varchar2(200) := '/oracle/u01/app/oracle/product/11.1.0/bin/tkprof';
  • Die Funktion GET_SESSION_TRACE_FILE liest das Tracefile der aktuellen Session aus und gibt es als CLOB zurück. Das ist bspw. bei Java in der Datenbank hilfreich, wenn eine Exception ausgelöst wurde und die Details im Tracefile stehen ...
  • Die Function GET_SESSION_TKPROF_TRACE gibt eine mit dem tkprof-Werkzeug aufbereitete Version des Tracefile als CLOB zurück. Während dieses Prozesses wird von eine temporäre Datei (diese nimmt den aufbereiteten Text auf) erzeugt, deren Inhalte werden in einen CLOB kopiert und dieser dann zurückgegeben. Schließlich wird die temporäre Datei gelöscht. Das Verzeichnis, in welches diese temporäre Datei abgelegt wird, könnt Ihr mit der Konstante C_OUTFILE_PREFIX oder mit der Prozedur SET_OUTPUT_TEMPFILE_PREFIX beeinflussen.
  • Die Prozedur SET_OUTPUT_TEMPFILE_PREFIX legt den Pfad und das Namens-Präfix für die eben erwähnte temporäre Datei fest.
Damit das ganze funktioniert, braucht euer Datenbankschema nun noch einige Privilegien ... das folgende Skript müsst Ihr als DBA ausführen und den TRCTEST durch euren DB User ersetzen.
  • The function GET_SESSION_TRACE_FILE reads just the tracefile content and returns it as a CLOB. This is helpful when java stored procedures throw exceptions - you then see the java error stack in the returning CLOB.
  • The function GET_SESSION_TKPROF_TRACE executes tkprof on the session's tracefile and returns the tkprof output as a CLOB. During this process a temporary file with the tkprof output is being created, its contents are then being copied into a CLOB and finally it's being deleted. The directory into which this temporary file is placed, is determined by the PL/SQL constant C_OUTFILE_PREFIX - so you might want to adjust this according to your environment. The directory and the filename prefix can also be adjusted by the procedure SET_OUTPUT_TEMPFILE_PREFIX.
  • The procedure SET_OUTPUT_TEMPFILE_PREFIX is used to set the directory and filename prefix for the temporary file generated by tkprof.
Your database schema needs some privileges in order to use the package. Just run the following script as the SYS user and change the TRCTEST user to the database user you're working with.
 
-- execute privilege for the "tkprof" utility
begin
  dbms_java.grant_permission( 
    'TRCTEST',
    'SYS:java.io.FilePermission',
    '/oracle/u01/app/oracle/product/11.1.0/bin/tkprof', 
    'execute' 
  );
end;
/

-- read privilege for the tracefile directory
declare
  v_diag_dir varchar2(4000);
begin
  select value into v_diag_dir 
  from v$diag_info where name = 'Diag Trace';
 
  dbms_java.grant_permission( 
    'TRCTEST',
    'SYS:java.io.FilePermission',
    v_diag_dir || '/-', 
    'read' 
  );
end;
/

-- read and write privileges for a temporary directory
-- the temporary files for the tkprof output are placed here
begin
  dbms_java.grant_permission( 
    'TRCTEST',
    'SYS:java.io.FilePermission',
    '/tmp/-',
    'read,write' 
  );
end;
/

-- this grants write permission on STDIN
begin
   dbms_java.grant_permission(
     grantee =>           'TRCTEST',
     permission_type =>   'SYS:java.lang.RuntimePermission',
     permission_name =>   'writeFileDescriptor',
     permission_action => null
   );
end;
/

-- this grants read permission on STDOUT
begin
   dbms_java.grant_permission(
     grantee =>           'TRCTEST',
     permission_type =>   'SYS:java.lang.RuntimePermission',
     permission_name =>   'readFileDescriptor',
     permission_action => null
   );
end;
/
Fertig. Testen ...
That's it ... here's a test ...
SQL> alter session set sql_trace=true;

Session altered.

SQL> select ... from ...;

:
:

SQL> select trace_helper.get_session_tkprof_trace from dual;

GET_SESSION_TKPROF_TRACE
--------------------------------------------------------------------------------

TKPROF: Release 11.1.0.7.0 - Production on Wed Oct 22 02:36:46 2008

Copyright (c) 1982, 2007, Oracle.  All rights reserved.

Trace file: /oracle/u01/app/oracle/diag/rdbms/orcl/orcl/trace/orcl_ora_6380.trc
Sort options: default

********************************************************************************

count    = number of times OCI procedure was executed
cpu      = cpu time in seconds executing
elapsed  = elapsed time in seconds executing
disk     = number of physical reads of buffers f ...
:
Ihr bekommt die Infos also direkt im SQL*Plus oder dem Werkzeug, mit dem Ihr gerade arbeitet - ein Wechseln der Umgebung ist nicht mehr nötig ...
You can now see the tracefile information within SQL*Plus (or your database development tool) - changing the environment is no longer necessary.

15. Oktober 2008

PDF und andere Binärformate in HTML umwandeln: Mit der Datenbank

English title: Convert PDF or other binary documents into HTML - with the database!

Der in der Datenbank enthaltene Volltextindex Oracle TEXT (dazu gibt es ein eigenes Blog) kann neben einfachen ASCII Texten auch gängige Dokumentformate indizieren. So werden Formate wie Microsoft Office, das PDF-Format und andere unterstützt. Technisch wird dies durch einen Filter-Mechanismus realisiert; der Filter wandelt die Binärformate in HTML um - dieses wird dann indiziert.
Oracle TEXT, the fulltext indexing engine inside the Oracle Database, can not only index ASCII or HTML documents but also common (binary) document formats like Microsoft Office, PDF or others. This is achieved with a document filter which converts the binary content to HTML - which is then being indexed.
Das Schöne ist nun, dass man den Filter auch separat (also losgelöst vom Volltextindex) nutzen kann. So kann man sich eine PL/SQL-Funktion schreiben, die einen BLOB entgegennimmt und HTML zurückgibt. In einer Webanwendungen kann man so ein Preview des Dokumentes im Browser anzeigen - der Endanwender muss die Desktop-Applikation gar nicht starten.
The cool bit is now that this filter can be called separately and completely independent from an Oracle TEXT index. So we can write a simple PL/SQL function which takes a binary document (as BLOB) and returns the filtered result (as a CLOB). In a web application this could be used to display a preview of the document directly in the browser - the end user does not have to open the desktop application.
Um das folgende Skript starten zu können, benötigt Ihr die Rolle CTXAPP
.
To execute the following script you need the CTXAPP role.
begin
  ctx_ddl.drop_policy(
    policy_name => 'filter_policy'
  );
end;
/
sho err

begin
  ctx_ddl.create_policy(
    policy_name => 'filter_policy',
    filter      => 'CTXSYS.AUTO_FILTER'
  );
end;
/
sho err

create or replace function filter_document(
  p_document in blob
) return clob
is
  l_data clob;
begin
  dbms_lob.createtemporary(
    lob_loc => l_data, 
    cache   => true, 
    dur     => dbms_lob.call
  );
  ctx_doc.policy_filter(
    policy_name => 'filter_policy',
    document    => p_document, 
    restab      => l_data
  );
  return l_data;
end;
/
sho err
Diese Funktion dient eigentlich nur der Bequemlichkeit - sie legt nur einen temporären CLOB an und riuft danach CTX_DOC.POLICY_FILTER auf (diese tut die eigentliche Arbeit).
This function does not do too much - it's just for convenience: It first createas a temporary CLOB to hold the filter results and then calls the procedure CTX_DOC.POLICY_FILTER. This one does the actual work.
Für die Experten: Wenn man CTX_DOC.IFILTER genutzt hätte, könnte man das gleiche auch ohne das etwas umständliche Erzeugen einer Policy und damit auch ohne die Rolle CTXAPP erreichen. Allerdings rät die Dokumentation davon ab - wir halten uns also an den offiziell empfohlenen Weg.
One remark for the experts: If we would use CTX_DOC.IFILTER we were not required to create the Policy object. We then also would not need the CTXAPP role. So why are we doing those things? Because the documentation recommends it: Applications should use POLICY_FILTER instead of IFILTER - and we want to follow that rule.
Testen ist nun ganz einfach. Für diesen Test wird ein PDF-Dokument direkt aus dem Dateisystem geholt - dazu nutze ich meine API zum Zugriff auf das Dateisystem ...
Testing is now easy: For this simple test I grab the PDF document directly from the file system. I use my API for file system interaction for that ...
select filter_document(file_type.get_file('/pfad/zu/einem/PDF-Dokument.pdf').get_content_as_blob())
from dual
/

<HTML><BODY>
:
HTML ...
Der Filter ist übrigens nicht Teil des Datenbankkerns: es wird ein Callout auf ein Executable gemacht: Das Executable $ORACLE_HOME/ctx/bin/ctxhx macht die eigentliche Arbeit. Und man kann es auch direkt von der Kommandozeile ausführen ... Probiert es mal aus ...
BTW: The actual filter engine is not part of the database kernel - the database does a callout instead. The executable called is $ORACLE_HOME/ctx/bin/ctxhx - and you can also call it directly - just give it a try ...

9. Oktober 2008

Bytes in KB, MB, GB, TB konvertieren: Eine nette PL/SQL Funktion

English title: Nice function to convert a byte number into KB, MB, TB etc ...

Heute bin ich per Zufall auf eine nette Funktion gestoßen - ist zwar Teil von Application Express, kann aber beliebig in SQL oder PL/SQL genutzt werden. Probiert es mal APEX_UTIL.FILESIZE_MASK aus:
Today I found a pretty nice function - it is in fact part of Application Express but it could easily be used in many SQL and PL/SQL contexts. Just give APEX_UTIL.FILESIZE_MASK a try:
SQL> select apex_util.filesize_mask(2) from dual;

APEX_UTIL.FILESIZE_MASK(2)
--------------------------------------------------------------------------------
2

SQL> select apex_util.filesize_mask(2122) from dual;

APEX_UTIL.FILESIZE_MASK(2122)
------------------------------
                           2KB

SQL> select apex_util.filesize_mask(21212212) from dual;

APEX_UTIL.FILESIZE_MASK(21212212)
---------------------------------
                             20MB

Kann man sicherlich hie und da gebrauchen ... Zumindest in Application Express 3.1 ist es drin; in 3.0 oder früheren Versionen müsste man testen ...
Perhaps this is useful also for you ... The function is at least contained in APEX 3.1. If you are on APEX 3.0 or earlier, you might check ...

7. Oktober 2008

OTN Software per Kommandozeile herunterladen

Letzte Woche habe ich meine Datenbank auf 11.1.0.7 gepatcht. Und beim Download des Patchsets aus Metalink habe ich gemerkt, wie lange das manchmal dauern kann ... Naja ... Wenn man den Download nachmittags startet, wird er vielfach eben nicht bis abends fertig. Arbeitsplatzrechner laufen lassen geht auch nicht immer. Und wenn der Patch dann auf dem PC ist, muss er noch zum Server - das braucht dann auch wieder seine Zeit.
Besser wäre es ja, wenn man die Software direkt auf den (Unix / Linux) Server laden könnte. Über VNC könnte man nun zwar den Browser starten und ganz normal arbeiten ... das kann je nach Netzanbindung aber auch ziemlich träge sein ...
Also habe ich mal probiert, wie man den Download im Hintergrund von der Kommandozeile aus machen könnte. Das Tool dazu ist ja schon da: wget
Und für ein Patchset würde man dann wie folgt vorgehen:
  1. Vom Arbeitsplatzrechner ganz normal mit dem Browser ins Metalink gehen
  2. Patchset raussuchen
  3. Download-URL (in die Zwischenablage) kopieren
... dann auf den Server gehen und folgenden Aufruf eingeben:
$ nohup wget \
  --http-user=[Metalink-Username] \
  --http-password=[Metalink-Passwort] \
  --output-file=[Dateiname] \
  --quiet
  --"[URL aus der Zwischenablage]" > download.log 2>1 &
Nun läuft der Download los - man kann die Shell schließen und am nächsten Morgen weiterarbeiten ...
Wenn es nun an OTN Downloads geht, ist das nicht ganz so einfach; denn beim OTN gibt es ein etwas anderes Login-Verfahren. Wenn man den Download anstößt, muss das OTN Browser Cookie gesetzt sein. Aber es gibt auch hier einen Weg:
  1. Via VNC einmal den Browser aufrufen und ins OTN gehen
  2. Mit dem OTN-Account anmelden und so sicherstellen, dass das OTN Cookie gesetzt ist
  3. Alternativ kann man auch (für Firefox) die Datei Cookies.txt auf den Server hochladen.
... nun können wir wieder wget aufrufen:
$ nohup wget \
  --load-cookies=[Pfad zur Cookies.txt-Datei] \
  --output-file=[Dateiname] \
  --quiet
  --"[Download-URL]" > download.log 2>1 &
Wenn das OTN-Cookie richtig gesetzt ist, kommt keine Umleitung zur Anmeldeseite mehr; die Datei wird direkt heruntergeladen.
Bei Firefox 3 hat sich allerdings das Format der Cookies-Datei geädert - es gibt nun keine cookies.txt mehr - diese heißt nun cookies.sqlite. Allerdings gibt es auch hierfür schon eine Lösung. Einfach das Skript mit oracle als Parameter aufrufen und schon extrahiert es die Oracle-Cookies in eine Datei cookies.txt, die dann wieder für den wget-Aufruf verwendet werden kann.
Damit kann man auch längerlaufende Downloads sehr schön automatisiert (über Nacht) ablaufen lassen. Natürlich solltet Ihr die entstandenen Cookie-Dateien (wenn Ihr sie auf den Server kopiert) anschließend löschen oder mit einem speziellen User arbeiten und die Zugriffsrechte sehr eng setzen - da sind schließlich eure OTN Login-Daten drin ...
Und Lizenzbestimmungen beim Download sind hiervon natürlich völlig unberührt - ob Ihr mit wgetoder mit dem Browser arbeitet: Es gilt stets das gleiche ... und man muss die Webseite mit dem License Agreement natürlich vorher lesen .. aber das ist eh' klar, oder?

Beliebte Postings