/* * Created on Aug 6, 2008 * Created by Paul Gardner * * Copyright 2008 Vuze, Inc. All rights reserved. * * 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; version 2 of the License only. * * 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. */ package com.aelitis.azureus.core.subs.impl; import java.util.*; import org.gudy.azureus2.core3.download.DownloadManager; import org.gudy.azureus2.core3.global.GlobalManager; import org.gudy.azureus2.core3.util.Base32; import org.gudy.azureus2.core3.util.ByteArrayHashMap; import org.gudy.azureus2.core3.util.Debug; import org.gudy.azureus2.core3.util.HashWrapper; import org.gudy.azureus2.core3.util.SystemTime; import com.aelitis.azureus.core.AzureusCoreFactory; import com.aelitis.azureus.core.metasearch.Engine; import com.aelitis.azureus.core.subs.SubscriptionHistory; import com.aelitis.azureus.core.subs.SubscriptionResult; public class SubscriptionHistoryImpl implements SubscriptionHistory { private final SubscriptionManagerImpl manager; private final SubscriptionImpl subs; private boolean enabled; private boolean auto_dl; private long last_scan; private long last_new_result; private int num_unread; private int num_read; private String last_error; private boolean auth_failed; private int consec_fails; private boolean auto_dl_supported; private boolean dl_with_ref = true; protected SubscriptionHistoryImpl( SubscriptionManagerImpl _manager, SubscriptionImpl _subs ) { manager = _manager; subs = _subs; loadConfig(); } protected SubscriptionResultImpl[] reconcileResults( Engine engine, SubscriptionResultImpl[] latest_results ) { auto_dl_supported = engine.getAutoDownloadSupported() == Engine.AUTO_DL_SUPPORTED_YES; int new_unread = 0; int new_read = 0; if ( last_scan == 0 ){ // first download feed -> mark all existing as read GlobalManager gm = AzureusCoreFactory.getSingleton().getGlobalManager(); for (int i=0;i<latest_results.length;i++){ SubscriptionResultImpl result = latest_results[i]; result.setReadInternal(true); // see if we can associate result with existing download try{ String hash_str = result.getAssetHash(); if ( hash_str != null ){ byte[] hash = Base32.decode( hash_str ); DownloadManager dm = gm.getDownloadManager( new HashWrapper( hash )); if ( dm != null ){ log( "Adding existing association on first read for '" + dm.getDisplayName()); subs.addAssociation( hash ); } } }catch( Throwable e ){ Debug.printStackTrace(e); } } } long now = SystemTime.getCurrentTime(); SubscriptionResultImpl[] result; int max_results = manager.getMaxNonDeletedResults(); synchronized( this ){ boolean got_new_or_changed_result = false; SubscriptionResultImpl[] existing_results = manager.loadResults( subs ); ByteArrayHashMap result_key_map = new ByteArrayHashMap(); ByteArrayHashMap result_key2_map = new ByteArrayHashMap(); List new_results = new ArrayList(); for (int i=0;i<existing_results.length;i++){ SubscriptionResultImpl r = existing_results[i]; result_key_map.put( r.getKey1(), r ); byte[] key2 = r.getKey2(); if ( key2 != null ){ result_key2_map.put( key2, r ); } new_results.add( r ); if ( !r.isDeleted()){ if ( r.getRead()){ new_read++; }else{ new_unread++; } } } for (int i=0;i<latest_results.length;i++){ SubscriptionResultImpl r = latest_results[i]; // we first of all insist on names uniqueness SubscriptionResultImpl existing = (SubscriptionResultImpl)result_key_map.get( r.getKey1()); if ( existing == null ){ // only if non-unique name do we fall back and use UID to remove duplicate // entries where the name has changed byte[] key2 = r.getKey2(); if ( key2 != null ){ existing = (SubscriptionResultImpl)result_key2_map.get( key2 ); } } if ( existing == null ){ last_new_result = now; new_results.add( r ); result_key_map.put( r.getKey1(), r ); byte[] key2 = r.getKey2(); if ( key2 != null ){ result_key2_map.put( key2, r ); } got_new_or_changed_result = true; if ( r.getRead()){ new_read++; }else{ new_unread++; } }else{ if ( existing.updateFrom( r )){ got_new_or_changed_result = true; } } } // see if we need to delete any old ones if ( max_results > 0 && (new_unread + new_read ) > max_results ){ for (int i=0;i<new_results.size();i++){ SubscriptionResultImpl r = (SubscriptionResultImpl)new_results.get(i); if ( !r.isDeleted()){ if ( r.getRead()){ new_read--; }else{ new_unread--; } r.deleteInternal(); got_new_or_changed_result = true; if (( new_unread + new_read ) <= max_results ){ break; } } } } if ( got_new_or_changed_result ){ result = (SubscriptionResultImpl[])new_results.toArray( new SubscriptionResultImpl[new_results.size()]); manager.saveResults( subs, result ); }else{ result = existing_results; } last_scan = now; num_unread = new_unread; num_read = new_read; } // always save config as we have a new scan time saveConfig(); return( result ); } public boolean isEnabled() { return( enabled ); } public void setEnabled( boolean _enabled ) { if ( _enabled != enabled ){ enabled = _enabled; saveConfig(); } } public boolean isAutoDownload() { return( auto_dl ); } public void setAutoDownload( boolean _auto_dl ) { if ( _auto_dl != auto_dl ){ auto_dl = _auto_dl; saveConfig(); if ( auto_dl ){ downloadNow(); } } } public void setDetails( boolean _enabled, boolean _auto_dl ) { if ( enabled != _enabled || auto_dl != _auto_dl ){ enabled = _enabled; auto_dl = _auto_dl; saveConfig(); if ( enabled && auto_dl ){ downloadNow(); } } } protected void downloadNow() { try{ subs.getManager().getScheduler().downloadAsync( subs, false ); }catch( Throwable e ){ log( "Failed to initiate download", e ); } } public long getLastScanTime() { return( last_scan ); } public long getLastNewResultTime() { return( last_new_result ); } public long getNextScanTime() { Map schedule = subs.getScheduleConfig(); if ( schedule.size() == 0 ){ log( "Schedule is empty!"); return( Long.MAX_VALUE ); }else{ try{ long interval_min = ((Long)schedule.get( "interval" )).longValue(); if ( interval_min == Integer.MAX_VALUE || interval_min == Long.MAX_VALUE ){ return( Long.MAX_VALUE ); } if ( last_scan == 0 ){ // never scanned, scan immediately return( SystemTime.getCurrentTime()); }else{ return( last_scan + interval_min*60*1000 ); } }catch( Throwable e ){ log( "Failed to decode schedule " + schedule, e ); return( Long.MAX_VALUE ); } } } public int getCheckFrequencyMins() { Map schedule = subs.getScheduleConfig(); if ( schedule.size() == 0 ){ return( DEFAULT_CHECK_INTERVAL_MINS ); }else{ try{ int interval_min = ((Long)schedule.get( "interval" )).intValue(); return( interval_min ); }catch( Throwable e ){ return( DEFAULT_CHECK_INTERVAL_MINS ); } } } public int getNumUnread() { return( num_unread ); } public int getNumRead() { return( num_read ); } public SubscriptionResult[] getResults( boolean include_deleted ) { SubscriptionResult[] results; synchronized( this ){ results = manager.loadResults( subs ); } if ( include_deleted ){ return( results ); }else{ List l = new ArrayList( results.length ); for (int i=0;i<results.length;i++){ if ( !results[i].isDeleted()){ l.add( results[i] ); } } return((SubscriptionResult[])l.toArray( new SubscriptionResult[l.size()])); } } public SubscriptionResult getResult( String result_id ) { SubscriptionResult[] results = getResults( true ); for (int i=0;i<results.length;i++){ if ( results[i].getID().equals( result_id )){ return( results[i] ); } } return( null ); } protected void updateResult( SubscriptionResultImpl result ) { byte[] key = result.getKey1(); boolean changed = false; synchronized( this ){ SubscriptionResultImpl[] results = manager.loadResults( subs ); for (int i=0;i<results.length;i++){ if ( Arrays.equals( results[i].getKey1(), key )){ results[i] = result; changed = true; } } if ( changed ){ updateReadUnread( results ); manager.saveResults( subs, results ); } } if ( changed ){ saveConfig(); } if ( isAutoDownload() && !result.getRead() && !result.isDeleted()){ manager.getScheduler().download( subs, result ); } } public void deleteResults( String[] result_ids ) { ByteArrayHashMap rids = new ByteArrayHashMap(); for (int i=0;i<result_ids.length;i++){ rids.put( Base32.decode( result_ids[i]), "" ); } boolean changed = false; synchronized( this ){ SubscriptionResultImpl[] results = manager.loadResults( subs ); for (int i=0;i<results.length;i++){ SubscriptionResultImpl result = results[i]; if ( !result.isDeleted() && rids.containsKey( result.getKey1())){ changed = true; result.deleteInternal(); } } if ( changed ){ updateReadUnread( results ); manager.saveResults( subs, results ); } } if ( changed ){ saveConfig(); } } public void deleteAllResults() { boolean changed = false; synchronized( this ){ SubscriptionResultImpl[] results = manager.loadResults( subs ); for (int i=0;i<results.length;i++){ SubscriptionResultImpl result = results[i]; if ( !result.isDeleted()){ changed = true; result.deleteInternal(); } } if ( changed ){ updateReadUnread( results ); manager.saveResults( subs, results ); } } if ( changed ){ saveConfig(); } } public void markAllResultsRead() { boolean changed = false; synchronized( this ){ SubscriptionResultImpl[] results = manager.loadResults( subs ); for (int i=0;i<results.length;i++){ SubscriptionResultImpl result = results[i]; if ( !result.getRead()){ changed = true; result.setReadInternal( true ); } } if ( changed ){ updateReadUnread( results ); manager.saveResults( subs, results ); } } if ( changed ){ saveConfig(); } } public void markAllResultsUnread() { boolean changed = false; synchronized( this ){ SubscriptionResultImpl[] results = manager.loadResults( subs ); for (int i=0;i<results.length;i++){ SubscriptionResultImpl result = results[i]; if ( result.getRead()){ changed = true; result.setReadInternal( false ); } } if ( changed ){ updateReadUnread( results ); manager.saveResults( subs, results ); } } if ( changed ){ saveConfig(); } } public void markResults( String[] result_ids, boolean[] reads ) { ByteArrayHashMap rid_map = new ByteArrayHashMap(); for (int i=0;i<result_ids.length;i++){ rid_map.put( Base32.decode( result_ids[i]), new Boolean( reads[i] )); } boolean changed = false; List newly_unread = new ArrayList(); synchronized( this ){ SubscriptionResultImpl[] results = manager.loadResults( subs ); for (int i=0;i<results.length;i++){ SubscriptionResultImpl result = results[i]; if ( result.isDeleted()){ continue; } Boolean b_read = (Boolean)rid_map.get( result.getKey1()); if ( b_read != null ){ boolean read = b_read.booleanValue(); if ( result.getRead() != read ){ changed = true; result.setReadInternal( read ); if ( !read ){ newly_unread.add( result ); } } } } if ( changed ){ updateReadUnread( results ); manager.saveResults( subs, results ); } } if ( changed ){ saveConfig(); } if ( isAutoDownload()){ for (int i=0;i<newly_unread.size();i++){ manager.getScheduler().download( subs, (SubscriptionResult)newly_unread.get(i)); } } } public void reset() { synchronized( this ){ SubscriptionResultImpl[] results = manager.loadResults( subs ); if ( results.length > 0 ){ results = new SubscriptionResultImpl[0]; manager.saveResults( subs, results ); } updateReadUnread( results ); } last_error = null; last_new_result = 0; last_scan = 0; saveConfig(); } protected void checkMaxResults( int max_results ) { if ( max_results <= 0 ){ return; } boolean changed = false; synchronized( this ){ if ((num_unread + num_read ) > max_results ){ SubscriptionResultImpl[] results = manager.loadResults( subs ); for (int i=0;i<results.length;i++){ SubscriptionResultImpl r = results[i]; if ( !r.isDeleted()){ if ( r.getRead()){ num_read--; }else{ num_unread--; } r.deleteInternal(); changed = true; if (( num_unread + num_read ) <= max_results ){ break; } } } if ( changed ){ manager.saveResults( subs, results ); } } } if ( changed ){ saveConfig(); } } protected void updateReadUnread( SubscriptionResultImpl[] results ) { int new_unread = 0; int new_read = 0; for (int i=0;i<results.length;i++){ SubscriptionResultImpl result = results[i]; if ( !result.isDeleted()){ if ( result.getRead()){ new_read++; }else{ new_unread++; } } } num_read = new_read; num_unread = new_unread; } protected boolean isAutoDownloadSupported() { return( auto_dl_supported ); } protected void setFatalError( String _error ) { last_error = _error; consec_fails = 1024; } protected void setLastError( String _last_error, boolean _auth_failed ) { last_error = _last_error; auth_failed = _auth_failed; if ( last_error == null ){ consec_fails = 0; }else{ consec_fails++; } subs.fireChanged(); } public String getLastError() { return( last_error ); } public boolean isAuthFail() { return( auth_failed ); } public int getConsecFails() { return( consec_fails ); } public boolean getDownloadWithReferer() { return( dl_with_ref ); } public void setDownloadWithReferer( boolean b ) { if ( b != dl_with_ref ){ dl_with_ref = b; saveConfig(); } } protected void loadConfig() { Map map = subs.getHistoryConfig(); Long l_enabled = (Long)map.get( "enabled" ); enabled = l_enabled==null?true:l_enabled.longValue()==1; Long l_auto_dl = (Long)map.get( "auto_dl" ); auto_dl = l_auto_dl==null?false:l_auto_dl.longValue()==1; Long l_last_scan = (Long)map.get( "last_scan" ); last_scan = l_last_scan==null?0:l_last_scan.longValue(); Long l_last_new = (Long)map.get( "last_new" ); last_new_result = l_last_new==null?0:l_last_new.longValue(); Long l_num_unread = (Long)map.get( "num_unread" ); num_unread = l_num_unread==null?0:l_num_unread.intValue(); Long l_num_read = (Long)map.get( "num_read" ); num_read = l_num_read==null?0:l_num_read.intValue(); // migration - if we've already downloaded this feed then we default to being // enabled Long l_auto_dl_s = (Long)map.get( "auto_dl_supported" ); auto_dl_supported = l_auto_dl_s==null?(last_scan>0):l_auto_dl_s.longValue()==1; Long l_dl_with_ref = (Long)map.get( "dl_with_ref" ); dl_with_ref = l_dl_with_ref==null?true:l_dl_with_ref.longValue()==1; } protected void saveConfig() { Map map = new HashMap(); map.put( "enabled", new Long( enabled?1:0 )); map.put( "auto_dl", new Long( auto_dl?1:0 )); map.put( "auto_dl_supported", new Long( auto_dl_supported?1:0)); map.put( "last_scan", new Long( last_scan )); map.put( "last_new", new Long( last_new_result )); map.put( "num_unread", new Long( num_unread )); map.put( "num_read", new Long( num_read )); map.put( "dl_with_ref", new Long( dl_with_ref?1:0 )); subs.updateHistoryConfig( map ); } protected void log( String str ) { subs.log( "History: " + str ); } protected void log( String str, Throwable e ) { subs.log( "History: " + str, e ); } protected String getString() { return( "unread=" + num_unread + ",read=" + num_read+ ",last_err=" + last_error ); } }