/* Avatar History Plugin --------- This plugin uses the event provided by Avatar Service to automatically back up contacts' avatars when they change. Copyright (C) 2006 Matthew Wild - Email: mwild1@gmail.com 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "stdafx.h" DWORD mirVer; HANDLE hFolder = nullptr; wchar_t profilePath[MAX_PATH]; // database profile path (read at startup only) wchar_t basedir[MAX_PATH]; CMPlugin g_plugin; MWindowList hAvatarWindowsList = nullptr; int OptInit(WPARAM wParam, LPARAM lParam); wchar_t* GetHistoryFolder(wchar_t *fn); wchar_t* GetProtocolFolder(wchar_t *fn, char *proto); wchar_t* GetOldStyleAvatarName(wchar_t *fn, MCONTACT hContact); void InitMenuItem(); ///////////////////////////////////////////////////////////////////////////////////////// PLUGININFOEX pluginInfoEx = { sizeof(PLUGININFOEX), __PLUGIN_NAME, PLUGIN_MAKE_VERSION(__MAJOR_VERSION, __MINOR_VERSION, __RELEASE_NUM, __BUILD_NUM), __DESCRIPTION, __AUTHOR, __COPYRIGHT, __AUTHORWEB, UNICODE_AWARE, // {DBE8C990-7AA0-458D-BAB7-33EB07238E71} {0xdbe8c990, 0x7aa0, 0x458d, {0xba, 0xb7, 0x33, 0xeb, 0x7, 0x23, 0x8e, 0x71}} }; CMPlugin::CMPlugin() : PLUGIN(MODULENAME, pluginInfoEx) {} ///////////////////////////////////////////////////////////////////////////////////////// // services static INT_PTR GetCachedAvatar(WPARAM wParam, LPARAM lParam) { wchar_t hash[128]; wcsncpy_s(hash, (wchar_t*)lParam, _TRUNCATE); ConvertToFilename(hash, _countof(hash)); return (INT_PTR)GetCachedAvatar((char*)wParam, hash); } static INT_PTR IsEnabled(WPARAM wParam, LPARAM) { MCONTACT hContact = (MCONTACT)wParam; return ContactEnabled(hContact, "LogToDisk", AVH_DEF_LOGTODISK) || ContactEnabled(hContact, "AvatarPopups", AVH_DEF_AVPOPUPS) || ContactEnabled(hContact, "LogToHistory", AVH_DEF_LOGTOHISTORY); } ///////////////////////////////////////////////////////////////////////////////////////// // events // fired when the contacts avatar changes // wParam = hContact // lParam = struct avatarCacheEntry *cacheEntry // the event CAN pass a NULL pointer in lParam which means that the avatar has changed, // but is no longer valid (happens, when a contact removes his avatar, for example). // DONT DESTROY the bitmap handle passed in the struct avatarCacheEntry * // // It is also possible that this event passes 0 as wParam (hContact), in which case, // a protocol picture (pseudo - avatar) has been changed. static int AvatarChanged(WPARAM hContact, LPARAM lParam) { if (hContact == NULL) return 0; char *proto = Proto_GetBaseAccountName(hContact); if (proto == nullptr) return 0; if (mir_strcmp(META_PROTO, proto) == 0) return 0; DBVARIANT dbvOldHash = { 0 }; bool ret = (g_plugin.getWString(hContact, "AvatarHash", &dbvOldHash) == 0); CONTACTAVATARCHANGEDNOTIFICATION* avatar = (CONTACTAVATARCHANGEDNOTIFICATION*)lParam; if (avatar == nullptr) { if (!ret || !mir_wstrcmp(dbvOldHash.pwszVal, L"-")) { //avoid duplicate "removed avatar" notifications //do not notify on an empty profile ShowDebugPopup(hContact, L"AVH Debug", L"Removed avatar, no avatar before... skipping"); db_free(&dbvOldHash); return 0; } Skin_PlaySound("avatar_removed"); // Is a flash avatar or avs could not load it g_plugin.setWString(hContact, "AvatarHash", L"-"); if (ContactEnabled(hContact, "AvatarPopups", AVH_DEF_AVPOPUPS) && opts.popup_show_removed) ShowPopup(hContact, nullptr, opts.popup_removed); } else { if (ret && !mir_wstrcmp(dbvOldHash.pwszVal, avatar->hash)) { // same avatar hash, skipping ShowDebugPopup(hContact, L"AVH Debug", L"Hashes are the same... skipping"); db_free(&dbvOldHash); return 0; } Skin_PlaySound("avatar_changed"); g_plugin.setWString(hContact, "AvatarHash", avatar->hash); wchar_t history_filename[MAX_PATH] = L""; if (ContactEnabled(hContact, "LogToDisk", AVH_DEF_LOGTODISK)) { if (!opts.log_store_as_hash) { if (opts.log_per_contact_folders) { GetOldStyleAvatarName(history_filename, hContact); if (CopyImageFile(avatar->filename, history_filename)) ShowPopup(hContact, TranslateT("Avatar history: Unable to save avatar"), history_filename); else ShowDebugPopup(hContact, L"AVH Debug: File copied successfully", history_filename); MCONTACT hMetaContact = db_mc_getMeta(hContact); if (hMetaContact && ContactEnabled(hMetaContact, "LogToDisk", AVH_DEF_LOGTOHISTORY)) { wchar_t filename[MAX_PATH] = L""; GetOldStyleAvatarName(filename, hMetaContact); if (CopyImageFile(avatar->filename, filename)) ShowPopup(hContact, TranslateT("Avatar history: Unable to save avatar"), filename); else ShowDebugPopup(hContact, L"AVH Debug: File copied successfully", filename); } } } else { // See if we already have the avatar wchar_t hash[128]; wcsncpy_s(hash, avatar->hash, _TRUNCATE); ConvertToFilename(hash, _countof(hash)); wchar_t *file = GetCachedAvatar(proto, hash); if (file != nullptr) { mir_wstrncpy(history_filename, file, _countof(history_filename)); mir_free(file); } else { if (opts.log_keep_same_folder) GetHistoryFolder(history_filename); else GetProtocolFolder(history_filename, proto); mir_snwprintf(history_filename, L"%s\\%s", history_filename, hash); if (CopyImageFile(avatar->filename, history_filename)) ShowPopup(hContact, TranslateT("Avatar history: Unable to save avatar"), history_filename); else ShowDebugPopup(hContact, L"AVH Debug: File copied successfully", history_filename); } if (opts.log_per_contact_folders) { CreateOldStyleShortcut(hContact, history_filename); MCONTACT hMetaContact = db_mc_getMeta(hContact); if (hMetaContact && ContactEnabled(hMetaContact, "LogToDisk", AVH_DEF_LOGTOHISTORY)) CreateOldStyleShortcut(hMetaContact, history_filename); } } } if (ContactEnabled(hContact, "AvatarPopups", AVH_DEF_AVPOPUPS) && opts.popup_show_changed) ShowPopup(hContact, nullptr, opts.popup_changed); if (ContactEnabled(hContact, "LogToHistory", AVH_DEF_LOGTOHISTORY)) { wchar_t rel_path[MAX_PATH]; PathToRelativeW(history_filename, rel_path); T2Utf blob(rel_path); DBEVENTINFO dbei = {}; dbei.szModule = Proto_GetBaseAccountName(hContact); dbei.flags = DBEF_READ | DBEF_UTF; dbei.timestamp = (DWORD)time(0); dbei.eventType = EVENTTYPE_AVATAR_CHANGE; dbei.cbBlob = (DWORD)mir_strlen(blob) + 1; dbei.pBlob = blob; db_event_add(hContact, &dbei); } } return 0; } static int PreShutdown(WPARAM, LPARAM) { WindowList_Broadcast(hAvatarWindowsList, WM_CLOSE, 0, 0); return 0; } static int ModulesLoaded(WPARAM, LPARAM) { mir_snwprintf(basedir, L"%s\\Avatars History", profilePath); hFolder = FoldersRegisterCustomPathW(LPGEN("Avatars"), LPGEN("Avatar History"), PROFILE_PATHW L"\\" CURRENT_PROFILEW L"\\Avatars History"); InitPopups(); HookEvent(ME_AV_CONTACTAVATARCHANGED, AvatarChanged); return 0; } ///////////////////////////////////////////////////////////////////////////////////////// static INT_PTR CALLBACK FirstRunDlgProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM) { switch (uMsg) { case WM_INITDIALOG: SendMessage(hwnd, WM_SETICON, ICON_BIG, (LPARAM)createDefaultOverlayedIcon(TRUE)); SendMessage(hwnd, WM_SETICON, ICON_SMALL, (LPARAM)createDefaultOverlayedIcon(FALSE)); TranslateDialogDefault(hwnd); CheckDlgButton(hwnd, IDC_MIR_PROTO, BST_CHECKED); break; case WM_COMMAND: switch (LOWORD(wParam)) { case IDOK: int ret = 0; if (IsDlgButtonChecked(hwnd, IDC_MIR_SAME)) ret = IDC_MIR_SAME; else if (IsDlgButtonChecked(hwnd, IDC_MIR_PROTO)) ret = IDC_MIR_PROTO; else if (IsDlgButtonChecked(hwnd, IDC_MIR_SHORT)) ret = IDC_MIR_SHORT; else if (IsDlgButtonChecked(hwnd, IDC_SHORT)) ret = IDC_SHORT; else if (IsDlgButtonChecked(hwnd, IDC_DUP)) ret = IDC_DUP; EndDialog(hwnd, ret); return TRUE; } break; case WM_CLOSE: EndDialog(hwnd, 0); return TRUE; } return FALSE; } int CMPlugin::Load() { CoInitialize(nullptr); // Is first run? if (g_plugin.getByte("FirstRun", 1)) { // Show dialog int ret = DialogBoxParam(g_plugin.getInst(), MAKEINTRESOURCE(IDD_FIRST_RUN), nullptr, FirstRunDlgProc, 0); if (ret == 0) return -1; // Write settings g_plugin.setByte("LogToDisk", 1); if (ret == IDC_MIR_SAME) g_plugin.setByte("LogKeepSameFolder", 1); else g_plugin.setByte("LogKeepSameFolder", 0); if (ret == IDC_MIR_SHORT || ret == IDC_SHORT || ret == IDC_DUP) g_plugin.setByte("LogPerContactFolders", 1); else g_plugin.setByte("LogPerContactFolders", 0); if (ret == IDC_DUP) g_plugin.setByte("StoreAsHash", 0); else g_plugin.setByte("StoreAsHash", 1); if (ret == IDC_MIR_SAME || ret == IDC_MIR_PROTO || ret == IDC_MIR_SHORT) g_plugin.setByte("LogToHistory", 1); else g_plugin.setByte("LogToHistory", 0); g_plugin.setByte("FirstRun", 0); } LoadOptions(); HookEvent(ME_SYSTEM_MODULESLOADED, ModulesLoaded); HookEvent(ME_SYSTEM_PRESHUTDOWN, PreShutdown); HookEvent(ME_OPT_INITIALISE, OptInit); HookEvent(ME_SKIN_ICONSCHANGED, IcoLibIconsChanged); HookEvent(ME_CLIST_PREBUILDCONTACTMENU, PreBuildContactMenu); CreateServiceFunction(MS_AVATARHISTORY_ENABLED, IsEnabled); CreateServiceFunction(MS_AVATARHISTORY_GET_CACHED_AVATAR, GetCachedAvatar); Profile_GetPathW(MAX_PATH, profilePath); g_plugin.addSound("avatar_changed", LPGENW("Avatar history"), LPGENW("Contact changed avatar")); g_plugin.addSound("avatar_removed", LPGENW("Avatar history"), LPGENW("Contact removed avatar")); hAvatarWindowsList = WindowList_Create(); SetupIcoLib(); InitMenuItem(); return 0; } int CMPlugin::Unload() { WindowList_Destroy(hAvatarWindowsList); return 0; }