///
/// 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.Collections.Generic;
using System.IO;
using System.Text;
using System.Diagnostics;
using JetBrains.Omea.Base;
using JetBrains.Omea.Database.DBF;
using JetBrains.Omea.OpenAPI;
using JetBrains.Omea.ResourceTools;
using Microsoft.Win32;
using JetBrains.Omea.Containers;
using JetBrains.DataStructures;
namespace JetBrains.Omea.InstantMessaging.ICQ.DBImport
{
public enum DBVersion
{
db_Undefined = 0,
db_99A = 10,
db_99B = 14,
db_2000a = 17,
db_2000b = 18,
db_2001a = 19, // 2001a, 2001b, 2002a, 2003a
db_2003b = 20 // 2003b and higher
}
/**
* Database enumerates messages & contacts in a directory
* Directory may contain database files for several UINs
*/
internal abstract class IICQDatabase : IEnumerable, IEnumerator
{
public abstract string CurrentLocation { get; }
public abstract DBVersion CurrentDBVersion { get; }
public abstract int CurrentUIN { get; }
public abstract bool EnumUINsOnly { get; set; }
public abstract void SkipUpdate();
public abstract bool MoveNext();
public abstract void Reset();
public abstract IEnumerator GetEnumerator();
public abstract object Current { get; }
public static ICachingStrategy CachingStrategy
{
get
{
if( _strategy == null )
{
_strategy = new SharedCachingStrategy( 1 << 19 );
}
return _strategy;
}
}
private static ICachingStrategy _strategy;
}
/**
* ICQDatabase class imports history of versions earlier than 2003b
*/
internal class ICQDatabase : IICQDatabase
{
public ICQDatabase( string sDirectory )
{
_sDirectory = sDirectory;
Reset();
}
#region IICQDabase
public override string CurrentLocation
{
get { return _sDirectory; }
}
public override DBVersion CurrentDBVersion
{
get { return (DBVersion)_iDBVersion; }
}
public override int CurrentUIN
{
get { return _iUIN; }
}
public override bool EnumUINsOnly
{
get { return _enumUINsOnly; }
set { _enumUINsOnly = value; }
}
public override void SkipUpdate() {}
#endregion
#region implementation of IEnumerator & IEnumerable
public override IEnumerator GetEnumerator()
{
return this;
}
public override object Current
{
get { return _resultObject; }
}
public override bool MoveNext()
{
while( !_bFinished )
{
if( _bNewFile )
{
// enumerate files in order to find ICQ db file
if( !_itfFilesEnumerator.MoveNext() )
{
_bFinished = true;
}
else
{
FileInfo fi = (FileInfo) _itfFilesEnumerator.Current;
_iUIN = ExtractUINByFileName( fi.Name );
if( _iUIN > 0 )
{
_currentContact = ContactsFactory.GetInstance().GetContact( _iUIN );
if( _enumUINsOnly )
{
return true;
}
_bNewFile = !OpenDBFiles( _iUIN );
}
}
}
else
{
if( ReadIndexes() )
{
return true;
}
_bNewFile = true;
}
}
return false;
}
public override void Reset()
{
_bFinished = false;
_bNewFile = true;
_enumUINsOnly = false;
try
{
_itfFilesEnumerator = new DirectoryInfo( _sDirectory ).GetFiles().GetEnumerator();
// messages dated earlier than year the 1998 and very likely broken
// and should be ignored
_boundDate = new DateTime( 1998, 1, 1 );
_Idx = new IdxRecord();
_datEntry = new DatRecord();
}
catch( Exception )
{
_bFinished = true;
}
}
private bool ReadIndexes()
{
// read dat entries for corresponding indexes
// index value -1 is meaning null index, so current index
// was the last one, and we are to try next file
while( _iCurrentIdx != -1 )
{
if( !_Idx.ReadIdxRecord( _iCurrentIdx, _fileIdx ) )
{
break;
}
_iCurrentIdx = _Idx.Next;
if( _Idx.Code == -2 && _Idx.DatPos != -1 &&
_datEntry.ReadDatRecord( _Idx.DatPos, _fileDat, CurrentDBVersion ) )
{
// parse dat entry and save it if ok
object Result = _datEntry.ParseEntry( _fileDat );
if( Result != null )
{
_resultObject = Result;
ICQMessage theMsg = Result as ICQMessage;
if( theMsg != null )
{
// ignore messages with empty body and broken date
if( theMsg.Body.Length == 0 || theMsg.Time < _boundDate )
{
continue;
}
++_currentContact.Messages;
if( theMsg.From == null )
{
theMsg.From = _currentContact;
}
else
{
theMsg.To = _currentContact;
}
}
return true;
}
}
}
return false;
}
#endregion
#region implementation details
/**
* returns UIN by name of ICQ database file
* if result <= 0 than sFileName is not a ICQ database file
*/
private static int ExtractUINByFileName( string sFileName )
{
int iUIN = 0;
int iDotIndex = sFileName.ToLower().IndexOf( ".dat" );
if( iDotIndex > 0 )
{
try
{
iUIN = Convert.ToInt32( sFileName.Substring( 0, iDotIndex ) );
}
catch {}
}
return iUIN;
}
/**
* opens db files for a specified UIN and reads header
*/
private bool OpenDBFiles( int iUIN )
{
string sUIN = '\\' + iUIN.ToString();
try
{
_fileDat = new BinaryReader( new CachedStream( new FileStream( _sDirectory + sUIN + ".dat",
FileMode.Open, FileAccess.Read, FileShare.ReadWrite, 256 ), CachingStrategy ) );
_fileIdx = new BinaryReader( new CachedStream( new FileStream( _sDirectory + sUIN + ".idx",
FileMode.Open, FileAccess.Read, FileShare.ReadWrite, 256 ), CachingStrategy ) );
// read idx header
if( _fileIdx.ReadUInt32() != 4 || _fileIdx.ReadUInt32() != 20 || _fileIdx.ReadUInt32() != 8 )
throw new Exception( "Header of " + _sDirectory + sUIN + ".idx file is invalid" );
_iCurrentIdx = _fileIdx.ReadInt32();
_iDBVersion = _fileIdx.ReadInt32();
// if the version is not supported then finish processing current database
if( _iDBVersion != (int)DBVersion.db_2001a &&
_iDBVersion != (int)DBVersion.db_2000a &&
_iDBVersion != (int)DBVersion.db_2000b)
throw new Exception( "ICQ DB version is not supported: " + _iDBVersion );
}
catch( Exception e )
{
Trace.WriteLine( e.ToString(), "ICQ.DBImport" );
return false;
}
return true;
}
private static void SeekBegin( BinaryReader reader, int offset )
{
reader.BaseStream.Seek( offset, SeekOrigin.Begin );
}
private static void SeekCurrent( BinaryReader reader, int offset )
{
reader.BaseStream.Seek( offset, SeekOrigin.Current );
}
private static string ReadANSIStr( BinaryReader reader )
{
ushort iStrLen = reader.ReadUInt16();
if( iStrLen == 0 )
{
return string.Empty;
}
return Encoding.Default.GetString( reader.ReadBytes( iStrLen ) ).Trim( '\0' );
}
private static string ReadUTF8Str( BinaryReader reader )
{
ushort iStrLen = reader.ReadUInt16();
if( iStrLen == 0 )
{
return string.Empty;
}
return Encoding.UTF8.GetString( reader.ReadBytes( iStrLen ) ).Trim( '\0' );
}
private static DateTime ReadDateTime( int iICQTimeStamp )
{
// it's rather strange but ICQ time is shifted on 7 hours
DateTime DT = new DateTime( 1970, 1, 1 ).AddDays( ((double)iICQTimeStamp) / (24 * 60 * 60) );
DT = DT.AddHours( _hourAddend );
DT = new DateTime( DT.Year, DT.Month, DT.Day, DT.Hour, DT.Minute, DT.Second, 0 );
return DT;
}
private class IdxRecord
{
public int Code; // ff entry is valid the it's set to -2
public int Number; // DAT entry number
public int Next; // next IdxRecord offset
public int Prev; // previous IdxRecord offset
public int DatPos; // offfset in .dat file
public bool ReadIdxRecord( int idx, BinaryReader reader )
{
try
{
SeekBegin( reader, idx );
Code = reader.ReadInt32();
Number = reader.ReadInt32();
Next = reader.ReadInt32();
Prev = reader.ReadInt32();
DatPos = reader.ReadInt32();
}
catch { return false; }
return true;
}
}
private class DatRecord
{
private int FillType;
private int Number;
private byte Command;
private DBVersion iDBVersion;
private bool _ignored;
public bool ReadDatRecord( int idx, BinaryReader reader, DBVersion iDBVersion )
{
try
{
SeekBegin( reader, idx );
reader.ReadInt32(); // skip length
FillType = reader.ReadInt32();
Number = reader.ReadInt32();
Command = reader.ReadByte();
reader.ReadBytes( 15 );
this.iDBVersion = iDBVersion;
_ignored = ( FillType == 2 );
}
catch { return false; }
return FillType != 9 && ( !_ignored || ( Command == 0xe5 && Command == 0xe4 ) );
}
public object ParseEntry( BinaryReader reader )
{
try
{
switch( Command )
{
// long message (ICQ99a-2002a)
case 0x50:
if( _ignored )
{
return null;
}
return ParseMessage( reader );
// short message & URL format (ICQ99a-2002a)
case 0xe0:
case 0xa0:
if( _ignored )
{
return null;
}
return ParseShortMessageOrURL( reader );
// contact
case 0xe5:
return ParseContact( reader );
// my details
case 0xe4:
return ParseMyDetails( reader );
}
}
catch {}
return null;
}
private object ParseMessage( BinaryReader reader )
{
reader.ReadInt64(); // skip separator, filling flags & entry subtype
int iUIN = reader.ReadInt32(); // UIN of sender or receiver
string sANSIBody = ReadANSIStr( reader ); // message body (ANSI text)
reader.ReadInt32(); // skip status of sender/receiver
bool bIsMessageSent =
( reader.ReadInt32() != 0 ); // is message sent ot received?
reader.ReadInt16(); // skip separator value
int iTimeStamp = reader.ReadInt32(); // time stamp
ICQMessage Result = new ICQMessage();
if( sANSIBody.Length > 0 )
{
Result.Body = sANSIBody;
}
else
{
SeekCurrent( reader, 19 ); // skip zeroes
string sRTFBody = string.Empty;
string sUTF8Body = string.Empty;
try
{
sRTFBody = ReadANSIStr( reader ); // message body with rich text
sUTF8Body = ReadUTF8Str( reader ); // message body with UTF-8 text
}
catch( EndOfStreamException )
{
}
Result.Body = (sUTF8Body.Length > 0) ? sUTF8Body : sRTFBody;
}
ICQContact theContact = ContactsFactory.GetInstance().GetContact( iUIN );
if( bIsMessageSent )
{
Result.To = theContact;
}
else
{
Result.From = theContact;
}
if( !( _ignored = theContact.Ignored ) )
{
++theContact.Messages;
}
Result.Time = ReadDateTime( iTimeStamp );
return Result;
}
private object ParseShortMessageOrURL( BinaryReader reader )
{
SeekCurrent( reader, 6 ); // skip separator & filling flags
short iSubType = reader.ReadInt16(); // Entry subtype: 1 - message; 4: URL
int iUIN = reader.ReadInt32(); // UIN of sender or receiver
string sANSIBody = ReadANSIStr( reader ); // message body (ANSI text)
reader.ReadInt32(); // skip status of sender/receiver
bool bIsMessageSent =
( reader.ReadInt32() != 0 ); // is message sent ot received?
reader.ReadInt16(); // skip separator value
int iTimeStamp = reader.ReadInt32(); // time stamp
ICQMessage Result = new ICQMessage();
if( iSubType == 1 )
{
Result.Type = ICQMessage.Types.ShortMessage;
}
else if( iSubType == 4 )
{
Result.Type = ICQMessage.Types.URL;
}
if( sANSIBody.Length > 0 )
{
int i = sANSIBody.IndexOf( "http://", 1 );
if( i > 0 )
{
sANSIBody = sANSIBody.Substring( 0, i - 1 ) + "\r\n" + sANSIBody.Substring( i );
}
else
{
i = sANSIBody.IndexOf( "ftp://", 1 );
if( i > 0 )
{
sANSIBody = sANSIBody.Substring( 0, i - 1 ) + "\r\n" + sANSIBody.Substring( i );
}
}
}
Result.Body = sANSIBody;
ICQContact theContact = ContactsFactory.GetInstance().GetContact( iUIN );
if( bIsMessageSent )
{
Result.To = theContact;
}
else
{
Result.From = theContact;
}
if( !( _ignored = theContact.Ignored ) )
{
++theContact.Messages;
}
Result.Time = ReadDateTime( iTimeStamp );
return Result;
}
private static void ParseProperty( ICQContact aContact, BinaryReader reader )
{
string sName = ReadANSIStr( reader );
switch( reader.ReadByte() )
{
// byte values
case 0x64:
case 0x65:
{
int iValue = reader.ReadByte();
if( sName == "Age" )
{
aContact.Age = iValue;
}
else if( sName == "Gender" )
{
aContact.Gender = (ICQContact.Genders)iValue;
}
else if( sName == "BirthDay" )
{
aContact.BirthDate =
ICQDbImportMisc.GetDate( aContact.BirthDate.Year, aContact.BirthDate.Month, iValue );
}
else if( sName == "BirthMonth" )
{
aContact.BirthDate =
ICQDbImportMisc.GetDate( aContact.BirthDate.Year, iValue, aContact.BirthDate.Day );
}
break;
}
// two-byte values
case 0x66:
case 0x67:
{
int iValue = reader.ReadInt16();
if( sName == "BirthYear")
{
aContact.BirthDate =
ICQDbImportMisc.GetDate( iValue, aContact.BirthDate.Month, aContact.BirthDate.Day );
}
break;
}
// four-byte values
case 0x68:
case 0x69:
{
int iValue = reader.ReadInt32();
if( sName == "UIN" )
{
aContact.UIN = iValue;
}
break;
}
// string values
case 0x6b:
{
string sValue = ReadANSIStr( reader );
if( sName == "NickName" )
{
aContact.NickName = sValue;
}
else if(sName == "MyDefinedHandle" )
{
aContact.MyDefinedHandle = sValue;
}
else if( sName == "FirstName" )
{
aContact.FirstName = sValue;
}
else if( sName == "LastName" )
{
aContact.LastName = sValue;
}
else if( sName == "PrimaryEmail" )
{
aContact.eMail = sValue;
}
else if( sName == "Company" )
{
aContact.Company = sValue;
}
else if( sName == "HomeAddress" )
{
aContact.Address = sValue;
}
else if( sName == "HomeHomepage" )
{
aContact.Homepage = sValue;
}
else if( sName == "Password" )
{
// for some unknown reasons, password is stored many times with the null value
if( aContact.Password.Length == 0 )
{
aContact.Password = sValue;
}
}
break;
}
case 0x6d:
{
int iSublistSize = reader.ReadInt32();
int type = reader.ReadByte();
if( type == 0x6b || type == 0x6e )
{
while( iSublistSize-- > 0 )
{
if( type == 0x6b )
{
ReadANSIStr( reader );
}
else
{
reader.ReadInt16();
int iPropValues = reader.ReadInt32();
while( iPropValues-- > 0 )
ParseProperty( aContact, reader );
}
}
}
break;
}
case 0x6f:
{
SeekCurrent( reader, reader.ReadInt32() );
break;
}
}
}
private object ParseContact( BinaryReader reader )
{
reader.ReadInt16(); // skip separator
if( reader.ReadInt32() != 0x55534552 ) // Label = 0x55534552 ('USER')
{
return null;
}
SeekCurrent( reader, 10 ); // User entry status, GroupID of contact group containing user, Separator value
// skip wav. entries
if( iDBVersion == DBVersion.db_2000a || iDBVersion == DBVersion.db_2000b )
{
int iWavEntries = reader.ReadInt32();
while( iWavEntries-- > 0 )
{
SeekCurrent( reader, 10 );
ReadANSIStr( reader );
}
reader.ReadInt16();
}
if( /*iSeparator >= 533 && */iDBVersion == DBVersion.db_2001a )
{
SeekCurrent( reader, 6 );
}
ICQContact aContact = new ICQContact();
// read property blocks
int iPropBlocks = reader.ReadInt32();
while( iPropBlocks-- > 0 )
{
reader.ReadInt16();
int iProperties = reader.ReadInt32();
while( iProperties-- > 0 )
{
ParseProperty( aContact, reader );
}
}
reader.ReadInt16();
reader.ReadInt32();
/*int iTimeStamp = reader.ReadInt32(); // time stamp, time of last update
aContact.LastUpdateTime = ReadDateTime( iTimeStamp );*/
if( aContact.UIN == 0 )
{
return null;
}
aContact.Ignored = _ignored;
return ContactsFactory.GetInstance().Update( aContact, iDBVersion );
}
private object ParseMyDetails( BinaryReader reader )
{
if( Number != 1005 )
{
return null;
}
reader.ReadInt16(); // skip separator
if( reader.ReadInt32() != 0x55534552 ) // Label = 0x55534552 ('USER')
{
return null;
}
// the following is the sign of "My Details"
if( reader.ReadInt32() != 6 )
{
return null;
}
SeekCurrent( reader, 6 );
// skip wav. entries
if( iDBVersion == DBVersion.db_2000a || iDBVersion == DBVersion.db_2000b ||
( iDBVersion == DBVersion.db_2001a ) )
{
int iWavEntries = reader.ReadInt32();
while( iWavEntries-- > 0 )
{
SeekCurrent( reader, 10 );
ReadANSIStr( reader );
}
reader.ReadInt16();
}
/*if( iSeparator >= 533 && iDBVersion == DBVersion.db_2001a )
SeekCurrent( reader, 6 );*/
ICQContact aContact = new ICQContact();
// read property blocks
int iPropBlocks = reader.ReadInt32();
while( iPropBlocks-- > 0 )
{
reader.ReadInt16();
int iProperties = reader.ReadInt32();
while( iProperties-- > 0 )
{
ParseProperty( aContact, reader );
}
}
reader.ReadInt16();
reader.ReadInt32();
/*int iTimeStamp = reader.ReadInt32(); // time stamp, time of last update
aContact.LastUpdateTime = ReadDateTime( iTimeStamp );*/
if( aContact.UIN == 0 )
{
return null;
}
// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
// TODO: here contact's password should be decrypted
// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
return ContactsFactory.GetInstance().Update( aContact, iDBVersion );
}
}
#endregion
private readonly string _sDirectory;
private IEnumerator _itfFilesEnumerator;
private BinaryReader _fileDat;
private BinaryReader _fileIdx;
private int _iCurrentIdx;
private int _iDBVersion;
private int _iUIN;
private ICQContact _currentContact;
private object _resultObject;
private bool _bFinished;
private bool _bNewFile;
private bool _enumUINsOnly;
private DateTime _boundDate;
private IdxRecord _Idx;
private DatRecord _datEntry;
private static readonly int _hourAddend = DateTime.Now.Hour - DateTime.UtcNow.Hour + 3;
}
/**
* ICQModernDatabase class imports history of 2003b and higher versions
*/
internal class ICQModernDatabase : IICQDatabase, IDisposable
{
public ICQModernDatabase( string directory )
{
_directory = directory;
try
{
_currentUIN = Convert.ToInt32( IOTools.GetFileName( directory ) );
}
catch( Exception ex )
{
Trace.WriteLine( ex.Message );
Dispose();
return;
}
_currentContact = ContactsFactory.GetInstance().GetContact( _currentUIN );
_skipUpdate = false;
Reset();
}
#region IICQDatabase Members
public override string CurrentLocation
{
get { return _directory; }
}
public override DBVersion CurrentDBVersion
{
get { return DBVersion.db_2003b; }
}
public override int CurrentUIN
{
get { return _currentUIN; }
}
public override bool EnumUINsOnly
{
get { return _enumUINsOnly; }
set { _enumUINsOnly = value; }
}
public override void SkipUpdate()
{
_skipUpdate = true;
}
#endregion
#region IDisposable Members
public void Dispose()
{
if( !_skipUpdate )
{
// marshall update record numbers with lowest priority in order to make this
// afters conversations are built
if( _users != null )
{
Core.ResourceAP.QueueJob( JobPriority.Lowest,
new UpdateRecordNumbersDelegate( UpdateRecordNumbers ), _users.FileName, _users.RecordCount - 1 );
}
if( _messages != null )
{
Core.ResourceAP.QueueJob( JobPriority.Lowest,
new UpdateRecordNumbersDelegate( UpdateRecordNumbers ), _messages.FileName, _messages.RecordCount - 1 );
}
}
_currentUIN = 0;
if( _messages != null )
{
_messages.Dispose();
_messages = null;
}
if( _users != null )
{
_users.Dispose();
_users = null;
}
}
private delegate void UpdateRecordNumbersDelegate( string tableName, int count );
private static void UpdateRecordNumbers( string tableName, int count )
{
if( !Core.NetworkAP.IsOwnerThread )
{
Core.NetworkAP.QueueJob( new UpdateRecordNumbersDelegate( UpdateRecordNumbers ), tableName, count );
}
else
{
if( Core.State == CoreState.Running )
{
ObjectStore.WriteInt( "ICQDbImportTableRecordNumbers", tableName, ( count > 0 ) ? count - 1 : -1 );
}
}
}
#endregion
#region implementation of IEnumerator & IEnumerable
public override IEnumerator GetEnumerator()
{
return this;
}
public override void Reset()
{
_enumUINsOnly = !ICQPlugin.IndexedUIN( _currentUIN );
if( !_enumUINsOnly )
{
_messages = new DBFTable(
IOTools.Combine( _directory, "Messages" + _currentUIN + ".dbf" ), true, CachingStrategy );
if( !_messages.IsOk )
{
Dispose();
return;
}
_messages.SetRecordArray( new ArrayList() );
_messages.Fields[ 8 ]._type = FieldType.Blob;
_users = new DBFTable(
IOTools.Combine( _directory, "Users" + _currentUIN + ".dbf" ), true, CachingStrategy );
if( !_users.IsOk )
{
Dispose();
return;
}
_users.SetRecordArray( new ArrayList() );
_users.Fields[ 9 ]._type = FieldType.Blob;
_userRecord = ObjectStore.ReadInt( "ICQDbImportTableRecordNumbers", _users.FileName, -1 );
_messageRecord = ObjectStore.ReadInt( "ICQDbImportTableRecordNumbers", _messages.FileName, -1 );
}
DBFTable owners = new DBFTable(
IOTools.Combine( _directory, "O" + _currentUIN + ".dbf" ), true, CachingStrategy );
try
{
if( owners.IsOk )
{
owners.Fields[ 2 ]._type = FieldType.Blob;
for( int i = 0; i < owners.RecordCount; ++i )
{
ArrayList record = owners[ i ];
if( record != null )
{
byte[] blob = (byte[]) record[ 2 ];
ParseContactProperties( blob, _currentContact, true );
}
}
}
}
finally
{
owners.Dispose();
}
}
public override object Current
{
get
{
if( !_enumUINsOnly )
{
int uin;
while( _userRecord < _users.RecordCount )
{
ArrayList record = _users[ _userRecord ];
if( record != null )
{
uin = GetUIN( record );
if( uin != 0 )
{
ICQContact contact = ContactsFactory.GetInstance().GetContact( uin );
contact.UIN = uin;
contact.NickName = (string) record[ 2 ];
contact.FirstName = (string) record[ 3 ];
contact.LastName = (string) record[ 4 ];
contact.Ignored = (bool) record[ 8 ];
byte[] blob = (byte[]) record[ 9 ];
ParseContactProperties( blob, contact, false );
return contact;
}
}
++_userRecord;
}
while( _messageRecord < _messages.RecordCount )
{
ArrayList record = _messages[ _messageRecord ];
if( record != null )
{
uin = GetUIN( record );
if( uin != 0 )
{
ICQContact contact = ContactsFactory.GetInstance().GetContact( uin );
ICQMessage message = new ICQMessage();
byte[] blob = (byte[]) record[ 8 ];
int statusIndex = SearchMemoForAttributeValue( blob, "Status" );
bool isMessageSent = ( statusIndex > 0 ) && ( blob[ statusIndex ] != 0 );
if( isMessageSent )
{
message.From = _currentContact;
message.To = contact;
}
else
{
message.From = contact;
message.To = _currentContact;
}
message.Body = (string) record[ 9 ];
string timeString = (string) record[ 6 ];
message.Time = ParseDateTime( timeString );
++_currentContact.Messages;
++contact.Messages;
return message;
}
}
++_messageRecord;
}
}
return null;
}
}
public override bool MoveNext()
{
if( !_enumUINsOnly && _currentUIN > 0 )
{
if( ++_userRecord < _users.RecordCount )
{
return true;
}
if( ++_messageRecord < _messages.RecordCount )
{
return true;
}
}
return false;
}
#endregion
#region implementation details
private static int SearchMemoForAttributeValue( byte[] blob, string attr )
{
int attrLength = attr.Length;
for( int i = 0; i < blob.Length - attrLength; ++i )
{
bool eq = true;
for( int j = 0; j < attrLength; ++j )
{
if( !( eq = ( blob[ i + j ] == (byte)attr[ j ] ) ) )
{
break;
}
}
if( eq )
{
return i + attrLength + 2;
}
}
return -1;
}
private static string ParseASCIIString( byte[] blob, int index )
{
int len = blob[ index ] + ( blob[ index + 1 ] << 8 );
if( len + index + 2 > blob.Length )
{
len = blob.Length - index - 2;
}
string result = string.Empty;
if( len > 0 )
{
try
{
result = Encoding.ASCII.GetString( blob, index + 2, len ).TrimEnd( '\0' );
}
catch {}
}
return result;
}
private static string ParseUnicodeString( byte[] blob, int index )
{
int len = blob[ index ] + ( blob[ index + 1 ] << 8 ) + ( blob[ index + 2 ] << 16 ) + ( blob[ index + 3 ] << 24 );
if( len > 0 && len + index + 2 > blob.Length )
{
len = blob.Length - index - 4;
}
string result = string.Empty;
if( len > 0 )
{
try
{
result = Encoding.Unicode.GetString( blob, index + 4, len ).TrimEnd( '\0' );
}
catch {}
}
return result;
}
private static int ParseInt( string str, int offset, int len )
{
int result;
try
{
result = Convert.ToInt32( str.Substring( offset, len ) );
}
catch
{
result = 0;
}
return result;
}
private static int GetUIN( ArrayList record )
{
string uinStr = (string) record[ 1 ];
return ParseInt( uinStr, 0, uinStr.Length );
}
private static DateTime ParseDateTime( string timeString )
{
int year = ParseInt( timeString, 0, 4 );
int month = ParseInt( timeString, 4, 2 );
int day = ParseInt( timeString, 6, 2 );
int hour = ParseInt( timeString, 8, 2 );
int minute = ParseInt( timeString, 11, 2 );
int second = ParseInt( timeString, 14, 2 );
DateTime result;
try
{
result = new DateTime( year, month, day, hour, minute, second, 0 );
result = result.AddHours( _hourAddend );
}
catch
{
result = DateTime.MinValue;
}
return result;
}
private static void ParseContactProperties( byte[] blob, ICQContact contact, bool mySelf )
{
int index = SearchMemoForAttributeValue( blob, "Age" );
if( index > 0 )
{
contact.Age = blob[ index ];
}
index = SearchMemoForAttributeValue( blob, "Gender" );
if( index > 0 )
{
contact.Gender = (ICQContact.Genders) blob[ index ];
}
index = SearchMemoForAttributeValue( blob, "BirthDay" );
if( index > 0 )
{
contact.BirthDate =
ICQDbImportMisc.GetDate( contact.BirthDate.Year, contact.BirthDate.Month, blob[ index ] );
}
index = SearchMemoForAttributeValue( blob, "BirthMonth" );
if( index > 0 )
{
contact.BirthDate =
ICQDbImportMisc.GetDate( contact.BirthDate.Year, blob[ index ], contact.BirthDate.Day );
}
index = SearchMemoForAttributeValue( blob, "BirthYear" );
if( index > 0 && index < blob.Length - 1 )
{
int year = blob[ index ] + ( ( blob[ index + 1 ] ) << 8 );
contact.BirthDate = ICQDbImportMisc.GetDate( year, contact.BirthDate.Month, contact.BirthDate.Day );
}
index = SearchMemoForAttributeValue( blob, "PrimaryEmail" );
if( index > 0 )
{
contact.eMail = ParseASCIIString( blob, index );
}
index = SearchMemoForAttributeValue( blob, "HomeHomepage" );
if( index > 0 )
{
contact.Homepage = ParseASCIIString( blob, index );
}
index = SearchMemoForAttributeValue( blob, "HomeAddress" );
if( index > 0 )
{
contact.Address = ParseASCIIString( blob, index );
}
index = SearchMemoForAttributeValue( blob, "Company" );
if( index > 0 )
{
contact.Company = ParseASCIIString( blob, index );
}
index = SearchMemoForAttributeValue( blob, "Alias" );
if( index > 0 )
{
string alias = ParseUnicodeString( blob, index );
if( alias.Length > 0 )
{
contact.NickName = alias;
}
}
if( mySelf )
{
index = SearchMemoForAttributeValue( blob, "NickName" );
if( index > 0 )
{
contact.NickName = ParseASCIIString( blob, index );
}
index = SearchMemoForAttributeValue( blob, "FirstName" );
if( index > 0 )
{
contact.FirstName = ParseASCIIString( blob, index );
}
index = SearchMemoForAttributeValue( blob, "LastName" );
if( index > 0 )
{
contact.LastName = ParseASCIIString( blob, index );
}
}
}
private bool _enumUINsOnly;
private readonly string _directory;
private int _currentUIN;
private readonly ICQContact _currentContact;
private DBFTable _messages;
private DBFTable _users;
private int _messageRecord;
private int _userRecord;
private bool _skipUpdate;
private static readonly int _hourAddend = DateTime.Now.Hour - DateTime.UtcNow.Hour;
#endregion
}
/**
* the Importer singleton class enumerates ICQ databases
*/
internal class Importer : IEnumerable, IEnumerator
{
private static readonly Importer theImporter = new Importer();
public static Importer GetInstance()
{
return theImporter;
}
public IEnumerator GetEnumerator()
{
return this;
}
public object Current
{
get { return _CurrentDB; }
}
public bool MoveNext()
{
if( !ICQPlugin.GetImportOnly2003b() )
{
while( _ICQHKLMPreferencesKey != null )
{
if( !_itfHKLMValueNamesEnumerator.MoveNext() )
{
_ICQHKLMPreferencesKey = null;
}
else
{
string currentValueName = (string) _itfHKLMValueNamesEnumerator.Current;
if( currentValueName.IndexOf( "Database" ) >= 0 )
{
object currentValue = _ICQHKLMPreferencesKey.GetValue( currentValueName );
if( currentValue is string )
{
_CurrentDB = new ICQDatabase( (string) currentValue );
return true;
}
}
}
}
while( _ICQHKCUPreferencesKey != null )
{
if( !_itfHKCUValueNamesEnumerator.MoveNext() )
{
_ICQHKCUPreferencesKey = null;
}
else
{
string currentValueName = (string) _itfHKCUValueNamesEnumerator.Current;
if( currentValueName.IndexOf( "Database" ) >= 0 )
{
object currentValue = _ICQHKCUPreferencesKey.GetValue( currentValueName );
if( currentValue is string )
{
_CurrentDB = new ICQDatabase( (string) currentValue );
return true;
}
}
}
}
while( _ICQ2003bHKLMPreferencesKey != null )
{
if( !_itf2003bHKLMValueNamesEnumerator.MoveNext() )
{
_ICQ2003bHKLMPreferencesKey = null;
}
else
{
string currentValueName = (string) _itf2003bHKLMValueNamesEnumerator.Current;
if( currentValueName.IndexOf( "Database" ) >= 0 )
{
object currentValue = _ICQ2003bHKLMPreferencesKey.GetValue( currentValueName );
if( currentValue is string )
{
_CurrentDB = new ICQDatabase( (string) currentValue );
return true;
}
}
}
}
while( _ICQ2003bHKCUPreferencesKey != null )
{
if( !_itf2003bHKCUValueNamesEnumerator.MoveNext() )
{
_ICQ2003bHKCUPreferencesKey = null;
}
else
{
string currentValueName = (string) _itf2003bHKCUValueNamesEnumerator.Current;
if( currentValueName.IndexOf( "Database" ) >= 0 )
{
object currentValue = _ICQ2003bHKCUPreferencesKey.GetValue( currentValueName );
if( currentValue is string )
{
_CurrentDB = new ICQDatabase( (string) currentValue );
return true;
}
}
}
}
}
if( _newDBs.Count > 0 )
{
_CurrentDB = new ICQModernDatabase( (string) _newDBs.Dequeue() );
return true;
}
return false;
}
public void Reset()
{
OpenRegKey( ICQ2003bDefaultPrefsRegKey,
Registry.LocalMachine, out _ICQ2003bHKLMPreferencesKey, out _itf2003bHKLMValueNamesEnumerator );
OpenRegKey( ICQ2003bDefaultPrefsRegKey,
Registry.CurrentUser, out _ICQ2003bHKCUPreferencesKey, out _itf2003bHKCUValueNamesEnumerator );
OpenRegKey( ICQDefaultPrefsRegKey,
Registry.LocalMachine, out _ICQHKLMPreferencesKey, out _itfHKLMValueNamesEnumerator );
OpenRegKey( ICQDefaultPrefsRegKey,
Registry.CurrentUser, out _ICQHKCUPreferencesKey, out _itfHKCUValueNamesEnumerator );
string icqPath = null;
if( _ICQ2003bHKLMPreferencesKey != null )
{
icqPath = (string) _ICQ2003bHKLMPreferencesKey.GetValue( "ICQPath" );
}
if( icqPath == null && _ICQ2003bHKCUPreferencesKey != null )
{
icqPath = (string) _ICQ2003bHKCUPreferencesKey.GetValue( "ICQPath" );
}
_newDBs.Clear();
EnumModernDatabases( icqPath );
}
private static void OpenRegKey( string regKey, RegistryKey baseKey, out RegistryKey key, out IEnumerator values )
{
key = null;
values = null;
try
{
key = baseKey.OpenSubKey( regKey );
}
catch { return; }
if( key != null )
{
values = key.GetValueNames().GetEnumerator();
}
}
private void EnumModernDatabases( string path )
{
if( string.IsNullOrEmpty( path ) )
{
return;
}
do
{
try
{
Convert.ToInt32( IOTools.GetFileName( path ) );
}
catch
{
break;
}
FileInfo[] files = IOTools.GetFiles( path, "messages*.dbf" );
if( files != null && files.Length > 0 )
{
files = IOTools.GetFiles( path, "users*.dbf" );
if( files != null && files.Length > 0 )
{
_newDBs.Enqueue( path );
}
}
}
while( false );
DirectoryInfo[] dirs = IOTools.GetDirectories( path );
if( dirs != null )
{
foreach( DirectoryInfo di in dirs )
{
EnumModernDatabases( IOTools.GetFullName( di ) );
}
}
}
private IICQDatabase _CurrentDB;
private RegistryKey _ICQHKLMPreferencesKey;
private RegistryKey _ICQHKCUPreferencesKey;
private RegistryKey _ICQ2003bHKLMPreferencesKey;
private RegistryKey _ICQ2003bHKCUPreferencesKey;
private IEnumerator _itfHKLMValueNamesEnumerator;
private IEnumerator _itfHKCUValueNamesEnumerator;
private IEnumerator _itf2003bHKLMValueNamesEnumerator;
private IEnumerator _itf2003bHKCUValueNamesEnumerator;
private readonly Queue _newDBs = new Queue();
private const string ICQDefaultPrefsRegKey = @"SOFTWARE\Mirabilis\ICQ\DefaultPrefs";
private const string ICQ2003bDefaultPrefsRegKey = @"SOFTWARE\Mirabilis\ICQ\ICQPro\DefaultPrefs";
}
/**
* ICQ contacts factory serves for merging same contacts, which may
* be stored in different ICQ database files
*/
internal class ContactsFactory
{
private ContactsFactory() {}
public static ContactsFactory GetInstance()
{
return _theInstance;
}
public ICQContact GetContact( int iUIN )
{
ContactEntry theEntry = (ContactEntry) _Contacts[ iUIN ];
if( theEntry == null )
{
ICQContact theContact = new ICQContact();
theContact.UIN = iUIN;
theEntry = new ContactEntry();
theEntry.theContact = theContact;
theEntry.DBVersion = DBVersion.db_Undefined;
_Contacts.Add( iUIN, theEntry);
}
return theEntry.theContact;
}
public ICQContact Update( ICQContact aContact, DBVersion aVersion )
{
ContactEntry theEntry = (ContactEntry) _Contacts[ aContact.UIN ];
if( theEntry == null )
{
theEntry = new ContactEntry();
theEntry.DBVersion = aVersion;
theEntry.theContact = aContact;
_Contacts[ aContact.UIN ] = theEntry;
}
else
{
if( theEntry.DBVersion < aVersion )
{
theEntry.DBVersion = aVersion;
// copy all properties in order to effect all data
// where the contact was stores (messages and so on)
theEntry.theContact.Address = aContact.Address;
theEntry.theContact.Age = aContact.Age;
theEntry.theContact.BirthDate = aContact.BirthDate;
theEntry.theContact.Company = aContact.Company;
theEntry.theContact.eMail = aContact.eMail;
theEntry.theContact.FirstName = aContact.FirstName;
theEntry.theContact.Gender = aContact.Gender;
theEntry.theContact.LastName = aContact.LastName;
theEntry.theContact.NickName = aContact.NickName;
theEntry.theContact.Password = aContact.Password;
theEntry.theContact.Messages = aContact.Messages;
theEntry.theContact.Ignored = aContact.Ignored;
}
}
return theEntry.theContact;
}
private class ContactEntry
{
public ICQContact theContact;
public DBVersion DBVersion;
}
private static readonly ContactsFactory _theInstance = new ContactsFactory();
private readonly IntHashTable _Contacts = new IntHashTable();
}
/**
* ICQ UINs collection (found on computer)
*/
internal class UINsCollection
{
// private static IntArrayList _UINs = new IntArrayList();
private static readonly List _UINs = new List();
private static bool _hasModernDBs;
static UINsCollection()
{
Refresh();
}
public static void Refresh()
{
DBImport.Importer theImporter = DBImport.Importer.GetInstance();
theImporter.Reset();
_hasModernDBs = false;
IntHashSet uins = new IntHashSet();
int lastUIN = 0;
foreach( IICQDatabase D in theImporter )
{
_hasModernDBs = _hasModernDBs || D is ICQModernDatabase;
D.EnumUINsOnly = true;
lastUIN = D.CurrentUIN;
if( lastUIN != 0 )
{
uins.Add( lastUIN );
}
while( D.MoveNext() )
{
if( lastUIN != D.CurrentUIN )
{
uins.Add( lastUIN = D.CurrentUIN );
}
}
}
_UINs.Clear();
foreach( IntHashSet.Entry e in uins )
{
_UINs.Add( e.Key );
}
_UINs.Sort();
}
public static List GetUINs()
{
return _UINs;
}
public static bool HasModernDBs
{
get { return _hasModernDBs; }
}
}
internal class ICQDbImportMisc
{
internal static DateTime GetDate( int year, int month, int day )
{
DateTime date;
try
{
date = new DateTime( year, month, day );
}
catch
{
try
{
date = new DateTime( DateTime.Now.Year, month, day );
}
catch
{
try
{
date = new DateTime( year, 1, day );
}
catch
{
try
{
date = new DateTime( year, month, 1 );
}
catch
{
date = DateTime.Now;
}
}
}
}
return date;
}
}
}