2011-04-08 23:15:10 +02:00
// WorkTables.java
2010-02-04 12:26:23 +01:00
// (C) 2010 by Michael Peter Christen; mc@yacy.net, Frankfurt a. M., Germany
// first published 04.02.2010 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$
// $LastChangedRevision$
// $LastChangedBy$
2010-02-04 12:26:23 +01:00
//
// LICENSE
2012-07-27 12:13:53 +02:00
//
2010-02-04 12:26:23 +01: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
2012-09-21 15:48:16 +02:00
package net.yacy.data ;
2010-02-04 12:26:23 +01:00
import java.io.File ;
import java.io.IOException ;
2010-08-19 14:13:54 +02:00
import java.util.ArrayList ;
import java.util.Collection ;
2010-02-04 12:26:23 +01:00
import java.util.Date ;
2011-01-03 21:52:54 +01:00
import java.util.Iterator ;
2010-08-19 14:13:54 +02:00
import java.util.LinkedHashMap ;
import java.util.Map ;
2011-01-03 21:52:54 +01:00
import java.util.TreeMap ;
2010-02-04 12:26:23 +01:00
2011-01-03 21:52:54 +01:00
import net.yacy.cora.date.GenericFormatter ;
2012-08-26 17:46:40 +02:00
import net.yacy.cora.document.ASCII ;
2011-03-07 21:36:40 +01:00
import net.yacy.cora.document.UTF8 ;
2012-09-21 16:46:57 +02:00
import net.yacy.cora.order.Base64Order ;
2010-08-23 00:32:39 +02:00
import net.yacy.cora.protocol.http.HTTPClient ;
2012-07-27 12:13:53 +02:00
import net.yacy.cora.storage.HandleSet ;
import net.yacy.cora.util.SpaceExceededException ;
2012-09-21 15:48:16 +02:00
import net.yacy.data.ymark.YMarkTables ;
2010-02-04 12:26:23 +01:00
import net.yacy.kelondro.blob.Tables ;
2010-12-06 15:34:58 +01:00
import net.yacy.kelondro.data.meta.DigestURI ;
import net.yacy.kelondro.data.word.WordReference ;
2010-02-04 12:26:23 +01:00
import net.yacy.kelondro.logging.Log ;
2010-12-06 15:34:58 +01:00
import net.yacy.kelondro.rwi.IndexCell ;
2011-09-25 18:59:06 +02:00
import net.yacy.search.Switchboard ;
2012-09-21 15:48:16 +02:00
import net.yacy.server.serverObjects ;
2010-02-04 12:26:23 +01:00
public class WorkTables extends Tables {
2012-07-27 12:13:53 +02:00
2010-02-04 12:26:23 +01:00
public final static String TABLE_API_NAME = " api " ;
public final static String TABLE_API_TYPE_STEERING = " steering " ;
public final static String TABLE_API_TYPE_CONFIGURATION = " configuration " ;
public final static String TABLE_API_TYPE_CRAWLER = " crawler " ;
2012-07-27 12:13:53 +02:00
2010-02-04 12:26:23 +01:00
public final static String TABLE_API_COL_TYPE = " type " ;
public final static String TABLE_API_COL_COMMENT = " comment " ;
2010-08-18 17:56:38 +02:00
public final static String TABLE_API_COL_DATE_RECORDING = " date_recording " ; // if not present default to old date field
public final static String TABLE_API_COL_DATE_LAST_EXEC = " date_last_exec " ; // if not present default to old date field
public final static String TABLE_API_COL_DATE_NEXT_EXEC = " date_next_exec " ; // if not present default to zero
public final static String TABLE_API_COL_DATE = " date " ; // old date; do not set in new records
2010-02-04 12:26:23 +01:00
public final static String TABLE_API_COL_URL = " url " ;
2010-08-18 17:56:38 +02:00
public final static String TABLE_API_COL_APICALL_PK = " apicall_pk " ; // the primary key for the table entry of that api call (not really a database field, only a name in the apicall)
public final static String TABLE_API_COL_APICALL_COUNT = " apicall_count " ; // counts how often the API was called (starts with 1)
public final static String TABLE_API_COL_APICALL_SCHEDULE_TIME = " apicall_schedule_time " ; // factor for SCHEULE_UNIT time units
public final static String TABLE_API_COL_APICALL_SCHEDULE_UNIT = " apicall_schedule_unit " ; // may be 'minutes', 'hours', 'days'
2010-08-31 17:47:47 +02:00
2010-03-04 12:58:07 +01:00
public final static String TABLE_ROBOTS_NAME = " robots " ;
2012-07-27 12:13:53 +02:00
2010-08-31 17:47:47 +02:00
public final static String TABLE_ACTIVECRAWLS_NAME = " crawljobsActive " ;
public final static String TABLE_PASSIVECRAWLS_NAME = " crawljobsPassive " ;
2010-12-06 15:34:58 +01:00
public final static String TABLE_SEARCH_FAILURE_NAME = " searchfl " ;
public final static String TABLE_SEARCH_FAILURE_COL_URL = " url " ;
public final static String TABLE_SEARCH_FAILURE_COL_DATE = " date " ;
public final static String TABLE_SEARCH_FAILURE_COL_WORDS = " words " ;
public final static String TABLE_SEARCH_FAILURE_COL_COMMENT = " comment " ;
2012-07-27 12:13:53 +02:00
2010-10-18 23:09:41 +02:00
public YMarkTables bookmarks ;
2012-07-27 12:13:53 +02:00
2010-04-13 03:16:09 +02:00
public WorkTables ( final File workPath ) {
2010-02-04 12:26:23 +01:00
super ( workPath , 12 ) ;
2010-10-18 23:09:41 +02:00
this . bookmarks = new YMarkTables ( this ) ;
2010-02-04 12:26:23 +01:00
}
2012-07-27 12:13:53 +02:00
2010-08-19 14:13:54 +02:00
/ * *
* recording of a api call . stores the call parameters into the API database table
* @param post the post arguments of the api call
* @param servletName the name of the servlet
* @param type name of the servlet category
* @param comment visual description of the process
2010-08-26 18:01:45 +02:00
* @return the pk of the new entry in the api table
2010-08-19 14:13:54 +02:00
* /
2010-08-26 18:01:45 +02:00
public byte [ ] recordAPICall ( final serverObjects post , final String servletName , final String type , final String comment ) {
2010-08-18 17:56:38 +02:00
// remove the apicall attributes from the post object
2010-08-26 18:01:45 +02:00
String pks = post . remove ( TABLE_API_COL_APICALL_PK ) ;
2011-04-12 07:02:36 +02:00
byte [ ] pk = pks = = null ? null : UTF8 . getBytes ( pks ) ;
2012-07-27 12:13:53 +02:00
2010-08-18 17:56:38 +02:00
// generate the apicall url - without the apicall attributes
2011-01-28 11:54:13 +01:00
final String apiurl = /*"http://localhost:" + getConfig("port", "8090") +*/ " / " + servletName + " ? " + post . toString ( ) ;
2010-08-18 17:56:38 +02:00
// read old entry from the apicall table (if exists)
Row row = null ;
2010-02-04 12:26:23 +01:00
try {
2010-08-26 18:01:45 +02:00
row = ( pk = = null ) ? null : super . select ( TABLE_API_NAME , pk ) ;
2010-08-18 17:56:38 +02:00
} catch ( IOException e ) {
Log . logException ( e ) ;
2012-07-27 12:13:53 +02:00
} catch ( SpaceExceededException e ) {
2010-08-18 17:56:38 +02:00
Log . logException ( e ) ;
}
2012-07-27 12:13:53 +02:00
2010-08-18 17:56:38 +02:00
// insert or update entry
try {
2010-08-20 01:52:38 +02:00
if ( row = = null ) {
2010-08-18 17:56:38 +02:00
// create and insert new entry
Data data = new Data ( ) ;
2011-04-12 07:02:36 +02:00
data . put ( TABLE_API_COL_TYPE , UTF8 . getBytes ( type ) ) ;
data . put ( TABLE_API_COL_COMMENT , UTF8 . getBytes ( comment ) ) ;
byte [ ] date = UTF8 . getBytes ( GenericFormatter . SHORT_MILSEC_FORMATTER . format ( ) ) ;
2010-08-18 17:56:38 +02:00
data . put ( TABLE_API_COL_DATE_RECORDING , date ) ;
data . put ( TABLE_API_COL_DATE_LAST_EXEC , date ) ;
2011-04-12 07:02:36 +02:00
data . put ( TABLE_API_COL_URL , UTF8 . getBytes ( apiurl ) ) ;
2012-07-27 12:13:53 +02:00
// insert APICALL attributes
2010-08-20 01:52:38 +02:00
data . put ( TABLE_API_COL_APICALL_COUNT , " 1 " ) ;
2010-08-26 18:01:45 +02:00
pk = super . insert ( TABLE_API_NAME , data ) ;
2010-08-20 01:52:38 +02:00
} else {
// modify and update existing entry
// modify date attributes and patch old values
2011-04-12 07:02:36 +02:00
row . put ( TABLE_API_COL_DATE_LAST_EXEC , UTF8 . getBytes ( GenericFormatter . SHORT_MILSEC_FORMATTER . format ( ) ) ) ;
2010-08-20 01:52:38 +02:00
if ( ! row . containsKey ( TABLE_API_COL_DATE_RECORDING ) ) row . put ( TABLE_API_COL_DATE_RECORDING , row . get ( TABLE_API_COL_DATE ) ) ;
row . remove ( TABLE_API_COL_DATE ) ;
2012-07-27 12:13:53 +02:00
// insert APICALL attributes
2010-08-20 01:52:38 +02:00
row . put ( TABLE_API_COL_APICALL_COUNT , row . get ( TABLE_API_COL_APICALL_COUNT , 1 ) + 1 ) ;
super . update ( TABLE_API_NAME , row ) ;
2010-08-26 18:01:45 +02:00
assert pk ! = null ;
2010-08-18 17:56:38 +02:00
}
2010-02-04 12:26:23 +01:00
} catch ( IOException e ) {
Log . logException ( e ) ;
2012-07-27 12:13:53 +02:00
} catch ( SpaceExceededException e ) {
2010-06-15 21:44:05 +02:00
Log . logException ( e ) ;
2010-02-04 12:26:23 +01:00
}
Log . logInfo ( " APICALL " , apiurl ) ;
2010-08-26 18:01:45 +02:00
return pk ;
2010-02-04 12:26:23 +01:00
}
2012-07-27 12:13:53 +02:00
2010-08-20 01:52:38 +02:00
/ * *
* store a API call and set attributes to schedule a re - call of that API call according to a given frequence
* This is the same as the previous method but it also computes a re - call time and stores that additionally
* @param post the post arguments of the api call
* @param servletName the name of the servlet
* @param type name of the servlet category
* @param comment visual description of the process
* @param time the time until next scheduled execution of this api call
* @param unit the time unit for the scheduled call
2010-08-26 18:01:45 +02:00
* @return the pk of the new entry in the api table
2010-08-20 01:52:38 +02:00
* /
2010-08-26 18:01:45 +02:00
public byte [ ] recordAPICall ( final serverObjects post , final String servletName , final String type , final String comment , int time , String unit ) {
2010-08-20 01:52:38 +02:00
if ( post . containsKey ( TABLE_API_COL_APICALL_PK ) ) {
// this api call has already been stored somewhere.
2010-08-26 18:01:45 +02:00
return recordAPICall ( post , servletName , type , comment ) ;
2010-08-20 01:52:38 +02:00
}
2012-07-10 22:59:03 +02:00
if ( time < 0 | | unit = = null | | unit . isEmpty ( ) | | " minutes,hours,days " . indexOf ( unit ) < 0 ) {
2010-08-20 01:52:38 +02:00
time = 0 ; unit = " " ;
} else {
if ( unit . equals ( " minutes " ) & & time < 10 ) time = 10 ;
}
2012-07-27 12:13:53 +02:00
2010-08-20 01:52:38 +02:00
// generate the apicall url - without the apicall attributes
2011-01-28 11:54:13 +01:00
final String apiurl = /*"http://localhost:" + getConfig("port", "8090") +*/ " / " + servletName + " ? " + post . toString ( ) ;
2010-08-26 18:01:45 +02:00
byte [ ] pk = null ;
2010-08-20 01:52:38 +02:00
// insert entry
try {
// create and insert new entry
Data data = new Data ( ) ;
2011-04-12 07:02:36 +02:00
data . put ( TABLE_API_COL_TYPE , UTF8 . getBytes ( type ) ) ;
data . put ( TABLE_API_COL_COMMENT , UTF8 . getBytes ( comment ) ) ;
byte [ ] date = UTF8 . getBytes ( GenericFormatter . SHORT_MILSEC_FORMATTER . format ( ) ) ;
2010-08-20 01:52:38 +02:00
data . put ( TABLE_API_COL_DATE_RECORDING , date ) ;
data . put ( TABLE_API_COL_DATE_LAST_EXEC , date ) ;
2011-04-12 07:02:36 +02:00
data . put ( TABLE_API_COL_URL , UTF8 . getBytes ( apiurl ) ) ;
2012-07-27 12:13:53 +02:00
// insert APICALL attributes
2011-04-12 07:02:36 +02:00
data . put ( TABLE_API_COL_APICALL_COUNT , UTF8 . getBytes ( " 1 " ) ) ;
2012-08-26 17:46:40 +02:00
data . put ( TABLE_API_COL_APICALL_SCHEDULE_TIME , ASCII . getBytes ( Integer . toString ( time ) ) ) ;
2011-04-12 07:02:36 +02:00
data . put ( TABLE_API_COL_APICALL_SCHEDULE_UNIT , UTF8 . getBytes ( unit ) ) ;
2010-08-20 01:52:38 +02:00
calculateAPIScheduler ( data , false ) ; // set next execution time
2010-08-26 18:01:45 +02:00
pk = super . insert ( TABLE_API_NAME , data ) ;
2010-08-20 01:52:38 +02:00
} catch ( IOException e ) {
Log . logException ( e ) ;
2012-07-27 12:13:53 +02:00
} catch ( SpaceExceededException e ) {
2010-08-20 01:52:38 +02:00
Log . logException ( e ) ;
}
Log . logInfo ( " APICALL " , apiurl ) ;
2010-08-26 18:01:45 +02:00
return pk ;
2010-08-20 01:52:38 +02:00
}
2012-07-27 12:13:53 +02:00
2010-08-19 14:13:54 +02:00
/ * *
* execute an API call using a api table row which contains all essentials
* to access the server also the host , port and the authentication realm must be given
* @param pks a collection of primary keys denoting the rows in the api table
* @param host the host where the api shall be called
* @param port the port on the host
* @param realm authentification realm
* @return a map of the called urls and the http status code of the api call or - 1 if any other IOException occurred
* /
2010-09-28 14:18:54 +02:00
public Map < String , Integer > execAPICalls ( String host , int port , String realm , Collection < String > pks ) {
2010-08-19 14:13:54 +02:00
// now call the api URLs and store the result status
2010-08-23 00:32:39 +02:00
final HTTPClient client = new HTTPClient ( ) ;
2010-08-19 14:13:54 +02:00
client . setRealm ( realm ) ;
client . setTimout ( 120000 ) ;
2011-08-02 17:52:33 +02:00
Tables . Row row ;
String url ;
2010-08-19 14:13:54 +02:00
LinkedHashMap < String , Integer > l = new LinkedHashMap < String , Integer > ( ) ;
2011-08-02 17:52:33 +02:00
for ( final String pk : pks ) {
row = null ;
2010-08-19 14:13:54 +02:00
try {
2011-04-12 07:02:36 +02:00
row = select ( WorkTables . TABLE_API_NAME , UTF8 . getBytes ( pk ) ) ;
2010-08-19 14:13:54 +02:00
} catch ( IOException e ) {
Log . logException ( e ) ;
2012-07-27 12:13:53 +02:00
} catch ( SpaceExceededException e ) {
2010-08-19 14:13:54 +02:00
Log . logException ( e ) ;
}
if ( row = = null ) continue ;
2011-08-02 17:52:33 +02:00
url = " http:// " + host + " : " + port + UTF8 . String ( row . get ( WorkTables . TABLE_API_COL_URL ) ) ;
2011-03-07 21:36:40 +01:00
url + = " & " + WorkTables . TABLE_API_COL_APICALL_PK + " = " + UTF8 . String ( row . getPK ( ) ) ;
2011-03-09 13:50:39 +01:00
Log . logInfo ( " WorkTables " , " executing url: " + url ) ;
2010-08-19 14:13:54 +02:00
try {
client . GETbytes ( url ) ;
l . put ( url , client . getStatusCode ( ) ) ;
} catch ( IOException e ) {
Log . logException ( e ) ;
l . put ( url , - 1 ) ;
}
}
return l ;
}
2012-07-27 12:13:53 +02:00
2010-09-28 14:18:54 +02:00
public static int execAPICall ( String host , int port , String realm , String path , byte [ ] pk ) {
// now call the api URLs and store the result status
final HTTPClient client = new HTTPClient ( ) ;
client . setRealm ( realm ) ;
client . setTimout ( 120000 ) ;
String url = " http:// " + host + " : " + port + path ;
2011-03-07 21:36:40 +01:00
if ( pk ! = null ) url + = " & " + WorkTables . TABLE_API_COL_APICALL_PK + " = " + UTF8 . String ( pk ) ;
2010-09-28 14:18:54 +02:00
try {
client . GETbytes ( url ) ;
return client . getStatusCode ( ) ;
} catch ( IOException e ) {
Log . logException ( e ) ;
return - 1 ;
}
}
2012-07-27 12:13:53 +02:00
2010-08-19 14:13:54 +02:00
/ * *
* simplified call to execute a single entry in the api database table
* @param pk the primary key of the entry
* @param host the host where the api shall be called
* @param port the port on the host
* @param realm authentification realm
* @return the http status code of the api call or - 1 if any other IOException occurred
* /
public int execAPICall ( String pk , String host , int port , String realm ) {
ArrayList < String > pks = new ArrayList < String > ( ) ;
pks . add ( pk ) ;
2010-09-28 14:18:54 +02:00
Map < String , Integer > m = execAPICalls ( host , port , realm , pks ) ;
2010-08-19 14:13:54 +02:00
if ( m . isEmpty ( ) ) return - 1 ;
return m . values ( ) . iterator ( ) . next ( ) . intValue ( ) ;
}
/ * *
* calculate the execution time in a api call table based on given scheduling time and last execution time
* @param row the database row in the api table
2010-08-20 01:52:38 +02:00
* @param update if true then the next execution time is based on the latest computed execution time ; othervise it is based on the last execution time
2010-08-19 14:13:54 +02:00
* /
2010-08-20 01:52:38 +02:00
public static void calculateAPIScheduler ( Tables . Data row , boolean update ) {
2010-08-26 18:42:00 +02:00
Date date = row . containsKey ( WorkTables . TABLE_API_COL_DATE ) ? row . get ( WorkTables . TABLE_API_COL_DATE , ( Date ) null ) : null ;
2010-08-19 14:13:54 +02:00
date = update ? row . get ( WorkTables . TABLE_API_COL_DATE_NEXT_EXEC , date ) : row . get ( WorkTables . TABLE_API_COL_DATE_LAST_EXEC , date ) ;
int time = row . get ( WorkTables . TABLE_API_COL_APICALL_SCHEDULE_TIME , 1 ) ;
if ( time < = 0 ) {
2010-08-26 18:42:00 +02:00
row . put ( WorkTables . TABLE_API_COL_DATE_NEXT_EXEC , " " ) ;
2010-08-19 14:13:54 +02:00
return ;
}
String unit = row . get ( WorkTables . TABLE_API_COL_APICALL_SCHEDULE_UNIT , " days " ) ;
long d = date . getTime ( ) ;
2010-08-20 01:52:38 +02:00
if ( unit . equals ( " minutes " ) ) d + = 60000L * Math . max ( 10 , time ) ;
2010-08-19 14:13:54 +02:00
if ( unit . equals ( " hours " ) ) d + = 60000L * 60L * time ;
if ( unit . equals ( " days " ) ) d + = 60000L * 60L * 24L * time ;
if ( d < System . currentTimeMillis ( ) ) d = System . currentTimeMillis ( ) + 600000L ;
2010-08-20 01:52:38 +02:00
d - = d % 60000 ; // remove seconds
2010-08-19 14:13:54 +02:00
row . put ( WorkTables . TABLE_API_COL_DATE_NEXT_EXEC , new Date ( d ) ) ;
}
2012-07-27 12:13:53 +02:00
2010-12-06 15:34:58 +01:00
public void failURLsRegisterMissingWord ( IndexCell < WordReference > indexCell , final DigestURI url , HandleSet queryHashes , final String reason ) {
// remove words from index
2012-08-31 14:35:56 +02:00
if ( indexCell ! = null ) {
for ( final byte [ ] word : queryHashes ) {
indexCell . removeDelayed ( word , url . hash ( ) ) ;
}
2010-12-06 15:34:58 +01:00
}
2012-07-27 12:13:53 +02:00
2010-12-06 15:34:58 +01:00
// insert information about changed url into database
try {
// create and insert new entry
Data data = new Data ( ) ;
2011-04-12 07:02:36 +02:00
byte [ ] date = UTF8 . getBytes ( GenericFormatter . SHORT_MILSEC_FORMATTER . format ( ) ) ;
2012-10-10 11:46:22 +02:00
data . put ( TABLE_SEARCH_FAILURE_COL_URL , url . toNormalform ( true ) ) ;
2010-12-06 15:34:58 +01:00
data . put ( TABLE_SEARCH_FAILURE_COL_DATE , date ) ;
data . put ( TABLE_SEARCH_FAILURE_COL_WORDS , queryHashes . export ( ) ) ;
2011-04-12 07:02:36 +02:00
data . put ( TABLE_SEARCH_FAILURE_COL_COMMENT , UTF8 . getBytes ( reason ) ) ;
2010-12-06 15:34:58 +01:00
super . insert ( TABLE_SEARCH_FAILURE_NAME , url . hash ( ) , data ) ;
} catch ( IOException e ) {
Log . logException ( e ) ;
}
}
2012-07-27 12:13:53 +02:00
2010-12-06 15:34:58 +01:00
public boolean failURLsContains ( byte [ ] urlhash ) {
try {
return super . has ( TABLE_SEARCH_FAILURE_NAME , urlhash ) ;
} catch ( IOException e ) {
Log . logException ( e ) ;
return false ;
}
}
2012-07-27 12:13:53 +02:00
2011-01-17 16:04:00 +01:00
/ * *
* cleanup cached failed searchs older then timeout
* /
public void cleanFailURLS ( long timeout ) {
if ( timeout > = 0 ) {
try {
2011-08-02 17:52:33 +02:00
Row row ;
Date date ;
Iterator < Row > iter = this . iterator ( WorkTables . TABLE_SEARCH_FAILURE_NAME ) ;
2011-01-17 16:04:00 +01:00
while ( iter . hasNext ( ) ) {
2011-08-02 17:52:33 +02:00
row = iter . next ( ) ;
date = new Date ( ) ;
2011-01-17 16:04:00 +01:00
date = row . get ( TABLE_SEARCH_FAILURE_COL_DATE , date ) ;
if ( date . before ( new Date ( System . currentTimeMillis ( ) - timeout ) ) ) {
this . delete ( TABLE_SEARCH_FAILURE_NAME , row . getPK ( ) ) ;
}
}
} catch ( IOException e ) {
Log . logException ( e ) ;
}
}
}
2012-07-27 12:13:53 +02:00
2011-01-03 21:52:54 +01:00
public static Map < byte [ ] , String > commentCache ( Switchboard sb ) {
Map < byte [ ] , String > comments = new TreeMap < byte [ ] , String > ( Base64Order . enhancedCoder ) ;
Iterator < Tables . Row > i ;
try {
i = sb . tables . iterator ( WorkTables . TABLE_API_NAME ) ;
Tables . Row row ;
while ( i . hasNext ( ) ) {
row = i . next ( ) ;
2011-03-07 21:36:40 +01:00
comments . put ( row . getPK ( ) , UTF8 . String ( row . get ( WorkTables . TABLE_API_COL_COMMENT ) ) ) ;
2011-01-03 21:52:54 +01:00
}
} catch ( IOException e ) {
Log . logException ( e ) ;
}
return comments ;
}
2010-02-04 12:26:23 +01:00
}