2009-10-10 02:39:15 +02:00
// ArrayStack.java
2008-08-19 16:10:40 +02:00
// (C) 2008 by Michael Peter Christen; mc@yacy.net, Frankfurt a. M., Germany
// first published 19.08.2008 on http://yacy.net
//
// This is a part of YaCy, a peer-to-peer based web search engine
//
2011-03-08 02:51:51 +01:00
// $LastChangedDate$
2010-01-04 19:12:03 +01:00
// $LastChangedRevision$
// $LastChangedBy$
2008-08-19 16:10:40 +02:00
//
// LICENSE
2011-07-15 10:38:10 +02:00
//
2008-08-19 16:10:40 +02:00
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
2009-10-10 02:43:25 +02:00
package net.yacy.kelondro.blob ;
2008-08-19 16:10:40 +02:00
import java.io.File ;
import java.io.IOException ;
import java.text.ParseException ;
import java.util.ArrayList ;
import java.util.Date ;
2009-04-01 14:39:11 +02:00
import java.util.HashSet ;
2008-08-19 16:10:40 +02:00
import java.util.Iterator ;
import java.util.List ;
import java.util.TreeMap ;
2009-06-19 01:24:23 +02:00
import java.util.concurrent.Callable ;
import java.util.concurrent.CompletionService ;
2008-08-19 16:10:40 +02:00
import java.util.concurrent.CopyOnWriteArrayList ;
2009-06-19 01:24:23 +02:00
import java.util.concurrent.ExecutionException ;
import java.util.concurrent.ExecutorCompletionService ;
import java.util.concurrent.ExecutorService ;
2012-01-16 01:05:30 +01:00
import java.util.concurrent.Executors ;
2009-06-19 01:24:23 +02:00
import java.util.concurrent.Future ;
2012-01-16 01:05:30 +01:00
import java.util.concurrent.FutureTask ;
2009-06-19 01:24:23 +02:00
import java.util.concurrent.LinkedBlockingQueue ;
import java.util.concurrent.RejectedExecutionException ;
import java.util.concurrent.ThreadPoolExecutor ;
import java.util.concurrent.TimeUnit ;
2008-08-19 16:10:40 +02:00
2011-01-03 21:52:54 +01:00
import net.yacy.cora.date.GenericFormatter ;
2011-09-08 00:15:01 +02:00
import net.yacy.cora.document.ASCII ;
2011-03-07 21:36:40 +01:00
import net.yacy.cora.document.UTF8 ;
2011-12-16 23:59:29 +01:00
import net.yacy.cora.order.ByteOrder ;
import net.yacy.cora.order.CloneableIterator ;
2009-12-10 00:27:26 +01:00
import net.yacy.kelondro.index.RowSpaceExceededException ;
2009-10-10 01:13:30 +02:00
import net.yacy.kelondro.logging.Log ;
2009-10-10 01:22:22 +02:00
import net.yacy.kelondro.order.MergeIterator ;
import net.yacy.kelondro.order.NaturalOrder ;
2009-10-10 02:39:15 +02:00
import net.yacy.kelondro.rwi.Reference ;
import net.yacy.kelondro.rwi.ReferenceContainer ;
import net.yacy.kelondro.rwi.ReferenceFactory ;
import net.yacy.kelondro.rwi.ReferenceIterator ;
2009-10-10 03:14:19 +02:00
import net.yacy.kelondro.util.FileUtils ;
2010-06-15 21:44:05 +02:00
import net.yacy.kelondro.util.LookAheadIterator ;
2009-10-10 03:14:19 +02:00
import net.yacy.kelondro.util.NamePrefixThreadFactory ;
2009-10-10 01:13:30 +02:00
2009-01-30 16:33:00 +01:00
2009-06-17 11:58:15 +02:00
public class ArrayStack implements BLOB {
2008-08-19 16:10:40 +02:00
/ *
2009-06-17 11:58:15 +02:00
* This class implements a BLOB using a set of Heap objects
* In addition to a Heap this BLOB can delete large amounts of data using a given time limit .
2008-08-19 16:10:40 +02:00
* This is realized by creating separate BLOB files . New Files are created when either
* - a given time limit is reached
* - a given space limit is reached
* To organize such an array of BLOB files , the following file name structure is used :
* < BLOB - Name > / < YYYYMMDDhhmm > . blob
* That means all BLOB files are inside a directory that has the name of the BLOBArray .
* To delete content that is out - dated , one special method is implemented that deletes content by a given
* time - out . Deletions are not made automatically , they must be triggered using this method .
* /
2011-07-15 10:38:10 +02:00
2010-11-04 14:29:19 +01:00
public static final long maxFileSize = Integer . MAX_VALUE ;
2008-10-16 23:24:09 +02:00
public static final long oneMonth = 1000L * 60L * 60L * 24L * 365L / 12L ;
2011-07-15 10:38:10 +02:00
2010-01-11 00:09:48 +01:00
protected int keylength ;
protected ByteOrder ordering ;
private final File heapLocation ;
private long fileAgeLimit ;
private long fileSizeLimit ;
private long repositoryAgeMax ;
private long repositorySizeMax ;
protected List < blobItem > blobs ;
private final String prefix ;
private final int buffersize ;
2010-08-04 15:33:12 +02:00
private final boolean trimall ;
2011-07-15 10:38:10 +02:00
2009-06-19 01:24:23 +02:00
// the thread pool for the keeperOf executor service
2010-01-11 00:09:48 +01:00
private final ExecutorService executor ;
2011-07-15 10:38:10 +02:00
2011-02-25 13:23:00 +01:00
// use our own formatter to prevent concurrency locks with other processes
2011-02-25 22:11:53 +01:00
private final static GenericFormatter my_SHORT_MILSEC_FORMATTER = new GenericFormatter ( GenericFormatter . FORMAT_SHORT_MILSEC , 1 ) ;
2011-02-25 13:23:00 +01:00
2011-07-15 10:38:10 +02:00
2009-06-17 11:58:15 +02:00
public ArrayStack (
2008-08-19 16:10:40 +02:00
final File heapLocation ,
2009-04-02 17:08:56 +02:00
final String prefix ,
2009-01-30 16:33:00 +01:00
final ByteOrder ordering ,
2011-05-18 16:26:28 +02:00
final int keylength ,
2010-08-04 15:33:12 +02:00
final int buffersize ,
final boolean trimall ) throws IOException {
2008-08-19 16:10:40 +02:00
this . keylength = keylength ;
2009-04-02 17:08:56 +02:00
this . prefix = prefix ;
2008-08-19 16:10:40 +02:00
this . ordering = ordering ;
2008-12-10 12:15:19 +01:00
this . buffersize = buffersize ;
2008-08-19 16:10:40 +02:00
this . heapLocation = heapLocation ;
2008-10-19 20:10:42 +02:00
this . fileAgeLimit = oneMonth ;
2010-11-04 14:29:19 +01:00
this . fileSizeLimit = maxFileSize ;
2008-10-16 23:24:09 +02:00
this . repositoryAgeMax = Long . MAX_VALUE ;
this . repositorySizeMax = Long . MAX_VALUE ;
2010-08-04 15:33:12 +02:00
this . trimall = trimall ;
2008-08-19 16:10:40 +02:00
2009-06-19 01:24:23 +02:00
// init the thread pool for the keeperOf executor service
this . executor = new ThreadPoolExecutor (
2012-02-25 14:07:20 +01:00
1 ,
Runtime . getRuntime ( ) . availableProcessors ( ) , 100 ,
2011-07-15 10:38:10 +02:00
TimeUnit . MILLISECONDS ,
new LinkedBlockingQueue < Runnable > ( ) ,
2012-06-06 13:36:10 +02:00
new NamePrefixThreadFactory ( this . prefix ) ) ;
2011-07-15 10:38:10 +02:00
2008-08-19 16:10:40 +02:00
// check existence of the heap directory
if ( heapLocation . exists ( ) ) {
2008-08-20 10:37:39 +02:00
if ( ! heapLocation . isDirectory ( ) ) throw new IOException ( " the BLOBArray directory " + heapLocation . toString ( ) + " does not exist (is blocked by a file with same name) " ) ;
2008-08-19 16:10:40 +02:00
} else {
2008-08-20 10:37:39 +02:00
if ( ! heapLocation . mkdirs ( ) ) throw new IOException ( " the BLOBArray directory " + heapLocation . toString ( ) + " does not exist (can not be created) " ) ;
2008-08-19 16:10:40 +02:00
}
// register all blob files inside this directory
String [ ] files = heapLocation . list ( ) ;
2011-07-15 10:38:10 +02:00
final HashSet < String > fh = new HashSet < String > ( ) ;
for ( final String file : files )
fh . add ( file ) ;
2009-04-01 14:39:11 +02:00
// delete unused temporary files
boolean deletions = false ;
2011-07-15 10:38:10 +02:00
for ( final String file : files ) {
if ( file . endsWith ( " .tmp " ) | | file . endsWith ( " .prt " ) ) {
FileUtils . deletedelete ( new File ( heapLocation , file ) ) ;
2009-04-01 14:39:11 +02:00
deletions = true ;
}
2011-07-15 10:38:10 +02:00
if ( file . endsWith ( " .idx " ) | | file . endsWith ( " .gap " ) ) {
final String s = file . substring ( 0 , file . length ( ) - 17 ) ;
2009-04-01 14:39:11 +02:00
if ( ! fh . contains ( s ) ) {
2011-07-15 10:38:10 +02:00
FileUtils . deletedelete ( new File ( heapLocation , file ) ) ;
2009-04-01 14:39:11 +02:00
deletions = true ;
}
}
}
if ( deletions ) files = heapLocation . list ( ) ; // make a fresh list
2009-04-02 17:08:56 +02:00
// migrate old file names
Date d ;
long time ;
deletions = false ;
2011-07-15 10:38:10 +02:00
for ( final String file : files ) {
if ( file . length ( ) > = 19 & & file . endsWith ( " .blob " ) ) {
final File f = new File ( heapLocation , file ) ;
2009-09-29 10:13:44 +02:00
if ( f . length ( ) = = 0 ) {
f . delete ( ) ;
deletions = true ;
} else try {
2011-07-15 10:38:10 +02:00
d = GenericFormatter . SHORT_SECOND_FORMATTER . parse ( file . substring ( 0 , 14 ) ) ;
2009-09-29 10:13:44 +02:00
f . renameTo ( newBLOB ( d ) ) ;
2009-04-02 17:08:56 +02:00
deletions = true ;
2011-07-15 10:38:10 +02:00
} catch ( final ParseException e ) { continue ; }
2009-04-02 17:08:56 +02:00
}
}
if ( deletions ) files = heapLocation . list ( ) ; // make a fresh list
2011-07-15 10:38:10 +02:00
2009-04-01 14:39:11 +02:00
// find maximum time: the file with this time will be given a write buffer
2011-07-15 10:38:10 +02:00
final TreeMap < Long , blobItem > sortedItems = new TreeMap < Long , blobItem > ( ) ;
2009-01-30 23:08:08 +01:00
BLOB oneBlob ;
2008-08-19 16:10:40 +02:00
File f ;
2009-04-02 17:08:56 +02:00
long maxtime = 0 ;
2011-07-15 10:38:10 +02:00
for ( final String file : files ) {
2012-06-06 13:36:10 +02:00
if ( file . length ( ) > = 22 & & file . charAt ( this . prefix . length ( ) ) = = '.' & & file . endsWith ( " .blob " ) ) {
2008-08-19 16:10:40 +02:00
try {
2012-06-06 13:36:10 +02:00
d = my_SHORT_MILSEC_FORMATTER . parse ( file . substring ( this . prefix . length ( ) + 1 , this . prefix . length ( ) + 18 ) ) ;
2008-12-10 12:15:19 +01:00
time = d . getTime ( ) ;
if ( time > maxtime ) maxtime = time ;
2011-07-15 10:38:10 +02:00
} catch ( final ParseException e ) { continue ; }
2008-12-10 12:15:19 +01:00
}
}
2011-07-15 10:38:10 +02:00
2009-05-27 17:04:04 +02:00
// open all blob files
2011-07-15 10:38:10 +02:00
for ( final String file : files ) {
2012-06-06 13:36:10 +02:00
if ( file . length ( ) > = 22 & & file . charAt ( this . prefix . length ( ) ) = = '.' & & file . endsWith ( " .blob " ) ) {
2009-04-02 17:08:56 +02:00
try {
2012-06-06 13:36:10 +02:00
d = my_SHORT_MILSEC_FORMATTER . parse ( file . substring ( this . prefix . length ( ) + 1 , this . prefix . length ( ) + 18 ) ) ;
2011-07-15 10:38:10 +02:00
f = new File ( heapLocation , file ) ;
2008-12-10 12:15:19 +01:00
time = d . getTime ( ) ;
2010-08-04 15:33:12 +02:00
if ( time = = maxtime & & ! trimall ) {
oneBlob = new Heap ( f , keylength , ordering , buffersize ) ;
} else {
oneBlob = new HeapModifier ( f , keylength , ordering ) ;
oneBlob . trim ( ) ; // no writings here, can be used with minimum memory
}
2008-12-10 12:15:19 +01:00
sortedItems . put ( Long . valueOf ( time ) , new blobItem ( d , f , oneBlob ) ) ;
2011-07-15 10:38:10 +02:00
} catch ( final ParseException e ) { continue ; }
2008-08-19 16:10:40 +02:00
}
}
2011-07-15 10:38:10 +02:00
2008-08-19 16:10:40 +02:00
// read the blob tree in a sorted way and write them into an array
2011-07-15 10:38:10 +02:00
this . blobs = new CopyOnWriteArrayList < blobItem > ( ) ;
for ( final blobItem bi : sortedItems . values ( ) ) {
this . blobs . add ( bi ) ;
2008-08-19 16:10:40 +02:00
}
}
2011-07-15 10:38:10 +02:00
2012-01-02 02:14:05 +01:00
@Override
2010-08-04 15:33:12 +02:00
public long mem ( ) {
long m = 0 ;
2011-07-15 10:38:10 +02:00
if ( this . blobs ! = null ) for ( final blobItem b : this . blobs ) m + = b . blob . mem ( ) ;
2010-08-04 15:33:12 +02:00
return m ;
}
2011-07-15 10:38:10 +02:00
2012-01-02 02:14:05 +01:00
@Override
2010-08-04 15:33:12 +02:00
public void trim ( ) {
// trim shall not be called for ArrayStacks because the characteristics of an ArrayStack is that the 'topmost' BLOB on the stack
// is used for write operations and all other shall be trimmed automatically since they are not used for writing. And the
// topmost BLOB must not be trimmed to support fast writings.
throw new UnsupportedOperationException ( ) ;
}
2011-07-15 10:38:10 +02:00
2009-01-21 19:23:37 +01:00
/ * *
* add a blob file to the array .
* note that this file must be generated with a file name from newBLOB ( )
* @param location
* @throws IOException
* /
2011-07-15 10:38:10 +02:00
public synchronized void mountBLOB ( final File location , final boolean full ) throws IOException {
2009-01-21 19:23:37 +01:00
Date d ;
try {
2011-07-15 10:38:10 +02:00
d = my_SHORT_MILSEC_FORMATTER . parse ( location . getName ( ) . substring ( this . prefix . length ( ) + 1 , this . prefix . length ( ) + 18 ) ) ;
} catch ( final ParseException e ) {
2009-01-21 19:23:37 +01:00
throw new IOException ( " date parse problem with file " + location . toString ( ) + " : " + e . getMessage ( ) ) ;
}
2010-08-04 15:33:12 +02:00
BLOB oneBlob ;
2011-07-15 10:38:10 +02:00
if ( full & & this . buffersize > 0 & & ! this . trimall ) {
oneBlob = new Heap ( location , this . keylength , this . ordering , this . buffersize ) ;
2010-08-04 15:33:12 +02:00
} else {
2011-07-15 10:38:10 +02:00
oneBlob = new HeapModifier ( location , this . keylength , this . ordering ) ;
2010-08-04 15:33:12 +02:00
oneBlob . trim ( ) ;
}
2011-07-15 10:38:10 +02:00
this . blobs . add ( new blobItem ( d , location , oneBlob ) ) ;
2009-01-21 19:23:37 +01:00
}
2011-07-15 10:38:10 +02:00
public synchronized void unmountBLOB ( final File location , final boolean writeIDX ) {
2009-01-21 19:23:37 +01:00
blobItem b ;
2009-03-31 19:03:13 +02:00
for ( int i = 0 ; i < this . blobs . size ( ) ; i + + ) {
b = this . blobs . get ( i ) ;
2009-05-04 00:54:47 +02:00
if ( b . location . getAbsolutePath ( ) . equals ( location . getAbsolutePath ( ) ) ) {
2009-03-31 19:03:13 +02:00
this . blobs . remove ( i ) ;
2009-03-18 17:14:31 +01:00
b . blob . close ( writeIDX ) ;
2009-03-30 21:05:08 +02:00
b . blob = null ;
b . location = null ;
2009-01-21 19:23:37 +01:00
return ;
}
}
2009-05-04 00:54:47 +02:00
Log . logSevere ( " BLOBArray " , " file " + location + " cannot be unmounted. The file " + ( ( location . exists ( ) ) ? " exists. " : " does not exist. " ) ) ;
2009-01-21 19:23:37 +01:00
}
2011-07-15 10:38:10 +02:00
private File unmount ( final int idx ) {
final blobItem b = this . blobs . remove ( idx ) ;
2009-03-30 21:05:08 +02:00
b . blob . close ( false ) ;
b . blob = null ;
2011-07-15 10:38:10 +02:00
final File f = b . location ;
2009-03-30 21:05:08 +02:00
b . location = null ;
return f ;
}
2011-07-15 10:38:10 +02:00
public synchronized File [ ] unmountBestMatch ( final float maxq , long maxResultSize ) {
2009-05-04 00:54:47 +02:00
if ( this . blobs . size ( ) < 2 ) return null ;
2009-03-31 18:49:02 +02:00
long l , r ;
2009-05-04 00:54:47 +02:00
File lf , rf ;
2011-02-02 16:54:13 +01:00
float min = Float . MAX_VALUE ;
2011-07-15 10:38:10 +02:00
final File [ ] bestMatch = new File [ 2 ] ;
2009-03-31 18:49:02 +02:00
maxResultSize = maxResultSize > > 1 ;
2009-06-29 18:07:10 +02:00
int loopcount = 0 ;
mainloop : for ( int i = 0 ; i < this . blobs . size ( ) - 1 ; i + + ) {
2009-03-30 21:05:08 +02:00
for ( int j = i + 1 ; j < this . blobs . size ( ) ; j + + ) {
2009-06-29 18:07:10 +02:00
loopcount + + ;
2009-05-04 00:54:47 +02:00
lf = this . blobs . get ( i ) . location ;
rf = this . blobs . get ( j ) . location ;
l = 1 + ( lf . length ( ) > > 1 ) ;
r = 1 + ( rf . length ( ) > > 1 ) ;
2009-03-31 18:49:02 +02:00
if ( l + r > maxResultSize ) continue ;
2011-07-15 10:38:10 +02:00
final float q = Math . max ( ( float ) l , ( float ) r ) / Math . min ( ( float ) l , ( float ) r ) ;
2009-03-30 21:05:08 +02:00
if ( q < min ) {
min = q ;
2009-05-04 00:54:47 +02:00
bestMatch [ 0 ] = lf ;
bestMatch [ 1 ] = rf ;
2009-03-30 21:05:08 +02:00
}
2011-02-02 16:54:13 +01:00
if ( loopcount > 1000 & & min < = maxq & & min ! = Float . MAX_VALUE ) break mainloop ;
2009-03-30 21:05:08 +02:00
}
}
if ( min > maxq ) return null ;
2009-05-04 00:54:47 +02:00
unmountBLOB ( bestMatch [ 1 ] , false ) ;
unmountBLOB ( bestMatch [ 0 ] , false ) ;
return bestMatch ;
2009-03-30 21:05:08 +02:00
}
2010-01-04 19:12:03 +01:00
public synchronized File unmountOldest ( ) {
2012-07-10 22:59:03 +02:00
if ( this . blobs . isEmpty ( ) ) return null ;
2010-01-04 19:12:03 +01:00
if ( System . currentTimeMillis ( ) - this . blobs . get ( 0 ) . creation . getTime ( ) < this . fileAgeLimit ) return null ;
2011-07-15 10:38:10 +02:00
final File f = this . blobs . get ( 0 ) . location ;
2010-01-04 19:12:03 +01:00
unmountBLOB ( f , false ) ;
return f ;
}
2011-07-15 10:38:10 +02:00
public synchronized File [ ] unmountSmallest ( final long maxResultSize ) {
2009-05-04 00:54:47 +02:00
if ( this . blobs . size ( ) < 2 ) return null ;
2011-07-15 10:38:10 +02:00
final File f0 = smallestBLOB ( null , maxResultSize ) ;
2009-03-31 18:49:02 +02:00
if ( f0 = = null ) return null ;
2011-07-15 10:38:10 +02:00
final File f1 = smallestBLOB ( f0 , maxResultSize - f0 . length ( ) ) ;
2009-03-31 18:49:02 +02:00
if ( f1 = = null ) return null ;
2011-07-15 10:38:10 +02:00
2009-03-31 18:49:02 +02:00
unmountBLOB ( f0 , false ) ;
unmountBLOB ( f1 , false ) ;
return new File [ ] { f0 , f1 } ;
}
2011-07-15 10:38:10 +02:00
public synchronized File unmountSmallestBLOB ( final long maxResultSize ) {
2009-03-31 21:17:45 +02:00
return smallestBLOB ( null , maxResultSize ) ;
2009-03-31 18:49:02 +02:00
}
2011-07-15 10:38:10 +02:00
public synchronized File smallestBLOB ( final File excluding , final long maxsize ) {
2009-12-02 01:37:59 +01:00
if ( this . blobs . isEmpty ( ) ) return null ;
2009-05-04 00:54:47 +02:00
File bestFile = null ;
2009-03-20 15:54:37 +01:00
long smallest = Long . MAX_VALUE ;
2009-05-04 00:54:47 +02:00
File f = null ;
2009-03-20 15:54:37 +01:00
for ( int i = 0 ; i < this . blobs . size ( ) ; i + + ) {
2009-05-04 00:54:47 +02:00
f = this . blobs . get ( i ) . location ;
if ( excluding ! = null & & f . getAbsolutePath ( ) . equals ( excluding . getAbsolutePath ( ) ) ) continue ;
if ( f . length ( ) < smallest ) {
smallest = f . length ( ) ;
bestFile = f ;
2009-03-20 15:54:37 +01:00
}
2009-06-29 18:07:10 +02:00
if ( i > 70 & & smallest < = maxsize & & smallest ! = Long . MAX_VALUE ) break ;
2009-03-20 15:54:37 +01:00
}
2009-03-31 21:17:45 +02:00
if ( smallest > maxsize ) return null ;
2009-05-04 00:54:47 +02:00
return bestFile ;
2009-03-20 15:54:37 +01:00
}
2011-07-15 10:38:10 +02:00
public synchronized File unmountOldestBLOB ( final boolean smallestFromFirst2 ) {
2009-12-02 01:37:59 +01:00
if ( this . blobs . isEmpty ( ) ) return null ;
2009-03-20 15:54:37 +01:00
int idx = 0 ;
if ( smallestFromFirst2 & & this . blobs . get ( 1 ) . location . length ( ) < this . blobs . get ( 0 ) . location . length ( ) ) idx = 1 ;
2009-03-30 21:05:08 +02:00
return unmount ( idx ) ;
2009-03-20 15:54:37 +01:00
}
2011-07-15 10:38:10 +02:00
2009-01-21 19:23:37 +01:00
/ * *
* return the number of BLOB files in this array
* @return
* /
2009-03-18 21:21:19 +01:00
public synchronized int entries ( ) {
2009-03-31 21:17:45 +02:00
return ( this . blobs = = null ) ? 0 : this . blobs . size ( ) ;
2009-01-21 19:23:37 +01:00
}
2011-07-15 10:38:10 +02:00
2009-01-21 19:23:37 +01:00
/ * *
* generate a new BLOB file name with a given date .
* This method is needed to generate a file name that matches to the name structure that is needed for parts of the array
* @param creation
* @return
* /
2011-07-15 10:38:10 +02:00
public synchronized File newBLOB ( final Date creation ) {
2009-04-02 17:08:56 +02:00
//return new File(heapLocation, DateFormatter.formatShortSecond(creation) + "." + blobSalt + ".blob");
2011-07-15 10:38:10 +02:00
return new File ( this . heapLocation , this . prefix + " . " + my_SHORT_MILSEC_FORMATTER . format ( creation ) + " .blob " ) ;
2009-01-21 19:23:37 +01:00
}
2011-07-15 10:38:10 +02:00
2012-01-02 02:14:05 +01:00
@Override
2008-12-08 01:17:45 +01:00
public String name ( ) {
return this . heapLocation . getName ( ) ;
}
2011-07-15 10:38:10 +02:00
public void setMaxAge ( final long maxAge ) {
2008-10-16 23:24:09 +02:00
this . repositoryAgeMax = maxAge ;
2008-10-19 20:10:42 +02:00
this . fileAgeLimit = Math . min ( oneMonth , maxAge / 10 ) ;
2008-10-16 23:24:09 +02:00
}
2011-07-15 10:38:10 +02:00
public void setMaxSize ( final long maxSize ) {
2008-10-16 23:24:09 +02:00
this . repositorySizeMax = maxSize ;
2010-11-04 14:29:19 +01:00
this . fileSizeLimit = Math . min ( maxFileSize , maxSize / 100L ) ;
2010-11-03 00:57:11 +01:00
executeLimits ( ) ;
2008-10-16 23:24:09 +02:00
}
2011-07-15 10:38:10 +02:00
2008-10-16 23:24:09 +02:00
private void executeLimits ( ) {
// check if storage limits are reached and execute consequences
2011-07-15 10:38:10 +02:00
if ( this . blobs . isEmpty ( ) ) return ;
2008-10-16 23:24:09 +02:00
// age limit:
2011-07-15 10:38:10 +02:00
while ( ! this . blobs . isEmpty ( ) & & System . currentTimeMillis ( ) - this . blobs . get ( 0 ) . creation . getTime ( ) - this . fileAgeLimit > this . repositoryAgeMax ) {
2008-10-16 23:24:09 +02:00
// too old
2011-07-15 10:38:10 +02:00
final blobItem oldestBLOB = this . blobs . remove ( 0 ) ;
2009-03-18 17:14:31 +01:00
oldestBLOB . blob . close ( false ) ;
2009-03-30 17:31:25 +02:00
oldestBLOB . blob = null ;
FileUtils . deletedelete ( oldestBLOB . location ) ;
2008-10-16 23:24:09 +02:00
}
2011-07-15 10:38:10 +02:00
2008-10-16 23:24:09 +02:00
// size limit
2011-07-15 10:38:10 +02:00
while ( ! this . blobs . isEmpty ( ) & & length ( ) > this . repositorySizeMax ) {
2008-10-16 23:24:09 +02:00
// too large
2011-07-15 10:38:10 +02:00
final blobItem oldestBLOB = this . blobs . remove ( 0 ) ;
2009-03-18 17:14:31 +01:00
oldestBLOB . blob . close ( false ) ;
2009-03-30 17:31:25 +02:00
FileUtils . deletedelete ( oldestBLOB . location ) ;
2008-10-16 23:24:09 +02:00
}
}
2011-07-15 10:38:10 +02:00
2008-10-16 23:24:09 +02:00
/ *
2009-03-18 21:21:19 +01:00
* return the size of the repository ( in bytes )
2008-10-16 23:24:09 +02:00
* /
2012-01-02 02:14:05 +01:00
@Override
2009-03-18 21:21:19 +01:00
public synchronized long length ( ) {
2008-10-16 23:24:09 +02:00
long s = 0 ;
2011-07-15 10:38:10 +02:00
for ( int i = 0 ; i < this . blobs . size ( ) ; i + + ) s + = this . blobs . get ( i ) . location . length ( ) ;
2008-10-16 23:24:09 +02:00
return s ;
}
2011-07-15 10:38:10 +02:00
2012-01-02 02:14:05 +01:00
@Override
2009-01-30 16:33:00 +01:00
public ByteOrder ordering ( ) {
2008-10-20 00:30:44 +02:00
return this . ordering ;
}
2011-07-15 10:38:10 +02:00
2008-08-19 16:10:40 +02:00
private class blobItem {
Date creation ;
File location ;
2009-01-30 23:08:08 +01:00
BLOB blob ;
2011-07-15 10:38:10 +02:00
public blobItem ( final Date creation , final File location , final BLOB blob ) {
2010-04-20 15:45:22 +02:00
assert blob ! = null ;
2008-08-19 16:10:40 +02:00
this . creation = creation ;
this . location = location ;
this . blob = blob ;
}
2011-07-15 10:38:10 +02:00
public blobItem ( final int buffer ) throws IOException {
2008-08-19 16:10:40 +02:00
// make a new blob file and assign it in this item
this . creation = new Date ( ) ;
2009-01-06 10:38:08 +01:00
this . location = newBLOB ( this . creation ) ;
2011-07-15 10:38:10 +02:00
this . blob = ( buffer = = 0 ) ? new HeapModifier ( this . location , ArrayStack . this . keylength , ArrayStack . this . ordering ) : new Heap ( this . location , ArrayStack . this . keylength , ArrayStack . this . ordering , buffer ) ;
2008-08-19 16:10:40 +02:00
}
}
2009-01-21 19:23:37 +01:00
2008-08-19 16:10:40 +02:00
/ * *
* ask for the length of the primary key
* @return the length of the key
* /
2012-01-02 02:14:05 +01:00
@Override
2008-08-19 16:10:40 +02:00
public int keylength ( ) {
return this . keylength ;
}
2011-07-15 10:38:10 +02:00
2008-08-19 16:10:40 +02:00
/ * *
* clears the content of the database
* @throws IOException
* /
2012-01-02 02:14:05 +01:00
@Override
2009-03-18 21:21:19 +01:00
public synchronized void clear ( ) throws IOException {
2011-07-15 10:38:10 +02:00
for ( final blobItem bi : this . blobs ) {
2009-03-29 23:28:14 +02:00
bi . blob . clear ( ) ;
bi . blob . close ( false ) ;
2011-03-17 18:09:19 +01:00
HeapWriter . delete ( bi . location ) ;
2009-03-29 23:28:14 +02:00
}
2011-07-15 10:38:10 +02:00
this . blobs . clear ( ) ;
2008-08-19 16:10:40 +02:00
}
2011-07-15 10:38:10 +02:00
2008-08-19 16:10:40 +02:00
/ * *
2009-03-18 21:21:19 +01:00
* ask for the number of blob entries
2008-08-19 16:10:40 +02:00
* @return the number of entries in the table
* /
2012-01-02 02:14:05 +01:00
@Override
2009-03-18 21:21:19 +01:00
public synchronized int size ( ) {
2008-08-19 16:10:40 +02:00
int s = 0 ;
2011-07-15 10:38:10 +02:00
for ( final blobItem bi : this . blobs ) s + = bi . blob . size ( ) ;
2008-08-19 16:10:40 +02:00
return s ;
}
2011-07-15 10:38:10 +02:00
2012-01-02 02:14:05 +01:00
@Override
2009-12-02 01:37:59 +01:00
public synchronized boolean isEmpty ( ) {
2011-07-15 10:38:10 +02:00
for ( final blobItem bi : this . blobs ) if ( ! bi . blob . isEmpty ( ) ) return false ;
2009-12-02 01:37:59 +01:00
return true ;
}
2011-07-15 10:38:10 +02:00
2009-06-14 00:59:54 +02:00
/ * *
* ask for the number of blob entries in each blob of the blob array
* @return the number of entries in each blob
* /
2009-07-20 10:21:17 +02:00
public synchronized int [ ] sizes ( ) {
2011-07-15 10:38:10 +02:00
if ( this . blobs = = null ) return new int [ 0 ] ;
final int [ ] s = new int [ this . blobs . size ( ) ] ;
2009-06-14 00:59:54 +02:00
int c = 0 ;
2011-07-15 10:38:10 +02:00
for ( final blobItem bi : this . blobs ) s [ c + + ] = bi . blob . size ( ) ;
2009-06-14 00:59:54 +02:00
return s ;
}
2011-07-15 10:38:10 +02:00
2008-08-19 16:10:40 +02:00
/ * *
* iterator over all keys
* @param up
* @param rotating
* @return
* @throws IOException
* /
2012-01-02 02:14:05 +01:00
@Override
2011-07-15 10:38:10 +02:00
public synchronized CloneableIterator < byte [ ] > keys ( final boolean up , final boolean rotating ) throws IOException {
2009-06-30 00:22:35 +02:00
assert rotating = = false ;
2011-07-15 10:38:10 +02:00
final List < CloneableIterator < byte [ ] > > c = new ArrayList < CloneableIterator < byte [ ] > > ( this . blobs . size ( ) ) ;
final Iterator < blobItem > i = this . blobs . iterator ( ) ;
2008-08-19 16:10:40 +02:00
while ( i . hasNext ( ) ) {
c . add ( i . next ( ) . blob . keys ( up , rotating ) ) ;
}
2009-01-30 23:44:20 +01:00
return MergeIterator . cascade ( c , this . ordering , MergeIterator . simpleMerge , up ) ;
2008-08-19 16:10:40 +02:00
}
2011-07-15 10:38:10 +02:00
2008-08-19 16:10:40 +02:00
/ * *
* iterate over all keys
* @param up
* @param firstKey
* @return
* @throws IOException
* /
2012-01-02 02:14:05 +01:00
@Override
2011-07-15 10:38:10 +02:00
public synchronized CloneableIterator < byte [ ] > keys ( final boolean up , final byte [ ] firstKey ) throws IOException {
final List < CloneableIterator < byte [ ] > > c = new ArrayList < CloneableIterator < byte [ ] > > ( this . blobs . size ( ) ) ;
final Iterator < blobItem > i = this . blobs . iterator ( ) ;
2008-08-19 16:10:40 +02:00
while ( i . hasNext ( ) ) {
c . add ( i . next ( ) . blob . keys ( up , firstKey ) ) ;
}
2009-01-30 23:44:20 +01:00
return MergeIterator . cascade ( c , this . ordering , MergeIterator . simpleMerge , up ) ;
2008-08-19 16:10:40 +02:00
}
2011-07-15 10:38:10 +02:00
2008-08-19 16:10:40 +02:00
/ * *
* check if a specific key is in the database
* @param key the primary key
* @return
* @throws IOException
* /
2012-01-02 02:14:05 +01:00
@Override
2011-07-15 10:38:10 +02:00
public synchronized boolean containsKey ( final byte [ ] key ) {
final blobItem bi = keeperOf ( key ) ;
2009-06-19 01:24:23 +02:00
return bi ! = null ;
//for (blobItem bi: blobs) if (bi.blob.has(key)) return true;
//return false;
}
2011-07-15 10:38:10 +02:00
2011-02-25 12:58:01 +01:00
/ * *
* find the blobItem that holds the key
* if no blobItem is found , then return null
* @param key
* @return the blobItem that holds the key or null if no blobItem is found
* /
private blobItem keeperOf ( final byte [ ] key ) {
2012-07-10 22:59:03 +02:00
if ( this . blobs . isEmpty ( ) ) return null ;
2011-07-15 10:38:10 +02:00
if ( this . blobs . size ( ) = = 1 ) {
final blobItem bi = this . blobs . get ( 0 ) ;
2011-02-25 12:58:01 +01:00
if ( bi . blob . containsKey ( key ) ) return bi ;
return null ;
}
2012-01-16 01:05:30 +01:00
2012-01-02 02:14:05 +01:00
// first check the current blob only because that has most probably the key if any has that key
int bs1 = this . blobs . size ( ) - 1 ;
blobItem bi = this . blobs . get ( bs1 ) ;
if ( bi . blob . containsKey ( key ) ) return bi ;
if ( this . blobs . size ( ) = = 2 ) {
// this should not be done concurrently
bi = this . blobs . get ( 0 ) ;
if ( bi . blob . containsKey ( key ) ) return bi ;
return null ;
}
2012-01-16 01:05:30 +01:00
2009-06-19 01:24:23 +02:00
// start a concurrent query to database tables
2011-07-15 10:38:10 +02:00
final CompletionService < blobItem > cs = new ExecutorCompletionService < blobItem > ( this . executor ) ;
2009-06-19 01:24:23 +02:00
int accepted = 0 ;
2012-01-02 02:14:05 +01:00
for ( int i = 0 ; i < bs1 ; i + + ) {
final blobItem b = this . blobs . get ( i ) ;
2009-06-19 01:24:23 +02:00
try {
2011-02-25 12:58:01 +01:00
cs . submit ( new Callable < blobItem > ( ) {
2012-01-02 02:14:05 +01:00
@Override
2011-02-25 12:58:01 +01:00
public blobItem call ( ) {
2012-01-02 02:14:05 +01:00
if ( b . blob . containsKey ( key ) ) return b ;
2011-02-25 12:58:01 +01:00
return null ;
2009-06-19 01:24:23 +02:00
}
2011-02-25 12:58:01 +01:00
} ) ;
accepted + + ;
} catch ( final RejectedExecutionException e ) {
// the executor is either shutting down or the blocking queue is full
// execute the search direct here without concurrency
2012-01-02 02:14:05 +01:00
if ( b . blob . containsKey ( key ) ) return b ;
2011-02-25 12:58:01 +01:00
}
}
// read the result
try {
for ( int i = 0 ; i < accepted ; i + + ) {
final Future < blobItem > f = cs . take ( ) ;
//hash(System.out.println("**********accepted = " + accepted + ", i =" + i);
if ( f = = null ) continue ;
final blobItem index = f . get ( ) ;
if ( index ! = null ) {
//System.out.println("*DEBUG SplitTable success.time = " + (System.currentTimeMillis() - start) + " ms");
return index ;
2009-06-19 01:24:23 +02:00
}
}
2011-02-25 12:58:01 +01:00
//System.out.println("*DEBUG SplitTable fail.time = " + (System.currentTimeMillis() - start) + " ms");
return null ;
} catch ( final InterruptedException e ) {
Thread . currentThread ( ) . interrupt ( ) ;
} catch ( final ExecutionException e ) {
Log . logSevere ( " ArrayStack " , " " , e ) ;
throw new RuntimeException ( e . getCause ( ) ) ;
2009-06-19 01:24:23 +02:00
}
//System.out.println("*DEBUG SplitTable fail.time = " + (System.currentTimeMillis() - start) + " ms");
return null ;
2008-08-19 16:10:40 +02:00
}
2011-07-15 10:38:10 +02:00
2008-08-19 16:10:40 +02:00
/ * *
* retrieve the whole BLOB from the table
* @param key the primary key
* @return
* @throws IOException
* /
2012-01-02 02:14:05 +01:00
@Override
2011-07-15 10:38:10 +02:00
public byte [ ] get ( final byte [ ] key ) throws IOException , RowSpaceExceededException {
2012-07-10 22:59:03 +02:00
if ( this . blobs = = null | | this . blobs . isEmpty ( ) ) return null ;
2011-07-15 10:38:10 +02:00
if ( this . blobs . size ( ) = = 1 ) {
final blobItem bi = this . blobs . get ( 0 ) ;
2011-02-25 12:58:01 +01:00
return bi . blob . get ( key ) ;
}
2011-07-15 10:38:10 +02:00
final blobItem bi = keeperOf ( key ) ;
2011-02-25 12:58:01 +01:00
return ( bi = = null ) ? null : bi . blob . get ( key ) ;
2011-07-15 10:38:10 +02:00
2011-02-25 12:58:01 +01:00
/ *
2009-06-19 01:24:23 +02:00
byte [ ] b ;
2008-08-19 16:10:40 +02:00
for ( blobItem bi : blobs ) {
b = bi . blob . get ( key ) ;
if ( b ! = null ) return b ;
}
return null ;
2011-02-25 12:58:01 +01:00
* /
2008-08-19 16:10:40 +02:00
}
2011-07-15 10:38:10 +02:00
2012-01-02 02:14:05 +01:00
@Override
2011-07-15 10:38:10 +02:00
public byte [ ] get ( final Object key ) {
2010-08-24 14:36:56 +02:00
if ( ! ( key instanceof byte [ ] ) ) return null ;
try {
return get ( ( byte [ ] ) key ) ;
2011-07-15 10:38:10 +02:00
} catch ( final IOException e ) {
2010-08-24 14:36:56 +02:00
Log . logException ( e ) ;
2011-07-15 10:38:10 +02:00
} catch ( final RowSpaceExceededException e ) {
2010-08-24 14:36:56 +02:00
Log . logException ( e ) ;
}
return null ;
}
2011-07-15 10:38:10 +02:00
2009-01-06 10:38:08 +01:00
/ * *
* get all BLOBs in the array .
* this is useful when it is not clear if an entry is unique in all BLOBs in this array .
* @param key
* @return
* @throws IOException
* /
2011-07-15 10:38:10 +02:00
public Iterable < byte [ ] > getAll ( final byte [ ] key ) throws IOException {
2009-06-02 18:53:45 +02:00
return new BlobValues ( key ) ;
}
2011-07-15 10:38:10 +02:00
2010-06-15 21:44:05 +02:00
public class BlobValues extends LookAheadIterator < byte [ ] > {
2009-06-02 18:53:45 +02:00
2010-01-11 00:09:48 +01:00
private final Iterator < blobItem > bii ;
private final byte [ ] key ;
2011-07-15 10:38:10 +02:00
public BlobValues ( final byte [ ] key ) {
this . bii = ArrayStack . this . blobs . iterator ( ) ;
2009-06-02 18:53:45 +02:00
this . key = key ;
}
2011-07-15 10:38:10 +02:00
2012-01-02 02:14:05 +01:00
@Override
2010-06-15 21:44:05 +02:00
protected byte [ ] next0 ( ) {
2009-06-02 18:53:45 +02:00
while ( this . bii . hasNext ( ) ) {
2011-07-15 10:38:10 +02:00
final BLOB b = this . bii . next ( ) . blob ;
2010-04-20 09:43:48 +02:00
if ( b = = null ) continue ;
2009-06-02 18:53:45 +02:00
try {
2011-07-15 10:38:10 +02:00
final byte [ ] n = b . get ( this . key ) ;
2010-06-15 21:44:05 +02:00
if ( n ! = null ) return n ;
2011-07-15 10:38:10 +02:00
} catch ( final IOException e ) {
Log . logSevere ( " ArrayStack " , " BlobValues - IOException: " + e . getMessage ( ) , e ) ;
2010-06-15 21:44:05 +02:00
return null ;
2011-07-15 10:38:10 +02:00
} catch ( final RowSpaceExceededException e ) {
Log . logSevere ( " ArrayStack " , " BlobValues - RowSpaceExceededException: " + e . getMessage ( ) , e ) ;
break ;
2009-06-02 18:53:45 +02:00
}
}
2010-06-15 21:44:05 +02:00
return null ;
2011-07-15 10:38:10 +02:00
}
2009-01-06 10:38:08 +01:00
}
2011-07-15 10:38:10 +02:00
2008-10-16 23:24:09 +02:00
/ * *
* retrieve the size of the BLOB
* @param key
* @return the size of the BLOB or - 1 if the BLOB does not exist
* @throws IOException
* /
2012-01-02 02:14:05 +01:00
@Override
2011-07-15 10:38:10 +02:00
public synchronized long length ( final byte [ ] key ) throws IOException {
2008-10-16 23:24:09 +02:00
long l ;
2011-07-15 10:38:10 +02:00
for ( final blobItem bi : this . blobs ) {
2008-10-16 23:24:09 +02:00
l = bi . blob . length ( key ) ;
if ( l > = 0 ) return l ;
}
return - 1 ;
}
2011-07-15 10:38:10 +02:00
2010-10-13 00:02:10 +02:00
/ * *
* get all BLOBs in the array .
* this is useful when it is not clear if an entry is unique in all BLOBs in this array .
* @param key
* @return
* @throws IOException
* /
2011-07-15 10:38:10 +02:00
public Iterable < Long > lengthAll ( final byte [ ] key ) throws IOException {
2010-10-13 00:02:10 +02:00
return new BlobLengths ( key ) ;
}
2011-07-15 10:38:10 +02:00
2010-10-13 00:02:10 +02:00
public class BlobLengths extends LookAheadIterator < Long > {
private final Iterator < blobItem > bii ;
private final byte [ ] key ;
2011-07-15 10:38:10 +02:00
public BlobLengths ( final byte [ ] key ) {
this . bii = ArrayStack . this . blobs . iterator ( ) ;
2010-10-13 00:02:10 +02:00
this . key = key ;
}
2011-07-15 10:38:10 +02:00
2012-01-02 02:14:05 +01:00
@Override
2010-10-13 00:02:10 +02:00
protected Long next0 ( ) {
while ( this . bii . hasNext ( ) ) {
2011-07-15 10:38:10 +02:00
final BLOB b = this . bii . next ( ) . blob ;
2010-10-13 00:02:10 +02:00
if ( b = = null ) continue ;
try {
2011-07-15 10:38:10 +02:00
final long l = b . length ( this . key ) ;
2010-10-13 00:02:10 +02:00
if ( l > = 0 ) return Long . valueOf ( l ) ;
2011-07-15 10:38:10 +02:00
} catch ( final IOException e ) {
2010-10-13 00:02:10 +02:00
Log . logSevere ( " ArrayStack " , " " , e ) ;
return null ;
}
}
return null ;
2011-07-15 10:38:10 +02:00
}
2010-10-13 00:02:10 +02:00
}
2011-07-15 10:38:10 +02:00
2009-09-29 10:13:44 +02:00
/ * *
* retrieve the sizes of all BLOB
* @param key
* @return the size of the BLOB or - 1 if the BLOB does not exist
* @throws IOException
* /
2011-07-15 10:38:10 +02:00
public synchronized long lengthAdd ( final byte [ ] key ) throws IOException {
2009-09-29 10:13:44 +02:00
long l = 0 ;
2011-07-15 10:38:10 +02:00
for ( final blobItem bi : this . blobs ) {
2009-09-29 10:13:44 +02:00
l + = bi . blob . length ( key ) ;
}
return l ;
}
2011-07-15 10:38:10 +02:00
2008-08-19 16:10:40 +02:00
/ * *
* write a whole byte array as BLOB to the table
* @param key the primary key
* @param b
* @throws IOException
2011-07-15 10:38:10 +02:00
* @throws RowSpaceExceededException
2008-08-19 16:10:40 +02:00
* /
2012-01-02 02:14:05 +01:00
@Override
2011-07-15 10:38:10 +02:00
public synchronized void insert ( final byte [ ] key , final byte [ ] b ) throws IOException {
blobItem bi = ( this . blobs . isEmpty ( ) ) ? null : this . blobs . get ( this . blobs . size ( ) - 1 ) ;
2010-07-22 09:59:22 +02:00
/ *
2008-10-16 23:24:09 +02:00
if ( bi = = null )
System . out . println ( " bi == null " ) ;
else if ( System . currentTimeMillis ( ) - bi . creation . getTime ( ) > this . fileAgeLimit )
System . out . println ( " System.currentTimeMillis() - bi.creation.getTime() > this.maxage " ) ;
else if ( bi . location . length ( ) > this . fileSizeLimit )
System . out . println ( " bi.location.length() > this.maxsize " ) ;
2010-07-22 09:59:22 +02:00
* /
2008-10-16 23:24:09 +02:00
if ( ( bi = = null ) | | ( System . currentTimeMillis ( ) - bi . creation . getTime ( ) > this . fileAgeLimit ) | | ( bi . location . length ( ) > this . fileSizeLimit ) ) {
2008-08-19 16:10:40 +02:00
// add a new blob to the array
2011-07-15 10:38:10 +02:00
bi = new blobItem ( this . buffersize ) ;
this . blobs . add ( bi ) ;
2008-08-19 16:10:40 +02:00
}
2009-06-17 11:58:15 +02:00
assert bi . blob instanceof Heap ;
2010-08-24 14:36:56 +02:00
bi . blob . insert ( key , b ) ;
2008-10-16 23:24:09 +02:00
executeLimits ( ) ;
2008-08-19 16:10:40 +02:00
}
2011-07-15 10:38:10 +02:00
2009-01-21 19:23:37 +01:00
/ * *
2010-08-04 15:33:12 +02:00
* replace a BLOB entry with another
2009-01-21 19:23:37 +01:00
* @param key the primary key
* @throws IOException
2011-07-15 10:38:10 +02:00
* @throws RowSpaceExceededException
2009-01-21 19:23:37 +01:00
* /
2012-01-02 02:14:05 +01:00
@Override
2011-07-15 10:38:10 +02:00
public synchronized int replace ( final byte [ ] key , final Rewriter rewriter ) throws IOException , RowSpaceExceededException {
2009-01-21 19:23:37 +01:00
int d = 0 ;
2011-07-15 10:38:10 +02:00
for ( final blobItem bi : this . blobs ) {
2009-01-21 19:23:37 +01:00
d + = bi . blob . replace ( key , rewriter ) ;
}
return d ;
}
2011-07-15 10:38:10 +02:00
2010-08-04 15:33:12 +02:00
/ * *
* replace a BLOB entry with another which must be smaller or same size
* @param key the primary key
* @throws IOException
2011-07-15 10:38:10 +02:00
* @throws RowSpaceExceededException
2010-08-04 15:33:12 +02:00
* /
2012-01-02 02:14:05 +01:00
@Override
2011-07-15 10:38:10 +02:00
public synchronized int reduce ( final byte [ ] key , final Reducer reduce ) throws IOException , RowSpaceExceededException {
2010-08-04 15:33:12 +02:00
int d = 0 ;
2011-07-15 10:38:10 +02:00
for ( final blobItem bi : this . blobs ) {
2010-08-04 15:33:12 +02:00
d + = bi . blob . reduce ( key , reduce ) ;
}
return d ;
}
2011-07-15 10:38:10 +02:00
2008-08-19 16:10:40 +02:00
/ * *
2011-02-25 12:58:01 +01:00
* delete a BLOB
* @param key the primary key
2008-08-19 16:10:40 +02:00
* @throws IOException
* /
2012-01-02 02:14:05 +01:00
@Override
2011-02-25 12:58:01 +01:00
public synchronized void delete ( final byte [ ] key ) throws IOException {
2011-07-15 10:38:10 +02:00
final long m = mem ( ) ;
2012-07-10 22:59:03 +02:00
if ( this . blobs . isEmpty ( ) ) {
2011-02-25 12:58:01 +01:00
// do nothing
2011-07-15 10:38:10 +02:00
} else if ( this . blobs . size ( ) = = 1 ) {
final blobItem bi = this . blobs . get ( 0 ) ;
2011-02-25 12:58:01 +01:00
bi . blob . delete ( key ) ;
} else {
2012-01-16 01:05:30 +01:00
@SuppressWarnings ( " unchecked " )
final FutureTask < Boolean > [ ] t = new FutureTask [ this . blobs . size ( ) - 1 ] ;
2011-02-25 12:58:01 +01:00
int i = 0 ;
2011-07-15 10:38:10 +02:00
for ( final blobItem bi : this . blobs ) {
2011-03-03 12:30:04 +01:00
if ( i < t . length ) {
// run this in a concurrent thread
final blobItem bi0 = bi ;
2012-01-16 01:05:30 +01:00
t [ i ] = new FutureTask < Boolean > ( new Callable < Boolean > ( ) {
2012-01-02 02:14:05 +01:00
@Override
2012-01-16 01:05:30 +01:00
public Boolean call ( ) {
2011-07-15 10:38:10 +02:00
try { bi0 . blob . delete ( key ) ; } catch ( final IOException e ) { }
2012-01-16 01:05:30 +01:00
return true ;
2011-03-03 12:30:04 +01:00
}
2012-01-16 01:05:30 +01:00
} ) ;
DELETE_EXECUTOR . execute ( t [ i ] ) ;
2011-03-03 12:30:04 +01:00
} else {
// no additional thread, run in this thread
2011-07-15 10:38:10 +02:00
try { bi . blob . delete ( key ) ; } catch ( final IOException e ) { }
2011-03-03 12:30:04 +01:00
}
2011-02-25 12:58:01 +01:00
i + + ;
}
2012-01-16 01:05:30 +01:00
// wait for termination
for ( final FutureTask < Boolean > s : t ) try { s . get ( ) ; } catch ( final InterruptedException e ) { } catch ( ExecutionException e ) { }
2011-02-25 12:58:01 +01:00
}
2011-07-15 10:38:10 +02:00
assert mem ( ) < = m : " m = " + m + " , mem() = " + mem ( ) ;
2008-08-19 16:10:40 +02:00
}
2011-07-15 10:38:10 +02:00
2012-01-16 01:05:30 +01:00
private static final ExecutorService DELETE_EXECUTOR = Executors . newFixedThreadPool ( 128 ) ;
2008-08-19 16:10:40 +02:00
/ * *
* close the BLOB
* /
2012-01-02 02:14:05 +01:00
@Override
2011-07-15 10:38:10 +02:00
public synchronized void close ( final boolean writeIDX ) {
for ( final blobItem bi : this . blobs ) bi . blob . close ( writeIDX ) ;
this . blobs . clear ( ) ;
this . blobs = null ;
2008-08-19 16:10:40 +02:00
}
2011-07-15 10:38:10 +02:00
2010-01-04 19:12:03 +01:00
/ * *
* merge two blob files into one . If the second file is given as null ,
* then the first file is only rewritten into a new one .
* @param f1
* @param f2 ( may also be null )
* @param factory
* @param payloadrow
* @param newFile
* @param writeBuffer
* @return the target file where the given files are merged in
* /
2011-07-15 10:38:10 +02:00
public File mergeMount ( final File f1 , final File f2 ,
final ReferenceFactory < ? extends Reference > factory ,
final File newFile , final int writeBuffer ) {
2010-01-04 19:12:03 +01:00
if ( f2 = = null ) {
// this is a rewrite
Log . logInfo ( " BLOBArray " , " rewrite of " + f1 . getName ( ) ) ;
2011-07-15 10:38:10 +02:00
final File resultFile = rewriteWorker ( factory , this . keylength , this . ordering , f1 , newFile , writeBuffer ) ;
2010-01-04 19:12:03 +01:00
if ( resultFile = = null ) {
Log . logWarning ( " BLOBArray " , " rewrite of file " + f1 + " returned null. newFile = " + newFile ) ;
return null ;
}
try {
mountBLOB ( resultFile , false ) ;
2011-07-15 10:38:10 +02:00
} catch ( final IOException e ) {
2010-01-04 19:12:03 +01:00
Log . logWarning ( " BLOBArray " , " rewrite of file " + f1 + " successfull, but read failed. resultFile = " + resultFile ) ;
return null ;
}
Log . logInfo ( " BLOBArray " , " rewrite of " + f1 . getName ( ) + " into " + resultFile ) ;
return resultFile ;
2009-12-10 00:27:26 +01:00
}
2012-07-05 08:44:39 +02:00
Log . logInfo ( " BLOBArray " , " merging " + f1 . getName ( ) + " with " + f2 . getName ( ) ) ;
final File resultFile = mergeWorker ( factory , this . keylength , this . ordering , f1 , f2 , newFile , writeBuffer ) ;
if ( resultFile = = null ) {
Log . logWarning ( " BLOBArray " , " merge of files " + f1 + " , " + f2 + " returned null. newFile = " + newFile ) ;
return null ;
}
try {
mountBLOB ( resultFile , false ) ;
} catch ( final IOException e ) {
Log . logWarning ( " BLOBArray " , " merge of files " + f1 + " , " + f2 + " successfull, but read failed. resultFile = " + resultFile ) ;
return null ;
}
Log . logInfo ( " BLOBArray " , " merged " + f1 . getName ( ) + " with " + f2 . getName ( ) + " into " + resultFile ) ;
return resultFile ;
2009-04-02 15:26:47 +02:00
}
2011-07-15 10:38:10 +02:00
2009-12-10 00:27:26 +01:00
private static < ReferenceType extends Reference > File mergeWorker (
2011-12-01 21:57:22 +01:00
final ReferenceFactory < ReferenceType > factory ,
final int keylength , final ByteOrder order , final File f1 , final File f2 , final File newFile , final int writeBuffer ) {
2009-04-02 15:26:47 +02:00
// iterate both files and write a new one
2011-12-01 21:57:22 +01:00
ReferenceIterator < ReferenceType > i1 = null ;
2009-12-10 00:27:26 +01:00
try {
2011-05-18 16:26:28 +02:00
i1 = new ReferenceIterator < ReferenceType > ( f1 , factory ) ;
2011-12-01 21:57:22 +01:00
ReferenceIterator < ReferenceType > i2 = null ;
try {
i2 = new ReferenceIterator < ReferenceType > ( f2 , factory ) ;
if ( ! i1 . hasNext ( ) ) {
if ( i2 . hasNext ( ) ) {
HeapWriter . delete ( f1 ) ;
2011-11-30 12:15:54 +01:00
if ( f2 . renameTo ( newFile ) ) return newFile ;
2011-12-01 21:57:22 +01:00
return f2 ;
}
HeapWriter . delete ( f1 ) ;
HeapWriter . delete ( f2 ) ;
return null ;
} else if ( ! i2 . hasNext ( ) ) {
HeapWriter . delete ( f2 ) ;
2011-11-30 12:15:54 +01:00
if ( f1 . renameTo ( newFile ) ) return newFile ;
2011-12-01 21:57:22 +01:00
return f1 ;
}
assert i1 . hasNext ( ) ;
assert i2 . hasNext ( ) ;
final File tmpFile = new File ( newFile . getParentFile ( ) , newFile . getName ( ) + " .prt " ) ;
try {
final HeapWriter writer = new HeapWriter ( tmpFile , newFile , keylength , order , writeBuffer ) ;
merge ( i1 , i2 , order , writer ) ;
writer . close ( true ) ;
} catch ( final IOException e ) {
Log . logSevere ( " ArrayStack " , " cannot writing or close writing merge, newFile = " + newFile . toString ( ) + " , tmpFile = " + tmpFile . toString ( ) + " : " + e . getMessage ( ) , e ) ;
HeapWriter . delete ( tmpFile ) ;
HeapWriter . delete ( newFile ) ;
return null ;
} catch ( final RowSpaceExceededException e ) {
Log . logSevere ( " ArrayStack " , " cannot merge because of memory failure: " + e . getMessage ( ) , e ) ;
HeapWriter . delete ( tmpFile ) ;
HeapWriter . delete ( newFile ) ;
return null ;
}
// we don't need the old files any more
2011-03-17 18:09:19 +01:00
HeapWriter . delete ( f1 ) ;
2011-12-01 21:57:22 +01:00
HeapWriter . delete ( f2 ) ;
return newFile ;
} catch ( final IOException e ) {
Log . logSevere ( " ArrayStack " , " cannot merge because input files cannot be read, f2 = " + f2 . toString ( ) + " : " + e . getMessage ( ) , e ) ;
return null ;
} finally {
if ( i2 ! = null ) i2 . close ( ) ;
2009-04-02 15:26:47 +02:00
}
2011-07-15 10:38:10 +02:00
} catch ( final IOException e ) {
2011-12-01 21:57:22 +01:00
Log . logSevere ( " ArrayStack " , " cannot merge because input files cannot be read, f1 = " + f1 . toString ( ) + " : " + e . getMessage ( ) , e ) ;
2009-04-02 15:26:47 +02:00
return null ;
2011-12-01 21:57:22 +01:00
} finally {
if ( i1 ! = null ) i1 . close ( ) ;
2009-04-02 15:26:47 +02:00
}
}
2011-07-15 10:38:10 +02:00
2010-01-04 19:12:03 +01:00
private static < ReferenceType extends Reference > File rewriteWorker (
2011-07-15 10:38:10 +02:00
final ReferenceFactory < ReferenceType > factory ,
final int keylength , final ByteOrder order , final File f , final File newFile , final int writeBuffer ) {
2010-01-04 19:12:03 +01:00
// iterate both files and write a new one
2011-07-15 10:38:10 +02:00
2010-01-04 19:12:03 +01:00
CloneableIterator < ReferenceContainer < ReferenceType > > i = null ;
try {
2011-05-18 16:26:28 +02:00
i = new ReferenceIterator < ReferenceType > ( f , factory ) ;
2011-07-15 10:38:10 +02:00
} catch ( final IOException e ) {
2010-01-04 19:12:03 +01:00
Log . logSevere ( " ArrayStack " , " cannot rewrite because input file cannot be read, f = " + f . toString ( ) + " : " + e . getMessage ( ) , e ) ;
return null ;
}
if ( ! i . hasNext ( ) ) {
FileUtils . deletedelete ( f ) ;
return null ;
}
assert i . hasNext ( ) ;
2011-07-15 10:38:10 +02:00
final File tmpFile = new File ( newFile . getParentFile ( ) , newFile . getName ( ) + " .prt " ) ;
2010-01-04 19:12:03 +01:00
try {
2011-07-15 10:38:10 +02:00
final HeapWriter writer = new HeapWriter ( tmpFile , newFile , keylength , order , writeBuffer ) ;
2010-01-04 19:12:03 +01:00
rewrite ( i , order , writer ) ;
writer . close ( true ) ;
2012-07-02 15:40:40 +02:00
i . close ( ) ;
2011-07-15 10:38:10 +02:00
} catch ( final IOException e ) {
2010-01-04 19:12:03 +01:00
Log . logSevere ( " ArrayStack " , " cannot writing or close writing rewrite, newFile = " + newFile . toString ( ) + " , tmpFile = " + tmpFile . toString ( ) + " : " + e . getMessage ( ) , e ) ;
FileUtils . deletedelete ( tmpFile ) ;
FileUtils . deletedelete ( newFile ) ;
return null ;
2011-07-15 10:38:10 +02:00
} catch ( final RowSpaceExceededException e ) {
2010-01-04 19:12:03 +01:00
Log . logSevere ( " ArrayStack " , " cannot rewrite because of memory failure: " + e . getMessage ( ) , e ) ;
FileUtils . deletedelete ( tmpFile ) ;
FileUtils . deletedelete ( newFile ) ;
return null ;
}
// we don't need the old files any more
FileUtils . deletedelete ( f ) ;
return newFile ;
}
2011-07-15 10:38:10 +02:00
2009-12-10 00:27:26 +01:00
private static < ReferenceType extends Reference > void merge (
2011-07-15 10:38:10 +02:00
final CloneableIterator < ReferenceContainer < ReferenceType > > i1 ,
final CloneableIterator < ReferenceContainer < ReferenceType > > i2 ,
final ByteOrder ordering , final HeapWriter writer ) throws IOException , RowSpaceExceededException {
2009-04-02 15:26:47 +02:00
assert i1 . hasNext ( ) ;
assert i2 . hasNext ( ) ;
2010-11-12 17:02:20 +01:00
byte [ ] c1lh , c2lh ;
ReferenceContainer < ReferenceType > c1 , c2 ;
2009-04-02 15:26:47 +02:00
c1 = i1 . next ( ) ;
c2 = i2 . next ( ) ;
2011-09-08 00:15:01 +02:00
int e , s ;
2009-04-02 15:26:47 +02:00
while ( true ) {
assert c1 ! = null ;
assert c2 ! = null ;
2009-04-16 17:29:00 +02:00
e = ordering . compare ( c1 . getTermHash ( ) , c2 . getTermHash ( ) ) ;
2009-04-02 15:26:47 +02:00
if ( e < 0 ) {
2011-09-08 00:15:01 +02:00
s = c1 . shrinkReferences ( ) ;
if ( s > 0 ) Log . logInfo ( " ArrayStack " , " shrinking index for " + ASCII . String ( c1 . getTermHash ( ) ) + " by " + s + " to " + c1 . size ( ) + " entries " ) ;
2009-04-16 17:29:00 +02:00
writer . add ( c1 . getTermHash ( ) , c1 . exportCollection ( ) ) ;
2009-04-02 15:26:47 +02:00
if ( i1 . hasNext ( ) ) {
2010-11-12 17:02:20 +01:00
c1lh = c1 . getTermHash ( ) ;
2009-04-02 15:26:47 +02:00
c1 = i1 . next ( ) ;
2010-11-12 17:02:20 +01:00
assert ordering . compare ( c1 . getTermHash ( ) , c1lh ) > 0 ;
2009-04-02 15:26:47 +02:00
continue ;
}
2010-01-05 02:34:46 +01:00
c1 = null ;
2009-04-02 15:26:47 +02:00
break ;
}
if ( e > 0 ) {
2011-09-08 00:15:01 +02:00
s = c2 . shrinkReferences ( ) ;
if ( s > 0 ) Log . logInfo ( " ArrayStack " , " shrinking index for " + ASCII . String ( c2 . getTermHash ( ) ) + " by " + s + " to " + c2 . size ( ) + " entries " ) ;
2009-04-16 17:29:00 +02:00
writer . add ( c2 . getTermHash ( ) , c2 . exportCollection ( ) ) ;
2009-04-02 15:26:47 +02:00
if ( i2 . hasNext ( ) ) {
2010-11-12 17:02:20 +01:00
c2lh = c2 . getTermHash ( ) ;
2009-04-02 15:26:47 +02:00
c2 = i2 . next ( ) ;
2010-11-12 17:02:20 +01:00
assert ordering . compare ( c2 . getTermHash ( ) , c2lh ) > 0 ;
2009-04-02 15:26:47 +02:00
continue ;
}
2010-01-05 02:34:46 +01:00
c2 = null ;
2009-04-02 15:26:47 +02:00
break ;
}
assert e = = 0 ;
// merge the entries
2010-11-12 17:02:20 +01:00
c1 = c1 . merge ( c2 ) ;
2011-09-08 00:15:01 +02:00
s = c1 . shrinkReferences ( ) ;
if ( s > 0 ) Log . logInfo ( " ArrayStack " , " shrinking index for " + ASCII . String ( c1 . getTermHash ( ) ) + " by " + s + " to " + c1 . size ( ) + " entries " ) ;
2010-11-12 17:02:20 +01:00
writer . add ( c1 . getTermHash ( ) , c1 . exportCollection ( ) ) ;
c1lh = c1 . getTermHash ( ) ;
c2lh = c2 . getTermHash ( ) ;
2009-04-02 15:26:47 +02:00
if ( i1 . hasNext ( ) & & i2 . hasNext ( ) ) {
c1 = i1 . next ( ) ;
2010-11-12 17:02:20 +01:00
assert ordering . compare ( c1 . getTermHash ( ) , c1lh ) > 0 ;
2009-04-02 15:26:47 +02:00
c2 = i2 . next ( ) ;
2010-11-12 17:02:20 +01:00
assert ordering . compare ( c2 . getTermHash ( ) , c2lh ) > 0 ;
2009-04-02 15:26:47 +02:00
continue ;
}
2010-01-05 02:34:46 +01:00
c1 = null ;
c2 = null ;
if ( i1 . hasNext ( ) ) {
c1 = i1 . next ( ) ;
2010-11-12 17:02:20 +01:00
assert ordering . compare ( c1 . getTermHash ( ) , c1lh ) > 0 ;
2010-01-05 02:34:46 +01:00
}
if ( i2 . hasNext ( ) ) {
c2 = i2 . next ( ) ;
2010-11-12 17:02:20 +01:00
assert ordering . compare ( c2 . getTermHash ( ) , c2lh ) > 0 ;
2010-01-05 02:34:46 +01:00
}
2009-04-02 15:26:47 +02:00
break ;
2011-07-15 10:38:10 +02:00
2009-04-02 15:26:47 +02:00
}
// catch up remaining entries
assert ! ( i1 . hasNext ( ) & & i2 . hasNext ( ) ) ;
2010-01-05 02:34:46 +01:00
assert ( c1 = = null ) | | ( c2 = = null ) ;
while ( c1 ! = null ) {
2009-04-02 15:26:47 +02:00
//System.out.println("FLUSH REMAINING 1: " + c1.getWordHash());
2011-09-08 00:15:01 +02:00
s = c1 . shrinkReferences ( ) ;
if ( s > 0 ) Log . logInfo ( " ArrayStack " , " shrinking index for " + ASCII . String ( c1 . getTermHash ( ) ) + " by " + s + " to " + c1 . size ( ) + " entries " ) ;
2009-04-16 17:29:00 +02:00
writer . add ( c1 . getTermHash ( ) , c1 . exportCollection ( ) ) ;
2009-04-02 15:26:47 +02:00
if ( i1 . hasNext ( ) ) {
2010-11-12 17:02:20 +01:00
c1lh = c1 . getTermHash ( ) ;
2009-04-02 15:26:47 +02:00
c1 = i1 . next ( ) ;
2010-11-12 17:02:20 +01:00
assert ordering . compare ( c1 . getTermHash ( ) , c1lh ) > 0 ;
2010-01-05 02:34:46 +01:00
} else {
c1 = null ;
2009-04-02 15:26:47 +02:00
}
}
2010-01-05 02:34:46 +01:00
while ( c2 ! = null ) {
2009-04-02 15:26:47 +02:00
//System.out.println("FLUSH REMAINING 2: " + c2.getWordHash());
2011-09-08 00:15:01 +02:00
s = c2 . shrinkReferences ( ) ;
if ( s > 0 ) Log . logInfo ( " ArrayStack " , " shrinking index for " + ASCII . String ( c2 . getTermHash ( ) ) + " by " + s + " to " + c2 . size ( ) + " entries " ) ;
2009-04-16 17:29:00 +02:00
writer . add ( c2 . getTermHash ( ) , c2 . exportCollection ( ) ) ;
2009-04-02 15:26:47 +02:00
if ( i2 . hasNext ( ) ) {
2010-11-12 17:02:20 +01:00
c2lh = c2 . getTermHash ( ) ;
2009-04-02 15:26:47 +02:00
c2 = i2 . next ( ) ;
2010-11-12 17:02:20 +01:00
assert ordering . compare ( c2 . getTermHash ( ) , c2lh ) > 0 ;
2010-01-05 02:34:46 +01:00
} else {
c2 = null ;
2009-04-02 15:26:47 +02:00
}
}
// finished with writing
}
2011-07-15 10:38:10 +02:00
2010-01-04 19:12:03 +01:00
private static < ReferenceType extends Reference > void rewrite (
2011-07-15 10:38:10 +02:00
final CloneableIterator < ReferenceContainer < ReferenceType > > i ,
final ByteOrder ordering , final HeapWriter writer ) throws IOException , RowSpaceExceededException {
2010-01-04 19:12:03 +01:00
assert i . hasNext ( ) ;
2010-11-12 17:02:20 +01:00
byte [ ] clh ;
ReferenceContainer < ReferenceType > c ;
2010-01-04 19:12:03 +01:00
c = i . next ( ) ;
2011-09-08 00:15:01 +02:00
int s ;
2010-01-04 19:12:03 +01:00
while ( true ) {
assert c ! = null ;
2011-09-08 00:15:01 +02:00
s = c . shrinkReferences ( ) ;
if ( s > 0 ) Log . logInfo ( " ArrayStack " , " shrinking index for " + ASCII . String ( c . getTermHash ( ) ) + " by " + s + " to " + c . size ( ) + " entries " ) ;
2010-01-04 19:12:03 +01:00
writer . add ( c . getTermHash ( ) , c . exportCollection ( ) ) ;
if ( i . hasNext ( ) ) {
2010-11-12 17:02:20 +01:00
clh = c . getTermHash ( ) ;
2010-01-04 19:12:03 +01:00
c = i . next ( ) ;
2010-11-12 17:02:20 +01:00
assert ordering . compare ( c . getTermHash ( ) , clh ) > 0 ;
2010-01-04 19:12:03 +01:00
continue ;
}
break ;
}
// finished with writing
}
2008-10-16 23:24:09 +02:00
public static void main ( final String [ ] args ) {
final File f = new File ( " /Users/admin/blobarraytest " ) ;
try {
//f.delete();
2011-05-18 16:26:28 +02:00
final ArrayStack heap = new ArrayStack ( f , " test " , NaturalOrder . naturalOrder , 12 , 512 * 1024 , false ) ;
2010-08-24 14:36:56 +02:00
heap . insert ( " aaaaaaaaaaaa " . getBytes ( ) , " eins zwei drei " . getBytes ( ) ) ;
heap . insert ( " aaaaaaaaaaab " . getBytes ( ) , " vier fuenf sechs " . getBytes ( ) ) ;
heap . insert ( " aaaaaaaaaaac " . getBytes ( ) , " sieben acht neun " . getBytes ( ) ) ;
heap . insert ( " aaaaaaaaaaad " . getBytes ( ) , " zehn elf zwoelf " . getBytes ( ) ) ;
2008-10-16 23:24:09 +02:00
// iterate over keys
2011-07-15 10:38:10 +02:00
final Iterator < byte [ ] > i = heap . keys ( true , false ) ;
2008-10-16 23:24:09 +02:00
while ( i . hasNext ( ) ) {
2011-03-07 21:36:40 +01:00
System . out . println ( " key_b: " + UTF8 . String ( i . next ( ) ) ) ;
2008-10-16 23:24:09 +02:00
}
2010-08-24 14:36:56 +02:00
heap . delete ( " aaaaaaaaaaab " . getBytes ( ) ) ;
heap . delete ( " aaaaaaaaaaac " . getBytes ( ) ) ;
heap . insert ( " aaaaaaaaaaaX " . getBytes ( ) , " WXYZ " . getBytes ( ) ) ;
2009-03-18 17:14:31 +01:00
heap . close ( true ) ;
2008-10-16 23:24:09 +02:00
} catch ( final IOException e ) {
2009-11-05 21:28:37 +01:00
Log . logException ( e ) ;
2008-10-16 23:24:09 +02:00
}
}
2011-07-15 10:38:10 +02:00
2008-08-19 16:10:40 +02:00
}