/* Import plugin for Miranda NG Copyright (C) 2012 George Hazan 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. */ #include "import.h" time_t dwSinceDate = 0; HWND hdlgProgress; static DWORD nDupes, nContactsCount, nMessagesCount, nGroupsCount, nSkippedEvents, nSkippedContacts; static MIDatabase *srcDb, *dstDb; ///////////////////////////////////////////////////////////////////////////////////////// static int myGet(MCONTACT hContact, const char *szModule, const char *szSetting, DBVARIANT *dbv) { dbv->type = 0; return srcDb->GetContactSetting(hContact, szModule, szSetting, dbv); } static TCHAR* myGetWs(MCONTACT hContact, const char *szModule, const char *szSetting) { DBVARIANT dbv = { DBVT_TCHAR }; return srcDb->GetContactSettingStr(hContact, szModule, szSetting, &dbv) ? NULL : dbv.ptszVal; } static BOOL myGetS(MCONTACT hContact, const char *szModule, const char *szSetting, char* dest) { DBVARIANT dbv = { DBVT_ASCIIZ }; dbv.pszVal = dest; dbv.cchVal = 100; return srcDb->GetContactSettingStatic(hContact, szModule, szSetting, &dbv); } static void mySet(MCONTACT hContact, const char *module, const char *var, DBVARIANT *dbv) { DBCONTACTWRITESETTING dbw; dbw.szModule = module; dbw.szSetting = var; dbw.value = *dbv; dstDb->WriteContactSetting(hContact, &dbw); } ///////////////////////////////////////////////////////////////////////////////////////// static MCONTACT HContactFromNumericID(char *pszProtoName, char *pszSetting, DWORD dwID) { MCONTACT hContact = dstDb->FindFirstContact(); while (hContact != NULL) { if ( db_get_dw((HCONTACT)hContact, pszProtoName, pszSetting, 0) == dwID) { char* szProto = GetContactProto((HCONTACT)hContact); if (szProto != NULL && !lstrcmpA(szProto, pszProtoName)) return hContact; } hContact = dstDb->FindNextContact((MCONTACT)hContact); } return INVALID_CONTACT_ID; } static MCONTACT HContactFromID(char *pszProtoName, char *pszSetting, TCHAR *pwszID) { MCONTACT hContact = dstDb->FindFirstContact(); while (hContact != NULL) { char* szProto = GetContactProto((HCONTACT)hContact); if ( !lstrcmpA(szProto, pszProtoName)) { ptrW id(db_get_tsa((HCONTACT)hContact, pszProtoName, pszSetting)); if ( !lstrcmp(pwszID, id)) return hContact; } hContact = (MCONTACT)dstDb->FindNextContact(hContact); } return INVALID_CONTACT_ID; } static MCONTACT HistoryImportFindContact(HWND hdlgProgress, char* szModuleName, DWORD uin, int addUnknown) { MCONTACT hContact = HContactFromNumericID(szModuleName, "UIN", uin); if (hContact == NULL) { AddMessage( LPGENT("Ignored event from/to self")); return INVALID_CONTACT_ID; } if (hContact != INVALID_CONTACT_ID) return hContact; if (!addUnknown) return INVALID_CONTACT_ID; hContact = (MCONTACT)CallService(MS_DB_CONTACT_ADD, 0, 0); CallService(MS_PROTO_ADDTOCONTACT, hContact, (LPARAM)szModuleName); db_set_dw((HCONTACT)hContact, szModuleName, "UIN", uin); AddMessage( LPGENT("Added contact %u (found in history)"), uin ); return hContact; } ///////////////////////////////////////////////////////////////////////////////////////// static MCONTACT AddContact(HWND hdlgProgress, char* szProto, char* pszUniqueSetting, DBVARIANT* id, const TCHAR* pszUserID, TCHAR *nick, TCHAR *group) { MCONTACT hContact = (MCONTACT)CallService(MS_DB_CONTACT_ADD, 0, 0); if (CallService(MS_PROTO_ADDTOCONTACT, hContact, (LPARAM)szProto) != 0) { CallService(MS_DB_CONTACT_DELETE, hContact, 0); AddMessage(LPGENT("Failed to add %S contact %s"), szProto, pszUserID); return INVALID_CONTACT_ID; } mySet(hContact, szProto, pszUniqueSetting, id); CreateGroup(group, (HCONTACT)hContact); if (nick && *nick) { db_set_ws((HCONTACT)hContact, "CList", "MyHandle", nick); AddMessage(LPGENT("Added %S contact %s, '%s'"), szProto, pszUserID, nick); } else AddMessage(LPGENT("Added %S contact %s"), szProto, pszUserID); srcDb->FreeVariant(id); return hContact; } static int ImportGroup(const char* szSettingName, LPARAM lParam) { int* pnGroups = (int*)lParam; TCHAR* tszGroup = myGetWs(NULL, "CListGroups", szSettingName); if (tszGroup != NULL) { if ( CreateGroup( tszGroup+1, NULL )) pnGroups[0]++; mir_free(tszGroup); } return 0; } ///////////////////////////////////////////////////////////////////////////////////////// static int ImportGroups() { int nGroups = 0; DBCONTACTENUMSETTINGS param = { 0 }; param.szModule = "CListGroups"; param.pfnEnumProc = ImportGroup; param.lParam = (LPARAM)&nGroups; srcDb->EnumContactSettings(NULL, ¶m); return nGroups; } static MCONTACT ImportContact(MCONTACT hSrc) { MCONTACT hDst; TCHAR id[ 40 ], *pszUniqueID; char szProto[100]; // Check what protocol this contact belongs to if (myGetS(hSrc, "Protocol", "p", szProto)) { AddMessage(LPGENT("Skipping contact with no protocol")); return NULL; } if (!IsProtocolLoaded(szProto)) { AddMessage(LPGENT("Skipping contact, %S not installed."), szProto); return NULL; } // Skip protocols with no unique id setting (some non IM protocols return NULL) char* pszUniqueSetting = (char*)CallProtoService(szProto, PS_GETCAPS, PFLAG_UNIQUEIDSETTING, 0); if (!pszUniqueSetting || (INT_PTR)pszUniqueSetting == CALLSERVICE_NOTFOUND) { AddMessage(LPGENT("Skipping non-IM contact (%S)"), szProto); return NULL; } DBVARIANT dbv; if (myGet(hSrc, szProto, pszUniqueSetting, &dbv)) { AddMessage(LPGENT("Skipping %S contact, ID not found"), szProto); return NULL; } // Does the contact already exist? switch (dbv.type) { case DBVT_DWORD: pszUniqueID = _ltot(dbv.dVal, id, 10); hDst = HContactFromNumericID(szProto, pszUniqueSetting, dbv.dVal); break; case DBVT_ASCIIZ: pszUniqueID = NEWTSTR_ALLOCA(_A2T(dbv.pszVal)); hDst = HContactFromID(szProto, pszUniqueSetting, pszUniqueID); break; case DBVT_WCHAR: pszUniqueID = NEWTSTR_ALLOCA(dbv.ptszVal); hDst = HContactFromID(szProto, pszUniqueSetting, pszUniqueID); break; } if (hDst != INVALID_CONTACT_ID) { AddMessage(LPGENT("Skipping duplicate %S contact %s"), szProto, pszUniqueID); srcDb->FreeVariant(&dbv); return NULL; } TCHAR *tszGroup = myGetWs(hSrc, "CList", "Group"), *tszNick = myGetWs(hSrc, "CList", "MyHandle"); if (tszNick == NULL) tszNick = myGetWs(hSrc, szProto, "Nick"); hDst = AddContact(hdlgProgress, szProto, pszUniqueSetting, &dbv, pszUniqueID, tszNick, tszGroup); mir_free(tszGroup), mir_free(tszNick); if (hDst != INVALID_CONTACT_ID) { // Hidden? if (!myGet(hSrc, "CList", "Hidden", &dbv)) { mySet(hDst, "CList", "Hidden", &dbv); srcDb->FreeVariant(&dbv); } // Ignore settings if (!myGet(hSrc, "Ignore", "Mask1", &dbv)) { mySet(hDst, "Ignore", "Mask1", &dbv); srcDb->FreeVariant(&dbv); } // Apparent mode if (!myGet(hSrc, szProto, "ApparentMode", &dbv)) { mySet(hDst, szProto, "ApparentMode", &dbv); srcDb->FreeVariant(&dbv); } // Nick if (!myGet(hSrc, szProto, "Nick", &dbv)) { mySet(hDst, szProto, "Nick", &dbv); srcDb->FreeVariant(&dbv); } // Myhandle if (!myGet(hSrc, szProto, "MyHandle", &dbv)) { mySet(hDst, szProto, "MyHandle", &dbv); srcDb->FreeVariant(&dbv); } // First name if (!myGet(hSrc, szProto, "FirstName", &dbv)) { mySet(hDst, szProto, "FirstName", &dbv); srcDb->FreeVariant(&dbv); } // Last name if (!myGet(hSrc, szProto, "LastName", &dbv)) { mySet(hDst, szProto, "LastName", &dbv); srcDb->FreeVariant(&dbv); } // About if (!myGet(hSrc, szProto, "About", &dbv)) { mySet(hDst, szProto, "About", &dbv); srcDb->FreeVariant(&dbv); } } else AddMessage(LPGENT("Unknown error while adding %S contact %s"), szProto, pszUniqueID); return hDst; } // This function should always be called after contact import. That is // why there are no messages for errors related to contacts. Those // would only be a repetition of the messages printed during contact // import. static MCONTACT convertContact(MCONTACT hContact) { // Check what protocol this contact belongs to char szProto[100]; if (myGetS(hContact, "Protocol", "p", szProto)) return INVALID_CONTACT_ID; // Protocol installed? if (!IsProtocolLoaded(szProto)) return INVALID_CONTACT_ID; // Is contact in database? char* pszUniqueSetting = (char*)CallProtoService(szProto, PS_GETCAPS, PFLAG_UNIQUEIDSETTING, 0); // Skip protocols with no unique id setting (some non IM protocols return NULL) MCONTACT hDst = INVALID_CONTACT_ID; if (pszUniqueSetting && (INT_PTR)pszUniqueSetting != CALLSERVICE_NOTFOUND) { DBVARIANT dbv; if (!myGet(hContact, szProto, pszUniqueSetting, &dbv)) { switch (dbv.type) { case DBVT_DWORD: hDst = HContactFromNumericID(szProto, pszUniqueSetting, dbv.dVal); break; case DBVT_ASCIIZ: hDst = HContactFromID(szProto, pszUniqueSetting, _A2T(dbv.pszVal)); break; case DBVT_WCHAR: hDst = HContactFromID(szProto, pszUniqueSetting, dbv.ptszVal); break; } srcDb->FreeVariant(&dbv); } } return hDst; } static void ImportHistory(MCONTACT hContact, PROTOACCOUNT **protocol, int protoCount) { // Is it contats history import? MCONTACT hDst = (protoCount == 0) ? convertContact(hContact) : NULL; //system history import // OK to import this chain? if (hDst == INVALID_CONTACT_ID) { nSkippedContacts++; return; } int i = 0, skipAll = 0; DWORD cbAlloc = 4096; BYTE* eventBuf = (PBYTE)mir_alloc(cbAlloc); bool bIsVoidContact = dstDb->GetEventCount(hDst) == 0; // Get the start of the event chain HANDLE hEvent = srcDb->FindFirstEvent(hContact); while (hEvent) { int skip = 0; // Copy the event and import it DBEVENTINFO dbei = { sizeof(DBEVENTINFO) }; dbei.cbBlob = srcDb->GetBlobSize(hEvent); if (dbei.cbBlob > cbAlloc) { cbAlloc = dbei.cbBlob + 4096 - dbei.cbBlob % 4096; eventBuf = (PBYTE)mir_realloc(eventBuf, cbAlloc); } dbei.pBlob = eventBuf; if (!srcDb->GetEvent(hEvent, &dbei)) { // check protocols during system history import if (hDst == NULL) { skipAll = 1; for (int i = 0; i < protoCount; i++) if (!strcmp(dbei.szModule, protocol[i]->szModuleName)) { skipAll = 0; break; } skip = skipAll; } // custom filtering if (!skip && nImportOption == IMPORT_CUSTOM) { BOOL sent = (dbei.flags & DBEF_SENT); if (dbei.timestamp < (DWORD)dwSinceDate) skip = 1; if (!skip) { if (hDst) { skip = 1; switch (dbei.eventType) { case EVENTTYPE_MESSAGE: if ((sent ? IOPT_MSGSENT : IOPT_MSGRECV) & nCustomOptions) skip = 0; break; case EVENTTYPE_FILE: if ((sent ? IOPT_FILESENT : IOPT_FILERECV) & nCustomOptions) skip = 0; break; case EVENTTYPE_URL: if ((sent ? IOPT_URLSENT : IOPT_URLRECV) & nCustomOptions) skip = 0; break; default: if ((sent ? IOPT_OTHERSENT : IOPT_OTHERRECV) & nCustomOptions) skip = 0; break; } } else if (!(nCustomOptions & IOPT_SYSTEM)) skip = 1; } if (skip) nSkippedEvents++; } if (!skip) { // Check for duplicate entries if (!IsDuplicateEvent((HCONTACT)hDst, dbei)) { // Add dbevent if (!bIsVoidContact) dbei.flags &= ~DBEF_FIRST; if (dstDb->AddEvent(hDst, &dbei) != NULL) nMessagesCount++; else AddMessage(LPGENT("Failed to add message")); } else nDupes++; } } if (!(i % 10)) { MSG msg; if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) { TranslateMessage(&msg); DispatchMessage(&msg); } } // skip this chain if needed if (skipAll) break; // Get next event hEvent = srcDb->FindNextEvent(hEvent); i++; } mir_free(eventBuf); } ///////////////////////////////////////////////////////////////////////////////////////// void MirandaImport(HWND hdlg) { DWORD dwTimer; char* pszModuleName = NULL; // Just to keep the macros happy hdlgProgress = hdlg; if ((dstDb = GetCurrentDatabase()) == NULL) { AddMessage(LPGENT("Error retrieving current profile, exiting.")); return; } DATABASELINK* dblink = FindDatabasePlugin(importFile); if (dblink == NULL) { AddMessage(LPGENT("There's no database driver to open the input file, exiting.")); return; } if ((srcDb = dblink->Load(importFile)) == NULL) { AddMessage(LPGENT("Error loading source file, exiting.")); return; } // Reset statistics nSkippedEvents = 0; nDupes = 0; nContactsCount = 0; nMessagesCount = 0; nGroupsCount = 0; nSkippedContacts = 0; SetProgress(0); // Get number of contacts int nNumberOfContacts = srcDb->GetContactCount(); AddMessage(LPGENT("Number of contacts in database: %d"), nNumberOfContacts); AddMessage(_T("")); // Configure database for fast writing dstDb->SetCacheSafetyMode(FALSE); // Start benchmark timer dwTimer = time(NULL); // Import Groups if (nImportOption == IMPORT_ALL || (nCustomOptions & IOPT_GROUPS)) { AddMessage(LPGENT("Importing groups.")); nGroupsCount = ImportGroups(); if (nGroupsCount == -1) AddMessage(LPGENT("Group import failed.")); AddMessage(_T("")); } // End of Import Groups // Import Contacts if (nImportOption != IMPORT_CUSTOM || (nCustomOptions & IOPT_CONTACTS)) { AddMessage(LPGENT("Importing contacts.")); int i = 1; MCONTACT hContact = srcDb->FindFirstContact(); while (hContact != NULL) { if (ImportContact(hContact)) nContactsCount++; // Update progress bar SetProgress(100 * i / nNumberOfContacts); i++; // Process queued messages MSG msg; if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) { TranslateMessage(&msg); DispatchMessage(&msg); } // Get next contact in chain hContact = srcDb->FindNextContact(hContact); } } else AddMessage(LPGENT("Skipping new contacts import.")); AddMessage(_T("")); // End of Import Contacts // Import history if (nImportOption != IMPORT_CONTACTS) { // Import NULL contact message chain if (nImportOption == IMPORT_ALL || (nCustomOptions & IOPT_SYSTEM)) { AddMessage(LPGENT("Importing system history.")); int protoCount; PROTOACCOUNT **accs; CallService(MS_PROTO_ENUMACCOUNTS, (WPARAM)&protoCount, (LPARAM)&accs); if (protoCount > 0) ImportHistory(NULL, accs, protoCount); } else AddMessage(LPGENT("Skipping system history import.")); AddMessage(_T("")); // Import other contact messages if (nImportOption == IMPORT_ALL || (nCustomOptions & 2046)) { // 2 - 1024 types AddMessage(LPGENT("Importing history.")); MCONTACT hContact = srcDb->FindFirstContact(); for (int i = 1; hContact != NULL; i++) { ImportHistory(hContact, NULL, NULL); SetProgress(100 * i / nNumberOfContacts); hContact = srcDb->FindNextContact(hContact); } } else AddMessage(LPGENT("Skipping history import.")); AddMessage(_T("")); } // End of Import History // Restore database writing mode dstDb->SetCacheSafetyMode(TRUE); // Clean up before exit dblink->Unload(srcDb); // Stop timer dwTimer = time(NULL) - dwTimer; // Print statistics AddMessage(LPGENT("Import completed in %d seconds."), dwTimer); SetProgress(100); AddMessage((nImportOption == IMPORT_CONTACTS) ? LPGENT("Added %d contacts and %d groups.") : LPGENT("Added %d contacts, %d groups and %d events."), nContactsCount, nGroupsCount, nMessagesCount); if (nImportOption != IMPORT_CONTACTS) { if (nSkippedContacts) AddMessage(LPGENT("Skipped %d contacts."), nSkippedContacts); AddMessage((nImportOption == IMPORT_CUSTOM) ? LPGENT("Skipped %d duplicates and %d filtered events.") : LPGENT("Skipped %d duplicates."), nDupes, nSkippedEvents); } }