///
/// Copyright © 2003-2008 JetBrains s.r.o.
/// You may distribute under the terms of the GNU General Public License, as published by the Free Software Foundation, version 2 (see License.txt in the repository root folder).
///
using System;
using System.Collections;
using System.Diagnostics;
using System.IO;
using JetBrains.DataStructures;
using JetBrains.Omea.AsyncProcessing;
using JetBrains.Omea.Conversations;
using JetBrains.Omea.OpenAPI;
using JetBrains.Omea.ResourceTools;
namespace JetBrains.Omea.InstantMessaging.Miranda
{
///
/// The class which manages the import of a single Miranda database.
///
internal class MirandaImportJob: ReenteringEnumeratorJob
{
private IMirandaDB _db;
private readonly string _dbPath;
private int _contactIndex;
private int _lastProgressUpdate = 0;
private long _startTicks;
private IEnumerator _contactEnumerator;
private IResource _selfContact;
private readonly Hashtable _selfAccounts = new Hashtable( new CaseInsensitiveHashCodeProvider(), new CaseInsensitiveComparer() );
private readonly IntHashTableOfInt _lastEventOffsets = new IntHashTableOfInt(); // contact offset -> last event offset
private readonly bool _traceImport;
private DateTime _indexStartDate = DateTime.MinValue;
private readonly AddressBook _mirandaAB;
private readonly IMConversationsManager _conversationManager;
private int _lastFileSize = -1;
private int _lastSlackSpace = -1;
private bool _dbErrorReported = false;
private IntHashSet _updatedConversations;
private bool _completed = false;
public MirandaImportJob( string dbPath, IMConversationsManager convManager, AddressBook mirandaAB )
{
_dbPath = dbPath;
_conversationManager = convManager;
_mirandaAB = mirandaAB;
_traceImport = IniSettings.TraceImport;
if ( !IniSettings.FullIndexingCompleted )
{
_indexStartDate = IniSettings.IndexStartDate;
}
}
public override string Name
{
get { return "Miranda Database Import"; }
}
internal void ClearEventOffsets()
{
_lastEventOffsets.Clear();
}
internal void ResetIndexStartDate()
{
_indexStartDate = DateTime.MinValue;
_lastFileSize = -1;
ClearEventOffsets();
}
public override void EnumerationStarting()
{
_completed = false;
_startTicks = DateTime.Now.Ticks;
if ( Core.State == CoreState.ShuttingDown )
{
Interrupted = true;
return;
}
if ( !File.Exists( _dbPath ) )
{
Trace.WriteLine( "Failed to open Miranda DB " + _dbPath );
Interrupted = true;
return;
}
MirandaDB db;
try
{
db = new MirandaDB( _dbPath );
}
catch( IOException )
{
CheckReportDBError( "Failed to open database " + _dbPath );
return;
}
catch( MirandaDatabaseCorruptedException ex )
{
CheckReportDBError( "Database " + _dbPath + " is corrupted: " + ex.Message );
return;
}
ImportDB( db );
}
private void CheckReportDBError( string message )
{
Interrupted = true;
if ( !_dbErrorReported )
{
_dbErrorReported = true;
Core.UIManager.QueueUIJob( new MirandaPlugin.StringCallback( MirandaPlugin.HandleDatabaseOpenError ), message );
}
}
public void ImportDB( IMirandaDB db )
{
_updatedConversations = new IntHashSet();
_db = db;
if ( _db.FileSize == _lastFileSize && _db.SlackSpace == _lastSlackSpace )
{
TraceImport( "Skipping Miranda DB import because file size and slack space did not change" );
_contactEnumerator = null;
return;
}
_lastFileSize = _db.FileSize;
_lastSlackSpace = db.SlackSpace;
ImportContact( _db.UserContact, true );
if ( _selfContact == null )
{
_selfContact = Core.ContactManager.MySelf.Resource;
}
// for AIM, we cannot create the contact resource from
// the account, so hook the contact to the account later
foreach( IResource acct in _selfAccounts.Values )
{
if ( !acct.HasLink( Props.MirandaAcct, _selfContact ) )
acct.AddLink( Props.MirandaAcct, _selfContact );
}
_contactIndex = 0;
_contactEnumerator = _db.Contacts.GetEnumerator();
}
public override AbstractJob GetNextJob()
{
if ( Core.State == CoreState.ShuttingDown )
{
Interrupted = true;
return null;
}
if ( _contactEnumerator == null || !_contactEnumerator.MoveNext() )
{
return null;
}
if ( Core.ProgressWindow != null )
{
if ( Environment.TickCount - _lastProgressUpdate > 500 && _db.ContactCount > 0 )
{
Core.ProgressWindow.UpdateProgress( _contactIndex * 100 / _db.ContactCount,
"Importing Miranda database...", null );
_lastProgressUpdate = Environment.TickCount;
}
}
DelegateJob job = new DelegateJob( new ImportContactDelegate( ImportContact ),
new[] { _contactEnumerator.Current, false } );
_contactIndex++;
return job;
}
public override void EnumerationFinished()
{
if ( _db != null )
{
_db.Close();
}
if ( _updatedConversations != null )
{
foreach( IntHashSet.Entry e in _updatedConversations )
{
IResource res = Core.ResourceStore.TryLoadResource( e.Key );
if ( res != null )
{
Core.FilterEngine.ExecRules( StandardEvents.ResourceReceived, res );
Core.TextIndexManager.QueryIndexing( res.Id );
}
}
_updatedConversations = null;
}
long endTicks = DateTime.Now.Ticks;
Trace.WriteLineIf( IniSettings.TraceImport,
"Miranda import took " + (endTicks - _startTicks) / 10000 + " ms" );
if ( !Interrupted && _indexStartDate == DateTime.MinValue )
{
IniSettings.FullIndexingCompleted = true;
ExecuteInIdle = false;
}
_completed = true;
}
public bool Completed
{
get { return _completed; }
}
private void TraceImport( string str )
{
if ( _traceImport )
{
Trace.WriteLine( str );
}
}
private delegate void ImportContactDelegate( IMirandaContact contact, bool myself );
private void ImportContact( IMirandaContact contact, bool myself )
{
if ( contact.LastEventOffset == _lastEventOffsets [contact.Offset] )
return;
// guard for job reentering (we may have restarted the import job with a new database - OM-11022)
if ( contact.DatabaseClosed )
return;
try
{
_lastEventOffsets [contact.Offset] = contact.LastEventOffset;
IMirandaContactSettings icqSettings = null;
IMirandaContactSettings aimSettings = null;
IMirandaContactSettings jabberSettings = null;
IMirandaContactSettings yahooSettings = null;
string group = null;
TraceImport( "Importing contact at " + contact.Offset );
foreach( IMirandaContactSettings settings in contact.ContactSettings )
{
TraceImport( "Found settings for " + settings.ModuleName );
if ( String.Compare( settings.ModuleName, "ICQ", true ) == 0 )
{
icqSettings = settings;
}
else if ( String.Compare( settings.ModuleName, "AIM", true ) == 0 )
{
aimSettings = settings;
}
else if ( String.Compare( settings.ModuleName, "JABBER", true ) == 0 )
{
jabberSettings = settings;
}
else if ( String.Compare( settings.ModuleName, "YAHOO", true ) == 0 )
{
yahooSettings = settings;
}
else if ( String.Compare( settings.ModuleName, "CList", true ) == 0 )
{
group = (string) settings.Settings ["Group"];
}
}
if ( icqSettings != null )
ImportICQContact( contact, icqSettings, group, myself );
if ( aimSettings != null )
ImportAIMContact( contact, aimSettings, group, myself );
if ( jabberSettings != null )
ImportJabberContact( contact, jabberSettings, group, myself );
if ( yahooSettings != null )
ImportYahooContact( contact, yahooSettings, group, myself );
}
catch( MirandaDatabaseCorruptedException ex )
{
CheckReportDBError( "Database " + _dbPath + " is corrupted: " + ex.Message );
}
}
private void ImportICQContact( IMirandaContact contact, IMirandaContactSettings settings,
string group, bool myself )
{
if ( !settings.Settings.Contains( "UIN" ) )
{
return;
}
int UIN = (int) settings.Settings ["UIN"];
string firstName = (string) settings.Settings ["FirstName"];
string lastName = (string) settings.Settings ["LastName"];
string email = (string) settings.Settings ["e-mail"];
string nick = (string) settings.Settings ["Nick"];
if ( firstName == null )
{
firstName = "";
}
if ( lastName == null )
{
lastName = "";
}
IResource contactRes;
IResource icqAccount = Core.ResourceStore.FindUniqueResource( ResourceTypes.MirandaICQAccount,
Props.UIN, UIN );
if ( icqAccount == null || !icqAccount.HasProp( Props.MirandaAcct ) )
{
if ( icqAccount == null )
{
icqAccount = Core.ResourceStore.BeginNewResource( ResourceTypes.MirandaICQAccount );
icqAccount.SetProp( Props.UIN, UIN );
icqAccount.SetProp( Props.NickName, nick );
icqAccount.EndUpdate();
}
if ( firstName == "" && lastName == "" )
{
string contactName;
if ( !string.IsNullOrEmpty( nick ) )
contactName = nick;
else
contactName = UIN.ToString();
IContact ct = Core.ContactManager.FindOrCreateContact( email, contactName );
contactRes = ct.Resource;
}
else
{
IContact ct = Core.ContactManager.FindOrCreateContact( email, firstName, lastName );
contactRes = ct.Resource;
}
icqAccount.AddLink( Props.MirandaAcct, contactRes );
}
else
{
contactRes = icqAccount.GetLinkProp( Props.MirandaAcct );
}
AssignCategory( contactRes, group );
if ( _mirandaAB != null )
{
_mirandaAB.AddContact( contactRes );
}
else if ( !contactRes.HasLink( Props.MirandaAcct, icqAccount ) )
{
contactRes.AddLink( Props.MirandaAcct, icqAccount );
}
if ( myself )
{
_selfContact = contactRes;
_selfAccounts ["ICQ"] = icqAccount;
}
else
ImportContactEvents( contact, icqAccount, "ICQ" );
}
private void ImportAIMContact( IMirandaContact contact, IMirandaContactSettings settings,
string group, bool myself )
{
string screenName = (string) settings.Settings ["SN"];
string nickName = (string) settings.Settings ["Nick"];
if ( screenName == null )
return;
ImportContactGeneric( contact, group, myself, "AIM", ResourceTypes.MirandaAIMAccount,
new[] { Props.ScreenName, Props.NickName }, new object[] { screenName, nickName } );
}
private void ImportJabberContact( IMirandaContact contact, IMirandaContactSettings settings,
string group, bool myself )
{
string jabberID = (string) settings.Settings ["jid"];
if ( jabberID == null )
{
jabberID = (string) settings.Settings ["LoginName"] + "@" +
(string) settings.Settings ["LoginServer"];
}
string nickName = (string) settings.Settings ["Nick"];
ImportContactGeneric( contact, group, myself, "JABBER", ResourceTypes.MirandaJabberAccount,
new[] { Props.JabberId, Props.NickName }, new object[] { jabberID, nickName } );
}
private void ImportYahooContact( IMirandaContact contact, IMirandaContactSettings settings,
string group, bool myself )
{
string yahooId = (string) settings.Settings ["yahoo_id"];
string nickName = (string) settings.Settings ["Nick"];
if ( yahooId == null )
return;
ImportContactGeneric( contact, group, myself, "YAHOO", ResourceTypes.MirandaYahooAccount,
new[] { Props.YahooId, Props.NickName }, new object[] { yahooId, nickName } );
}
private void ImportContactGeneric( IMirandaContact contact, string group, bool myself,
string moduleName, string accountResType, int[] propIds, object[] propValues )
{
IResource imAccount = Core.ResourceStore.FindUniqueResource( accountResType,
propIds [0], propValues [0] );
if ( imAccount == null )
{
imAccount = Core.ResourceStore.BeginNewResource( accountResType );
for( int i=0; i lastImportedTime )
{
if ( mirandaEvent.Timestamp > lastMessageTime )
lastMessageTime = mirandaEvent.Timestamp;
if ( mirandaEvent.Timestamp < firstMessageTime )
firstMessageTime = mirandaEvent.Timestamp;
IResource fromAccount, toAccount;
if ( (mirandaEvent.Flags & 2) != 0 ) // DBEF_SENT
{
fromAccount = selfAccount;
toAccount = accountRes;
}
else
{
fromAccount = accountRes;
toAccount = selfAccount;
}
IResource conversation = _conversationManager.Update(
mirandaEvent.EventData,
mirandaEvent.Timestamp.ToLocalTime(),
fromAccount, toAccount );
if ( conversation != null )
{
_updatedConversations.Add( conversation.Id );
}
eventsImported++;
}
else
eventsSkippedImportedTime++;
}
}
TraceImport( String.Format( "Imported {0} events, skipped because of index start date - {1}, skipped because already imported - {2}",
eventsImported, eventsSkippedIndexStart, eventsSkippedImportedTime ) );
if ( lastMessageTime > lastImportedTime )
{
accountRes.SetProp( Props.LastMirandaImport, lastMessageTime );
}
if ( _indexStartDate != DateTime.MinValue )
{
accountRes.SetProp( Props.FirstMirandaImport, _indexStartDate );
}
else if ( firstMessageTime < firstImportedTime )
{
accountRes.SetProp( Props.FirstMirandaImport, firstMessageTime );
}
}
private static void AssignCategory( IResource contactRes, string group )
{
if ( IniSettings.CreateCategories && group != null )
{
IResource groupCategory = Core.CategoryManager.FindOrCreateCategory(
Core.CategoryManager.GetRootForTypedCategory( "Contact" ), group );
Core.CategoryManager.AddResourceCategory( contactRes, groupCategory );
}
}
}
}