From a8c2c6d3903b1b61b02941d19739d42cd5fe88eb Mon Sep 17 00:00:00 2001 From: George Hazan Date: Thu, 28 Dec 2023 16:18:04 +0300 Subject: Import: JSON export code moved into the Import plugin to maintain consistency between export & import --- include/m_db_int.h | 53 +++++++----- libs/win32/mir_app.lib | Bin 276800 -> 277558 bytes libs/win64/mir_app.lib | Bin 276086 -> 276852 bytes plugins/Import/src/textjson.cpp | 153 +++++++++++++++++++++++++++++++---- plugins/NewStory/src/history_dlg.cpp | 113 +++++--------------------- src/mir_app/src/mir_app.def | 3 + src/mir_app/src/mir_app64.def | 3 + 7 files changed, 193 insertions(+), 132 deletions(-) diff --git a/include/m_db_int.h b/include/m_db_int.h index fa25ddcc26..baa1395bf3 100644 --- a/include/m_db_int.h +++ b/include/m_db_int.h @@ -309,6 +309,19 @@ public: STDMETHODIMP_(int) UpdateEventId(MEVENT hDbEvent, const char *szId) override; }; +///////////////////////////////////////////////////////////////////////////////////////// +// Export database, that can export a contact or an event in the specified format + +struct MIR_APP_EXPORT MDatabaseExport : public MDatabaseReadonly +{ + MDatabaseExport() {} + + STDMETHOD_(BOOL, BeginExport)(void) PURE; + STDMETHOD_(BOOL, ExportContact)(MCONTACT hContact) PURE; + STDMETHOD_(BOOL, ExportEvent)(const DB::EventInfo &ddei) PURE; + STDMETHOD_(BOOL, EndExport)(void) PURE; +}; + ///////////////////////////////////////////////////////////////////////////////////////// // Each database plugin should register itself using this structure @@ -328,7 +341,7 @@ public: #define MDB_CAPS_CREATE 0x0001 // new database can be created #define MDB_CAPS_COMPACT 0x0002 // database can be compacted #define MDB_CAPS_CHECK 0x0004 // database can be checked - +#define MDB_CAPS_EXPORT 0x0008 // driver can export contacts/events struct DATABASELINK { @@ -336,32 +349,28 @@ struct DATABASELINK char* szShortName; // uniqie short database name wchar_t* szFullName; // in English, auto-translated by the core - /* - profile: pointer to a string which contains full path + name - Affect: The database plugin should create the profile, the filepath will not exist at - the time of this call, profile will be C:\..\.dat - Returns: 0 on success, non zero on failure - error contains extended error information, see EMKPRF_* - */ + // profile: pointer to a string which contains full path + name + // The database plugin should create the profile, the filepath will not exist at + // the time of this call, profile will be C:\..\.dat + // Returns: 0 on success, non zero on failure - error contains extended error information, see EMKPRF_* int (*makeDatabase)(const wchar_t *profile); - /* - profile: [in] a null terminated string to file path of selected profile - error: [in/out] pointer to an int to set with error if any - Affect: Ask the database plugin if it supports the given profile, if it does it will - return 0, if it doesnt return 1, with the error set in error -- EGROKPRF_* can be valid error - condition, most common error would be [EGROKPRF_UNKHEADER] - Note: Just because 1 is returned, doesnt mean the profile is not supported, the profile might be damaged - etc. - Returns: 0 on success, non zero on failure - */ + // profile: [in] a null terminated string to file path of selected profile + // error: [in/out] pointer to an int to set with error if any + // Asks the database plugin if it supports the given profile, if it does it will + // return 0, if it doesnt return 1, with the error set in error -- EGROKPRF_* can be valid error + // condition, most common error would be [EGROKPRF_UNKHEADER] + // Note: Just because 1 is returned, doesnt mean the profile is not supported, the profile might be damaged etc. + // Returns: 0 on success, non zero on failure int (*grokHeader)(const wchar_t *profile); - /* - Affect: Tell the database to create all services/hooks that a 3.xx legacy database might support into link, - which is a PLUGINLINK structure - Returns: 0 on success, nonzero on failure - */ + // Loads the database and tells it to create all services/hooks + // Returns: 0 on success, nonzero on failure MDatabaseCommon* (*Load)(const wchar_t *profile, BOOL bReadOnly); + + // Prepares the export from the currently opened database into the specified file + // Should be implemented if capabilities contains MDB_CAPS_EXPORT + MDatabaseExport* (*Export)(const wchar_t *profile); }; ///////////////////////////////////////////////////////////////////////////////////////// diff --git a/libs/win32/mir_app.lib b/libs/win32/mir_app.lib index 92640a1e1a..1e603f9851 100644 Binary files a/libs/win32/mir_app.lib and b/libs/win32/mir_app.lib differ diff --git a/libs/win64/mir_app.lib b/libs/win64/mir_app.lib index d7cf9c5710..edfe9d3eb1 100644 Binary files a/libs/win64/mir_app.lib and b/libs/win64/mir_app.lib differ diff --git a/plugins/Import/src/textjson.cpp b/plugins/Import/src/textjson.cpp index db0adcd73d..5f45920dee 100644 --- a/plugins/Import/src/textjson.cpp +++ b/plugins/Import/src/textjson.cpp @@ -24,7 +24,22 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. #include -static int mc_makeDatabase(const wchar_t*) +static const char *pSettings[] = +{ + LPGEN("FirstName"), + LPGEN("LastName"), + LPGEN("e-mail"), + LPGEN("Nick"), + LPGEN("Age"), + LPGEN("Gender"), + LPGEN("City"), + LPGEN("State"), + LPGEN("Phone"), + LPGEN("Homepage"), + LPGEN("About") +}; + +static int json_makeDatabase(const wchar_t*) { return 1; } @@ -37,11 +52,13 @@ static int CompareModules(const char *p1, const char *p2) return mir_strcmp(p1, p2); } -class CDbxJson : public MDatabaseReadonly, public MZeroedObject +class CDbxJson : public MDatabaseExport, public MZeroedObject { JSONNode *m_root = nullptr; LIST m_events; LIST m_modules; + FILE *m_out = nullptr; + bool m_bAppendOnly = false; public: CDbxJson() : @@ -71,7 +88,7 @@ public: return EGROKPRF_CANTREAD; DWORD dwSize = GetFileSize(hFile, nullptr), dwRead; - ptrA szFile((char*)mir_alloc(dwSize + 1)); + ptrA szFile((char *)mir_alloc(dwSize + 1)); BOOL r = ReadFile(hFile, szFile, dwSize, &dwRead, nullptr); CloseHandle(hFile); if (!r) @@ -149,10 +166,10 @@ public: std::string szModule = node["module"].as_string(); if (!szModule.empty()) { - dbei->szModule = m_modules.find((char*)szModule.c_str()); + dbei->szModule = m_modules.find((char *)szModule.c_str()); if (dbei->szModule == nullptr) { dbei->szModule = mir_strdup(szModule.c_str()); - m_modules.insert((char*)dbei->szModule); + m_modules.insert((char *)dbei->szModule); } } @@ -169,7 +186,7 @@ public: if (auto &size = node["size"]) blob.setSize(size.as_int()); - blob.write(*(DB::EventInfo*)dbei); + blob.write(*(DB::EventInfo *)dbei); } else { std::string szBody = node["body"].as_string(); @@ -209,13 +226,13 @@ public: if ((int)iEvent >= m_events.getCount()) return 0; - return iEvent+1; + return iEvent + 1; } STDMETHODIMP_(MEVENT) FindLastEvent(MCONTACT) override { int numEvents = m_events.getCount(); - return numEvents ? numEvents-1 : 0; + return numEvents ? numEvents - 1 : 0; } STDMETHODIMP_(MEVENT) FindPrevEvent(MCONTACT, MEVENT iEvent) override @@ -223,18 +240,109 @@ public: if (iEvent <= 1) return 0; - return iEvent-1; + return iEvent - 1; } STDMETHODIMP_(DATABASELINK *) GetDriver(); + + ////////////////////////////////////////////////////////////////////////////////////// + // Export interface + + int Create(const wchar_t *profile) + { + m_out = _wfopen(profile, L"wt"); + return (m_out == nullptr) ? EGROKPRF_CANTREAD : EGROKPRF_NOERROR; + } + + STDMETHODIMP_(int) BeginExport() override + { + return 0; + } + + STDMETHODIMP_(int) ExportContact(MCONTACT hContact) override + { + char *szProto = Proto_GetBaseAccountName(hContact); + ptrW id(Contact::GetInfo(CNF_UNIQUEID, hContact, szProto)); + ptrW nick(Contact::GetInfo(CNF_DISPLAY, hContact, szProto)); + const char *uid = Proto_GetUniqueId(szProto); + + JSONNode pRoot, pInfo, pHist(JSON_ARRAY); + pInfo.set_name("info"); + if (szProto) + pInfo.push_back(JSONNode("proto", szProto)); + + if (id != NULL) + pInfo.push_back(JSONNode(uid, T2Utf(id).get())); + + for (auto &it : pSettings) { + wchar_t *szValue = db_get_wsa(hContact, szProto, it); + if (szValue) + pInfo.push_back(JSONNode(it, T2Utf(szValue).get())); + mir_free(szValue); + } + + pRoot.push_back(pInfo); + + pHist.set_name("history"); + pRoot.push_back(pHist); + + fputs(pRoot.write_formatted().c_str(), m_out); + fseek(m_out, -4, SEEK_CUR); + return 0; + } + + STDMETHODIMP_(int) ExportEvent(const DB::EventInfo &dbei) override + { + if (m_bAppendOnly) { + fseek(m_out, -4, SEEK_END); + fputs(",", m_out); + } + + JSONNode pRoot2; + pRoot2.push_back(JSONNode("type", dbei.eventType)); + + char *szProto = Proto_GetBaseAccountName(dbei.hContact); + if (mir_strcmp(dbei.szModule, szProto)) + pRoot2.push_back(JSONNode("module", dbei.szModule)); + + pRoot2.push_back(JSONNode("timestamp", dbei.timestamp)); + + wchar_t szTemp[500]; + TimeZone_PrintTimeStamp(UTC_TIME_HANDLE, dbei.timestamp, L"I", szTemp, _countof(szTemp), 0); + pRoot2.push_back(JSONNode("isotime", T2Utf(szTemp).get())); + + std::string flags; + if (dbei.flags & DBEF_SENT) + flags += "m"; + if (dbei.flags & DBEF_READ) + flags += "r"; + pRoot2.push_back(JSONNode("flags", flags)); + + ptrW msg(DbEvent_GetTextW(&dbei)); + if (msg) + pRoot2.push_back(JSONNode("body", T2Utf(msg).get())); + + fputs(pRoot2.write_formatted().c_str(), m_out); + fputs("\n]}", m_out); + + m_bAppendOnly = true; + return 0; + } + + STDMETHODIMP_(int) EndExport() override + { + if (m_out) + fclose(m_out); + return 0; + } }; -static int mc_grokHeader(const wchar_t *profile) +static int json_grokHeader(const wchar_t *profile) { return CDbxJson().Open(profile); } -static MDatabaseCommon* mc_load(const wchar_t *profile, BOOL) +static MDatabaseCommon* json_load(const wchar_t *profile, BOOL) { std::unique_ptr db(new CDbxJson()); if (db->Open(profile)) @@ -244,14 +352,25 @@ static MDatabaseCommon* mc_load(const wchar_t *profile, BOOL) return db.release(); } +static MDatabaseExport *json_export(const wchar_t *profile) +{ + std::unique_ptr db(new CDbxJson()); + if (db->Create(profile)) + return nullptr; + + db->Load(); + return db.release(); +} + static DATABASELINK dblink = { - 0, - "mcontacts", - L"mContacts file driver", - mc_makeDatabase, - mc_grokHeader, - mc_load + MDB_CAPS_EXPORT, + "JSON", + L"JSON text file driver", + json_makeDatabase, + json_grokHeader, + json_load, + json_export }; STDMETHODIMP_(DATABASELINK *) CDbxJson::GetDriver() diff --git a/plugins/NewStory/src/history_dlg.cpp b/plugins/NewStory/src/history_dlg.cpp index e4f966e852..669a5d3b73 100644 --- a/plugins/NewStory/src/history_dlg.cpp +++ b/plugins/NewStory/src/history_dlg.cpp @@ -73,21 +73,6 @@ void LayoutFilterBar(HDWP hDwp, int x, int y, int w, InfoBarEvents *ib) x + 32 + WND_SPACING, y + (16 + WND_SPACING) * 2, w - WND_SPACING - 32, 16, SWP_NOZORDER); } -static const char *pSettings[] = -{ - LPGEN("FirstName"), - LPGEN("LastName"), - LPGEN("e-mail"), - LPGEN("Nick"), - LPGEN("Age"), - LPGEN("Gender"), - LPGEN("City"), - LPGEN("State"), - LPGEN("Phone"), - LPGEN("Homepage"), - LPGEN("About") -}; - class CHistoryDlg : public CDlgBase { HMENU m_hMenu; @@ -184,47 +169,6 @@ class CHistoryDlg : public CDlgBase } } - void ExportEvent(ItemData *pItem, FILE *out) - { - DB::EventInfo dbei(pItem->hEvent); - if (!dbei) - return; - - if (bAppendOnly) { - fseek(out, -4, SEEK_END); - fputs(",", out); - } - - JSONNode pRoot2; - pRoot2.push_back(JSONNode("type", dbei.eventType)); - - char *szProto = Proto_GetBaseAccountName(pItem->hContact); - if (mir_strcmp(dbei.szModule, szProto)) - pRoot2.push_back(JSONNode("module", dbei.szModule)); - - pRoot2.push_back(JSONNode("timestamp", dbei.timestamp)); - - wchar_t szTemp[500]; - TimeZone_PrintTimeStamp(UTC_TIME_HANDLE, dbei.timestamp, L"I", szTemp, _countof(szTemp), 0); - pRoot2.push_back(JSONNode("isotime", T2Utf(szTemp).get())); - - std::string flags; - if (dbei.flags & DBEF_SENT) - flags += "m"; - if (dbei.flags & DBEF_READ) - flags += "r"; - pRoot2.push_back(JSONNode("flags", flags)); - - ptrW msg(DbEvent_GetTextW(&dbei)); - if (msg) - pRoot2.push_back(JSONNode("body", T2Utf(msg).get())); - - fputs(pRoot2.write_formatted().c_str(), out); - fputs("\n]}", out); - - bAppendOnly = true; - } - void ShowHideControls() { int cmd = (m_dwOptions & WND_OPT_FILTERBAR) ? SW_SHOW : SW_HIDE; @@ -446,6 +390,11 @@ public: showFlags = g_plugin.getWord("showFlags", 0x7f); m_dwOptions = g_plugin.getDword("dwOptions"); + if (!GetDatabasePlugin("JSON")) { + btnExport.Disable(); + btnExport.SetTooltip(LPGEN("You need to install the Import plugin to export events")); + } + if (m_hContact == INVALID_CONTACT_ID) m_dwOptions |= WND_OPT_SEARCHBAR; else if (m_hContact > 0) { @@ -899,16 +848,11 @@ public: return; } - char *szProto = Proto_GetBaseAccountName(m_hContact); - ptrW id(Contact::GetInfo(CNF_UNIQUEID, m_hContact, szProto)); - ptrW nick(Contact::GetInfo(CNF_DISPLAY, m_hContact, szProto)); - const char *uid = Proto_GetUniqueId(szProto); - OPENFILENAME ofn = { 0 }; ofn.lStructSize = sizeof(ofn); CMStringW tszFilter, tszTitle, tszFileName; tszFilter.AppendFormat(L"%s (*.json)%c*.json%c%c", TranslateT("JSON files"), 0, 0, 0); - tszTitle.AppendFormat(TranslateT("Export %s history"), nick); + tszTitle.AppendFormat(TranslateT("Export history")); ofn.lpstrFilter = tszFilter; ofn.hwndOwner = nullptr; ofn.lpstrTitle = tszTitle; @@ -927,33 +871,10 @@ public: if (PathFileExistsW(FileName)) DeleteFileW(FileName); - FILE *out = _wfopen(FileName, L"wt"); - if (out == NULL) - return; - - // export contact info - JSONNode pRoot, pInfo, pHist(JSON_ARRAY); - pInfo.set_name("info"); - if (szProto) - pInfo.push_back(JSONNode("proto", szProto)); - - if (id != NULL) - pInfo.push_back(JSONNode(uid, T2Utf(id).get())); - - for (auto &it : pSettings) { - wchar_t *szValue = db_get_wsa(m_hContact, szProto, it); - if (szValue) - pInfo.push_back(JSONNode(it, T2Utf(szValue).get())); - mir_free(szValue); - } - - pRoot.push_back(pInfo); - - pHist.set_name("history"); - pRoot.push_back(pHist); - - fputs(pRoot.write_formatted().c_str(), out); - fseek(out, -4, SEEK_CUR); + auto *pDriver = GetDatabasePlugin("JSON"); + auto *pDB = pDriver->Export(FileName); + pDB->BeginExport(); + pDB->ExportContact(m_hContact); // export events int iDone = 0; @@ -963,18 +884,24 @@ public: for (int i = 0; i < iCount; i++) { auto *pItem = arItems.get(i); if (pItem->m_bSelected) { - ExportEvent(pItem, out); + DB::EventInfo dbei(pItem->hEvent); + if (dbei) + pDB->ExportEvent(dbei); iDone++; } } // no items selected? export whole history if (iDone == 0) - for (int i = 0; i < iCount; i++) - ExportEvent(arItems.get(i), out); + for (int i = 0; i < iCount; i++) { + auto *pItem = arItems.get(i); + DB::EventInfo dbei(pItem->hEvent); + if (dbei) + pDB->ExportEvent(dbei); + } // Close the file - fclose(out); + pDB->EndExport(); MessageBox(m_hwnd, TranslateT("Complete"), TranslateT("History export"), MB_OK | MB_ICONINFORMATION); } diff --git a/src/mir_app/src/mir_app.def b/src/mir_app/src/mir_app.def index 705e7ba2ea..8f84e99e5e 100644 --- a/src/mir_app/src/mir_app.def +++ b/src/mir_app/src/mir_app.def @@ -922,3 +922,6 @@ Clist_GroupSaveExpanded @1003 NONAME ?DlgProc@CUserInfoPageDlg@@UAEHIIJ@Z @1040 NONAME ?wipeNotify@EventInfo@DB@@QAEXI@Z @1047 NONAME _CallContactService@20 @1048 NONAME +??0MDatabaseExport@@QAE@XZ @1049 NONAME +??1MDatabaseExport@@UAE@XZ @1050 NONAME +??_7MDatabaseExport@@6B@ @1051 NONAME diff --git a/src/mir_app/src/mir_app64.def b/src/mir_app/src/mir_app64.def index 6e06ba8f4a..7eeea9080a 100644 --- a/src/mir_app/src/mir_app64.def +++ b/src/mir_app/src/mir_app64.def @@ -922,3 +922,6 @@ Clist_GroupSaveExpanded @1003 NONAME ?DlgProc@CUserInfoPageDlg@@UEAA_JI_K_J@Z @1040 NONAME ?wipeNotify@EventInfo@DB@@QEAAXI@Z @1041 NONAME CallContactService @1042 NONAME +??0MDatabaseExport@@QEAA@XZ @1043 NONAME +??1MDatabaseExport@@UEAA@XZ @1044 NONAME +??_7MDatabaseExport@@6B@ @1045 NONAME -- cgit v1.2.3