diff --git a/Code/Resource.rc b/Code/Resource.rc --- a/Code/Resource.rc +++ b/Code/Resource.rc @@ -1,139 +1,139 @@ // Microsoft Visual C++ generated resource script. // #include "resource.h" #define APSTUDIO_READONLY_SYMBOLS ///////////////////////////////////////////////////////////////////////////// // // Generated from the TEXTINCLUDE 2 resource. // #include "winres.h" ///////////////////////////////////////////////////////////////////////////// #undef APSTUDIO_READONLY_SYMBOLS ///////////////////////////////////////////////////////////////////////////// // English (U.S.) resources #if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU) #ifdef _WIN32 LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US #pragma code_page(1252) #endif //_WIN32 ///////////////////////////////////////////////////////////////////////////// // // Icon // // Icon with lowest ID value placed first to ensure application icon // remains consistent on all systems. IDI_APP ICON "..\\Resources\\Icons\\app.ico" IDI_MAIL ICON "..\\Resources\\Icons\\mail.ico" IDI_BLANK ICON "..\\Resources\\Icons\\blank.ico" IDI_SMALL ICON "..\\Resources\\Icons\\small.ico" IDI_CONTACT ICON "..\\Resources\\Icons\\contact.ico" IDI_FILTER ICON "..\\Resources\\Icons\\filter.ico" IDI_CALENDER ICON "..\\Resources\\Icons\\calender.ico" IDI_EVENT ICON "..\\Resources\\Icons\\event.ico" #endif // English (U.S.) resources ///////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////// // English (Australia) resources #if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENA) #ifdef _WIN32 LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_AUS #pragma code_page(1252) #endif //_WIN32 ///////////////////////////////////////////////////////////////////////////// // // Icon // // Icon with lowest ID value placed first to ensure application icon // remains consistent on all systems. IDI_ERR ICON "..\\Resources\\Icons\\error.ico" #ifdef APSTUDIO_INVOKED ///////////////////////////////////////////////////////////////////////////// // // TEXTINCLUDE // 1 TEXTINCLUDE BEGIN "resource.h\0" END 2 TEXTINCLUDE BEGIN "#include ""afxres.h""\r\n" "\0" END 3 TEXTINCLUDE BEGIN "\r\n" "\0" END #endif // APSTUDIO_INVOKED ///////////////////////////////////////////////////////////////////////////// // // Version // VS_VERSION_INFO VERSIONINFO - FILEVERSION 3,1,93 - PRODUCTVERSION 3,1,93 + FILEVERSION 3,1,104 + PRODUCTVERSION 3,1,104 FILEFLAGSMASK 0x3fL #ifdef _DEBUG FILEFLAGS 0x1L #else FILEFLAGS 0x0L #endif FILEOS 0x40004L FILETYPE 0x1L FILESUBTYPE 0x0L BEGIN BLOCK "StringFileInfo" BEGIN BLOCK "0c0904b0" BEGIN VALUE "CompanyName", "Memecode" VALUE "FileDescription", "Scribe" - VALUE "FileVersion", "3,1,93\0" + VALUE "FileVersion", "3,1,104\0" VALUE "InternalName", "Scribe" VALUE "LegalCopyright", "Copyright © 2016" VALUE "OriginalFilename", "Scribe.exe" VALUE "ProductName", "i.Scribe\0" - VALUE "ProductVersion", "3,1,93\0" + VALUE "ProductVersion", "3,1,104\0" END END BLOCK "VarFileInfo" BEGIN VALUE "Translation", 0xc09, 1200 END END #endif // English (Australia) resources ///////////////////////////////////////////////////////////////////////////// #ifndef APSTUDIO_INVOKED ///////////////////////////////////////////////////////////////////////////// // // Generated from the TEXTINCLUDE 3 resource. // ///////////////////////////////////////////////////////////////////////////// #endif // not APSTUDIO_INVOKED diff --git a/Code/ScribeApp.cpp b/Code/ScribeApp.cpp --- a/Code/ScribeApp.cpp +++ b/Code/ScribeApp.cpp @@ -1,12476 +1,12475 @@ /* ** FILE: ScribeApp.cpp ** AUTHOR: Matthew Allen ** DATE: 22/10/1998 ** DESCRIPTION: Scribe email application ** ** Copyright (C) 1998, Matthew Allen ** fret@memecode.com */ // Debug defines // #define PRINT_OUT_STORAGE_TREE // #define TEST_OBJECT_SIZE #define USE_SPELLCHECKER 1 #define USE_INTERNAL_BROWSER 1 // for help #define RUN_STARTUP_SCRIPTS 1 #define PROFILE_ON_PULSE 0 #define TRAY_CONTACT_BASE 1000 #define TRAY_MAIL_BASE 10000 // Includes #include #include #include #include #include "Scribe.h" #include "lgi/common/StoreConvert1To2.h" #include "lgi/common/NetTools.h" #include "lgi/common/ProgressDlg.h" #include "lgi/common/TextLabel.h" #include "lgi/common/Button.h" #include "lgi/common/CheckBox.h" #include "lgi/common/OpenSSLSocket.h" #include "lgi/common/SoftwareUpdate.h" #include "lgi/common/Html.h" #include "lgi/common/TextView3.h" #include "lgi/common/RichTextEdit.h" #include "lgi/common/Browser.h" #include "lgi/common/ClipBoard.h" #include "lgi/common/Store3.h" #include "lgi/common/Growl.h" #include "lgi/common/Edit.h" #include "lgi/common/Box.h" #include "lgi/common/LgiRes.h" #include "lgi/common/SpellCheck.h" #include "lgi/common/SubProcess.h" #include "lgi/common/CssTools.h" #include "lgi/common/Map.h" #include "lgi/common/Charset.h" #include "ScribePrivate.h" #include "PreviewPanel.h" #include "ScribeStatusPanel.h" #include "ScribeFolderDlg.h" #include "ScribePageSetup.h" #include "Calendar.h" #include "CalendarView.h" #include "ScribeSpellCheck.h" #include "Store3Common.h" #include "PrintContext.h" #include "resource.h" #include "ManageMailStores.h" #include "ReplicateDlg.h" #include "ScribeAccountPreview.h" #include "Encryption/GnuPG.h" #include "Store3Webdav/WebdavStore.h" #include "../Resources/resdefs.h" #include "../UnitTests/UnitTest.h" #include "../src/common/Coding/ScriptingPriv.h" #define DEBUG_STORE_EVENTS 0 #if DEBUG_STORE_EVENTS #define LOG_STORE(...) LgiTrace(__VA_ARGS__) #else #define LOG_STORE(...) #endif #define IDM_LOAD_MSG 2000 #define RAISED_LOOK 0 #define SUNKEN_LOOK false #ifdef MAC #define SUNKEN_CTRL false #else #define SUNKEN_CTRL true #endif #if LGI_CARBON #define TRAY_ICON_NONE -1 #define TRAY_ICON_NORMAL -1 #define TRAY_ICON_MAIL 0 #define TRAY_ICON_ERROR 1 #elif defined(WIN32) #define TRAY_ICON_NORMAL 0 #define TRAY_ICON_ERROR 1 #define TRAY_ICON_MAIL 2 #define TRAY_ICON_NONE 3 #else #define TRAY_ICON_NONE -1 #define TRAY_ICON_NORMAL 0 #define TRAY_ICON_ERROR 1 #define TRAY_ICON_MAIL 2 #endif char ScribeThingList[] = "com.memecode.ThingList"; ScribeClipboardFmt *ScribeClipboardFmt::Alloc(bool ForFolders, size_t Size) { ScribeClipboardFmt *obj = (ScribeClipboardFmt*) calloc(sizeof(ScribeClipboardFmt)+((Size-1)*sizeof(Thing*)), 1); if (obj) { memcpy(obj->Magic, ForFolders ? ScribeFolderMagic : ScribeThingMagic, sizeof(obj->Magic)); obj->ProcessId = LAppInst->GetProcessId(); obj->Len = (uint32_t)Size; } return obj; } ScribeClipboardFmt *ScribeClipboardFmt::Alloc(List &Lst) { ScribeClipboardFmt *Fmt = Alloc(false, Lst.Length()); for (unsigned i=0; iThingAt(i, Lst[i]); return Fmt; } ScribeClipboardFmt *ScribeClipboardFmt::Alloc(LArray &Arr) { ScribeClipboardFmt *Fmt = Alloc(false, Arr.Length()); for (unsigned i=0; iThingAt(i, Arr[i]); return Fmt; } bool ScribeClipboardFmt::Is(const char *Type, void *Ptr, size_t Bytes) { // Do we have the minimum bytes for the structure? if (Bytes >= sizeof(ScribeClipboardFmt) && Ptr != NULL) { ScribeClipboardFmt *This = (ScribeClipboardFmt*)Ptr; // Check the magic is the right value if (memcmp(This->Magic, Type, 4) != 0) return false; // Check it's from this process if (This->ProcessId != LAppInst->GetProcessId()) return false; return true; } return false; } Thing *ScribeClipboardFmt::ThingAt(size_t Idx, Thing *Set) { if (memcmp(Magic, ScribeThingMagic, 4)) return NULL; if (Idx >= Len) return NULL; if (Set) Things[Idx] = Set; return Things[Idx]; } ScribeFolder *ScribeClipboardFmt::FolderAt(size_t Idx, ScribeFolder *Set) { if (memcmp(Magic, ScribeFolderMagic, 4)) return NULL; if (Idx >= Len) return NULL; if (Set) Folders[Idx] = Set; return Folders[Idx]; } size_t ScribeClipboardFmt::Sizeof() { return sizeof(*this) + ((Len - 1) * sizeof(Thing*)); } bool OptionSizeInKiB = false; bool ShowRelativeDates = false; const char *MailAddressDelimiters = "\t\r\n;,"; char16 SpellDelim[] = { ' ', '\t', '\r', '\n', ',', ',', '.', ':', ';', '{', '}', '[', ']', '!', '@', '#', '$', '%', '^', '&', '*', '(', ')', '_', '-', '+', '=', '|', '\\', '/', '?', '\"', 0 }; const char *DefaultRfXml = "---------- %s ----------\n" "%s: ()\n" "%s: ()\n" "%s: \n" "%s: \n" "\n" "\n" "\n" "\n"; uchar DateTimeFormats[] = { GDTF_DEFAULT, GDTF_DAY_MONTH_YEAR | GDTF_12HOUR, GDTF_MONTH_DAY_YEAR | GDTF_12HOUR, GDTF_YEAR_MONTH_DAY | GDTF_12HOUR, GDTF_DAY_MONTH_YEAR | GDTF_24HOUR, GDTF_MONTH_DAY_YEAR | GDTF_24HOUR, GDTF_YEAR_MONTH_DAY | GDTF_24HOUR }; SystemFolderInfo SystemFolders[] = { {FOLDER_INBOX, OPT_Inbox, NULL}, {FOLDER_OUTBOX, OPT_Outbox, NULL}, {FOLDER_SENT, OPT_Sent, NULL}, {FOLDER_CONTACTS, OPT_Contacts, NULL}, {FOLDER_TRASH, OPT_Trash, NULL}, {FOLDER_CALENDAR, OPT_Calendar, OPT_HasCalendar}, {FOLDER_TEMPLATES, OPT_Templates, OPT_HasTemplates}, {FOLDER_FILTERS, OPT_Filters, OPT_HasFilters}, {FOLDER_GROUPS, OPT_Groups, OPT_HasGroups}, {FOLDER_SPAM, OPT_SpamFolder, OPT_HasSpam}, {-1, 0, 0} }; ScribeBehaviour *ScribeBehaviour::New(ScribeWnd *app) { return 0; } void ScribeOptionsDefaults(LOptionsFile *f) { if (f) { f->CreateTag("Accounts"); f->CreateTag("CalendarUI"); f->CreateTag("CalendarUI.Sources"); f->CreateTag("MailUI"); f->CreateTag("ScribeUI"); f->CreateTag("Plugins"); f->CreateTag("Print"); } } const char *Store3ItemTypeName(Store3ItemTypes t) { switch (t) { case MAGIC_NONE: return "MAGIC_NONE"; case MAGIC_BASE: return "MAGIC_BASE"; case MAGIC_MAIL: return "MAGIC_MAIL"; case MAGIC_CONTACT: return "MAGIC_CONTACT"; // case MAGIC_FOLDER: return "MAGIC_FOLDER"; case MAGIC_MAILBOX: return "MAGIC_MAILBOX"; case MAGIC_ATTACHMENT: return "MAGIC_ATTACHMENT"; case MAGIC_ANY: return "MAGIC_ANY"; case MAGIC_FILTER: return "MAGIC_FILTER"; case MAGIC_FOLDER: return "MAGIC_FOLDER"; case MAGIC_CONDITION: return "MAGIC_CONDITION"; case MAGIC_ACTION: return "MAGIC_ACTION"; case MAGIC_CALENDAR: return "MAGIC_CALENDAR"; case MAGIC_ATTENDEE: return "MAGIC_ATTENDEE"; case MAGIC_GROUP: return "MAGIC_GROUP"; default: LAssert(0); break; } return "(error)"; } void SetRecipients(ScribeWnd *App, char *Start, GDataIt l, EmailAddressType CC) { while (Start && *Start) { LString Str; char *End = strchr(Start, ','); if (End) { Str.Set(Start, End-Start); Start = End + 1; } else { Str = Start; Start = 0; } if (Str) { ListAddr *a = new ListAddr(App); if (a) { a->CC = CC; if (_strnicmp(Str, "mailto:", 7) == 0) a->sAddr = Str(7,-1); else a->sAddr = Str; l->Insert(a); } } } } static char SoftwareUpdateUri[] = "http://www.memecode.com/update.php"; enum SoftwareStatus { SwError, SwCancel, SwOutOfDate, SwUpToDate }; static LString ExtractVer(const char *s) { char Buf[256], *Out = Buf; for (const char *In = s; *In && Out < Buf + sizeof(Buf) - 1; In++) { if (*In == ' ') break; if (IsDigit(*In) || *In == '.') *Out++ = *In; } *Out++ = 0; return LString(Buf); } void IsSoftwareUpToDate(LSoftwareUpdate::UpdateInfo &Info, ScribeWnd *Parent, bool WithUI, bool IncBetas, std::function callback) { // Software update? LAutoString Proxy = Parent->GetHttpProxy(); LSoftwareUpdate Update(AppName, SoftwareUpdateUri, Proxy); Update.CheckForUpdate( Info, WithUI?Parent:0, IncBetas, [Info, WithUI, Parent, callback](auto status, auto errorMsg) { if (status) { auto LocalVer = LString(ScribeVer).SplitDelimit("."); LString BuildVer = ExtractVer(Info.Build); LToken OnlineVer(BuildVer, "."); if (OnlineVer.Length() != LocalVer.Length()) { LgiTrace("%s:%i - Invalid online version number \"%s\"\n", _FL, Info.Version.Get()); if (callback) callback(SwError); return; } unsigned i; for (i=0; i o) { if (callback) callback(SwUpToDate); return; } } LDateTime Compile; LToken Date(__DATE__, " "); Compile.Month(LDateTime::MonthFromName(Date[0])); Compile.Day(atoi(Date[1])); Compile.Year(atoi(Date[2])); Compile.SetTime(__TIME__); bool DateGreaterThenCompile = Info.Date > Compile; if (callback) callback(DateGreaterThenCompile ? SwOutOfDate : SwUpToDate); return; } else if (WithUI) { if (Info.Cancel) { if (callback) callback(SwCancel); return; } LgiMsg(Parent, LLoadString(IDS_ERROR_SOFTWARE_UPDATE), AppName, MB_OK, errorMsg); } if (callback) callback(SwError); }); } bool UpgradeSoftware(const LSoftwareUpdate::UpdateInfo &Info, ScribeWnd *Parent, bool WithUI) { bool DownloadUpdate = true; if (WithUI) { char Ds[64]; Info.Date.Get(Ds, sizeof(Ds)); DownloadUpdate = LgiMsg(Parent, LLoadString(IDS_SOFTWARE_UPDATE_DOWNLOAD), AppName, MB_YESNO, (char*)Info.Build, (char*)Info.Uri, Ds) == IDYES; } if (!DownloadUpdate) return false; LAutoString Proxy = Parent->GetHttpProxy(); LSoftwareUpdate Update(AppName, SoftwareUpdateUri, Proxy, ScribeTempPath()); return Update.ApplyUpdate(Info, false, Parent); } void SoftwareUpdate(ScribeWnd *Parent, bool WithUI, bool IncBetas, std::function callback) { // Software update? LSoftwareUpdate::UpdateInfo Info; IsSoftwareUpToDate(Info, Parent, WithUI, IncBetas, [WithUI, Parent, Info, callback](auto s) { if (s == SwUpToDate) { if (WithUI) LgiMsg(Parent, LLoadString(IDS_SOFTWARE_CURRENT), AppName, MB_OK); if (callback) callback(false); // we're up to date } else if (s == SwOutOfDate) { auto status = UpgradeSoftware(Info, Parent, WithUI); if (callback) callback(status); // update is going to happen } }); } const char *AppName = "Scribe"; char HelpFile[] = "index.html"; const char OptionsFileName[] = "ScribeOptions"; const char AuthorEmailAddr[] = "fret@memecode.com"; const char AuthorHomepage[] = "http://www.memecode.com"; const char ApplicationHomepage[] = "http://www.memecode.com/scribe.php"; const char CommercialHomepage[] = "http://www.memecode.com/inscribe.php"; const char FaqHomepage[] = "http://www.memecode.com/scribe/faq.php"; const char *DefaultFolderNames[16]; Store3ItemTypes DefaultFolderTypes[] = { MAGIC_MAIL, // Inbox MAGIC_MAIL, // Outbox MAGIC_MAIL, // Sent MAGIC_ANY, // Trash MAGIC_CONTACT, // Contacts MAGIC_MAIL, // Templates MAGIC_FILTER, // Filters MAGIC_CALENDAR, // Calendar Events MAGIC_GROUP, // Groups MAGIC_MAIL, // Spam MAGIC_NONE, MAGIC_NONE, MAGIC_NONE, MAGIC_NONE }; extern void Log(char *File, char *Str, ...); ////////////////////////////////////////////////////////////////////////////// void LogMsg(char *str, ...) { #ifdef _DEBUG char f[256]; LMakePath(f, sizeof(f), LGetExePath(), "log.txt"); if (str) { char buffer[256]; va_list arg; va_start(arg ,str); vsprintf_s(buffer, sizeof(buffer), str, arg); va_end(arg); LFile File; while (!File.Open(f, O_WRITE)) { LSleep(5); } File.Seek(File.GetSize(), SEEK_SET); File.Write(buffer, strlen(buffer)); } else { FileDev->Delete(f, false); } #endif } LString GetFullAppName(bool Platform) { LString Ret = AppName; if (Platform) { LString s; const char *Build = #ifndef _DEBUG "Release"; #else "Debug"; #endif LArray Ver; int Os = LGetOs(&Ver); const char *OsName = LGetOsName(); if (Os == LGI_OS_WIN9X) { switch (Ver[1]) { case 0: OsName = "Win95"; break; case 10: OsName = "Win98"; break; case 90: OsName = "WinME"; break; } } else if (Os == LGI_OS_WIN32 || Os == LGI_OS_WIN64) { if (Ver[0] < 5) { OsName = "WinNT"; } else if (Ver[0] == 5) { if (Ver[1] == 0) OsName = "Win2k"; else OsName = "WinXP"; } else if (Ver[0] == 6) { if (Ver[1] == 0) OsName = "Vista"; else if (Ver[1] == 1) OsName = "Win7"; else if (Ver[1] == 2) OsName = "Win8"; else if (Ver[1] == 3) OsName = "Win8.1"; } else if (Ver[0] == 10) { OsName = "Win10"; } else if (Ver[0] == 11) { // What's the chances eh? OsName = "Win11"; } } s.Printf(" v%s (%s v", ScribeVer, OsName); Ret += s; for (unsigned i=0; iId); Ret += s; } s.Printf(")"); Ret += s; } return Ret; } bool MatchWord(char *Str, char *Word) { bool Status = false; if (Str && Word) { #define IsWord(c) ( IsDigit(c) || IsAlpha(c) ) for (char *s=stristr(Str, Word); s; s=stristr(s+1, Word)) { char *e = s + strlen(Word); if ( (s<=Str || !IsWord(s[-1]) ) && (e[0] == 0 || !IsWord(e[0])) ) { return true; } } } return Status; } ////////////////////////////////////////////////////////////////////////////// ScribePanel::ScribePanel(ScribeWnd *app, const char *name, int size, bool open) : LPanel(name, size, open) { App = app; } bool ScribePanel::Pour(LRegion &r) { if (App) { SetClosedSize(App->GetToolbarHeight()); } return LPanel::Pour(r); } ////////////////////////////////////////////////////////////////////////////// class NoContactType : public Contact { LString NoFace80Path; LString NoFace160Path; public: NoContactType(ScribeWnd *wnd) : Contact(wnd) { } Thing &operator =(Thing &c) override { return *this; } bool GetVariant(const char *Name, LVariant &Value, const char *Array) override { ScribeDomType Fld = StrToDom(Name); int Px = Array ? atoi(Array) : 80; LString &Str = Px == 160 ? NoFace160Path : NoFace80Path; if (!Str) { LString f; f.Printf("NoFace%i.png", Px); Str = LFindFile(f); LAssert(Str != NULL); // This should always resolve. } if (!Str) return false; if (Fld == SdImageHtml) { LString html; html.Printf("\n", Str.Get()); Value = html; return true; } else if (Fld == SdImage) { Value = Str; return true; } return false; } }; class ScribeWndPrivate : public LBrowser::LBrowserEvents, public LVmDebuggerCallback, public LHtmlStaticInst { LOptionsFile::PortableType InstallMode = LOptionsFile::UnknownMode; public: ScribeWnd *App; uint64 LastTs = 0; int ClipboardFormat = 0; LFont *PreviewFont = NULL; int PrintMaxPages = -1; int NewMailTimeout = -1; bool SendAfterReceive = false; bool IngoreOnClose = false; LAutoString UiTags; LAutoPtr Growl; LArray TrayMenuContacts; bool ExitAfterSend = false; LToolButton *ShowConsoleBtn = NULL; LString MulPassword; LBox *SubSplit = NULL, *SearchSplit = NULL; LArray ThingSources; int LastLayout = 0; LMenuItem *DisableUserFilters = NULL; LOptionsFile *Options = NULL; HttpImageThread *ImageLoader = NULL; int LastMinute = -1, LastHour = -1; LArray Store3EventCallbacks; LAutoPtr PrintOptions; LHashTbl, LString> ResFiles; // These are for the LDataEventsI callbacks to store source context // Mainly for debugging where various events came from. const char *CtxFile = NULL; int CtxLine = 0; // Contact no face images LAutoRefPtr NoContact; // Remote content white/blacklists bool RemoteContent_Init = false; LString::Array RemoteWhiteLst, RemoteBlackLst; // Spell checking int AppWndHnd; LAutoPtr SpellerThread; // Missing caps LCapabilityTarget::CapsHash MissingCaps; MissingCapsBar *Bar = NULL; LString ErrSource; // Script file that has an error. Filter *ErrFilter = NULL; // Filter that has scripting error. // Load state bool FoldersLoaded = false; // Bayesian filter LStringPipe BayesLog; // Thread item processing LArray Transfers; // Scripting... LAutoPtr Engine; LArray Scripts; LArray CurrentScripts; LScript *CurrentScript() { return CurrentScripts.Length() ? CurrentScripts.Last() : NULL; } int NextToolMenuId = IDM_TOOL_SCRIPT_BASE; LAutoPtr ScriptToolbar; LArray OnSecondTimerCallbacks; // Encryption LAutoPtr GpgInst; // Unit tests LAutoPtr UnitTestServer; class ScribeTextControlFactory : public LViewFactory { ScribeWnd *Wnd; LView *NewView(const char *Class, LRect *Pos, const char *Text) { if (!_stricmp(Class, "ScribeTextView")) return Wnd->CreateTextControl(-1, 0, true); return NULL; } public: ScribeTextControlFactory(ScribeWnd *wnd) { Wnd = wnd; } } TextControlFactory; ScribeWndPrivate(ScribeWnd *app) : App(app), TextControlFactory(app) { NoContact = new NoContactType(app); NoContact->DecRef(); // 2->1 AppWndHnd = LEventSinkMap::Dispatch.AddSink(App); #ifdef WIN32 ClipboardFormat = RegisterClipboardFormat( #ifdef UNICODE L"Scribe.Item" #else "Scribe.Item" #endif ); #endif LScribeScript::Inst = new LScribeScript(App); if (Engine.Reset(new LScriptEngine(App, LScribeScript::Inst, this))) Engine->SetConsole(LScribeScript::Inst->GetLog()); } ~ScribeWndPrivate() { // Why do we need this? ~LView will take care of it? // LEventSinkMap::Dispatch.RemoveSink(App); DeleteObj(Options); Scripts.DeleteObjects(); DeleteObj(ImageLoader); Engine.Reset(); DeleteObj(LScribeScript::Inst); } LGrowl *GetGrowl() { if (!Growl && Growl.Reset(new LGrowl)) { LAutoPtr r(new LGrowl::LRegister); r->App = "Scribe"; r->IconUrl = "http://memecode.com/images/scribe/growl-app.png"; LGrowl::LNotifyType &NewMail = r->Types.New(); NewMail.Name = "new-mail"; NewMail.IconUrl = "http://memecode.com/images/scribe/growl-new-mail.png"; NewMail.Enabled = true; LGrowl::LNotifyType &Cal = r->Types.New(); Cal.Name = "calendar"; Cal.IconUrl = "http://memecode.com/images/scribe/growl-calendar.png"; Cal.Enabled = true; LGrowl::LNotifyType &Debug = r->Types.New(); Debug.Name = "debug"; Debug.IconUrl = "http://memecode.com/images/scribe/growl-bug.png"; Debug.Enabled = false; LGrowl::LNotifyType &Info = r->Types.New(); Info.IconUrl = "http://memecode.com/images/scribe/growl-note.png"; Info.Name = "info"; Info.Enabled = true; Growl->Register(r); } return Growl; } LVmDebugger *AttachVm(LVirtualMachine *Vm, LCompiledCode *Code, const char *Assembly) { LVariant v; if (Options) Options->GetValue(OPT_ScriptDebugger, v); if (v.CastInt32()) return new LVmDebuggerWnd(App, this, Vm, Code, NULL); return NULL; } bool CompileScript(LAutoPtr &Output, const char *FileName, const char *Source) { LCompiler c; return c.Compile(Output, Engine->GetSystemContext(), LScribeScript::Inst, FileName, Source, NULL); } bool OnSearch(LBrowser *br, const char *txt) { char Path[256]; if (!App->GetHelpFilesPath(Path, sizeof(Path))) return false; LToken Terms(txt, ", "); LStringPipe p; p.Print("\n

Search Results

\n
    \n"); LDirectory Dir; for (int b = Dir.First(Path, "*.html"); b; b = Dir.Next()) { if (!Dir.IsDir()) { char Path[256]; Dir.Path(Path, sizeof(Path)); LFile f; if (f.Open(Path, O_READ)) { LXmlTree t(GXT_NO_DOM); LXmlTag r; if (t.Read(&r, &f)) { char *PrevName = 0; char PrevUri[256] = ""; for (auto c: r.Children) { if (c->IsTag("a")) { char *Name = c->GetAttr("name"); if (Name) { PrevName = Name; } } else if (c->GetContent()) { bool Hit = false; for (unsigned i=0; !Hit && iGetContent(), Terms[i]) != 0; } if (Hit) { LStringPipe Uri(256); char *Leaf = strrchr(Path, DIR_CHAR); Leaf = Leaf ? Leaf + 1 : Path; Uri.Print("file://%s", Path); if (PrevName) Uri.Print("#%s", PrevName); LAutoString UriStr(Uri.NewStr()); if (_stricmp(UriStr, PrevUri)) { p.Print("
  • %s", UriStr.Get(), Leaf); if (PrevName) p.Print("#%s", PrevName); p.Print("\n"); strcpy_s(PrevUri, sizeof(PrevUri), UriStr); } } } } } } } } p.Print("
\n\n\n"); LAutoString Html(p.NewStr()); br->SetHtml(Html); return true; } LOptionsFile::PortableType GetInstallMode() { if (InstallMode == LOptionsFile::UnknownMode) { if (LAppInst->GetOption("portable")) { InstallMode = LOptionsFile::PortableMode; LgiTrace("Selecting portable mode based on -portable switch.\n"); } else if (LAppInst->GetOption("desktop")) { InstallMode = LOptionsFile::DesktopMode; LgiTrace("Selecting portable mode based on -desktop switch.\n"); } } if (InstallMode == LOptionsFile::UnknownMode) { bool PortableIsPossible = true; char Inst[MAX_PATH_LEN] = ""; LGetSystemPath(LSP_APP_INSTALL, Inst, sizeof(Inst)); // Do write check char Wr[MAX_PATH_LEN]; LMakePath(Wr, sizeof(Wr), Inst, "_write_test.txt"); LFile f; if (f.Open(Wr, O_WRITE)) { // Clean up f.Close(); FileDev->Delete(Wr, false); } else { // No write perms PortableIsPossible = false; } if (PortableIsPossible && LAppInst->IsElevated()) { // Check if the install is in some read only location: // e.g. c:\Program Files char Pm[MAX_PATH_LEN]; if (LGetSystemPath(LSP_USER_APPS, Pm, sizeof(Pm))) { size_t n = strlen(Pm); PortableIsPossible = _strnicmp(Pm, Inst, n) != 0; // LgiMsg(App, "%i\n%s\n%s", AppName, MB_OK, PortableIsPossible, Pm, Inst); } else LgiTrace("%s:%i - Failed to get paths.", _FL); } if (PortableIsPossible) { while (InstallMode == LOptionsFile::UnknownMode) { LAlert Dlg( App, AppName, LLoadString(IDS_PORTABLE_Q), LLoadString(IDS_HELP), LLoadString(IDS_DESKTOP), LLoadString(IDS_PORTABLE)); int Btn = Dlg.DoModal(); if (Btn == 1) { // Help App->LaunchHelp("install.html"); } else if (Btn == 2) { // Desktop InstallMode = LOptionsFile::DesktopMode; } else if (Btn == 3) { // Portable InstallMode = LOptionsFile::PortableMode; } else { LAppInst->Exit(1); break; } } /* InstallMode = LgiMsg(App, LLoadString(IDS_PORTABLE_Q), AppName, MB_YESNO) == IDYES ? LOptionsFile::PortableMode : LOptionsFile::DesktopMode; */ LgiTrace("Selecting %s mode based on querying user.\n", InstallMode == LOptionsFile::PortableMode ? "portable" : "desktop"); } else { InstallMode = LOptionsFile::DesktopMode; LgiTrace("Selecting Desktop based on lack of write permissions to install folder.\n"); } } return InstallMode; } void SetInstallMode(LOptionsFile::PortableType t) { InstallMode = t; } void DeleteCallbacks(LArray &Callbacks) { for (unsigned i=0; iGetMenu()->FindItem(Callbacks[i].Param); if (it) { it->Remove(); DeleteObj(it); } } } } }; ////////////////////////////////////////////////////////////////////////////// void UpgradeRfOption(ScribeWnd *App, const char *New, const char *Old, const char *Default) { LVariant v; /* App->GetOptions()->GetValue(New, v); if (v.Str()) { ScribePath *Path = new ScribePath(App, Old); if (Path) { char *Xml = LReadTextFile(*Path); if (Xml) { App->GetOptions()->SetValue(New, v = Xml); DeleteArray(Xml); } App->GetOptions()->DeleteValue(Old); DeleteObj(Path); } } */ if (Default && !App->GetOptions()->GetValue(New, v)) { App->GetOptions()->SetValue(New, v = Default); } } //////////////////////////////////////////////////////////////////////////// ScribeWnd::AppState ScribeWnd::ScribeState = ScribeInitializing; ScribeWnd::ScribeWnd() : BayesianFilter(this), CapabilityInstaller("Scribe", ScribeVer, "http://memecode.com/components/lookup.php", ScribeTempPath()), TrayIcon(this) { if (_Lock) _Lock->SetName("ScribeWnd"); // init some variables LApp::ObjInstance()->AppWnd = this; LCharsetSystem::Inst()->DetectCharset = ::DetectCharset; d = new ScribeWndPrivate(this); ScribeIpc = new LSharedMemory("Scribe", SCRIBE_INSTANCE_MAX * sizeof(ScribeIpcInstance)); if (ScribeIpc && ScribeIpc->GetPtr()) { ScribeIpcInstance *InstLst = (ScribeIpcInstance*) ScribeIpc->GetPtr(); for (int i=0; iMagic = SCRIBE_INSTANCE_MAGIC; ThisInst->Pid = LProcessId(); // LgiTrace("Install Scribe pid=%i to pos=%i\n", LProcessId(), i); } } } else { DeleteObj(ScribeIpc); } #ifndef WIN32 LString App = GetFullAppName(true); printf("%s\n", App.Get()); #endif if (!LoadOptions()) { ScribeState = ScribeExiting; return; } ScribeOptionsDefaults(d->Options); #define DefaultIntOption(opt, def) { LVariant v; if (!GetOptions()->GetValue(opt, v)) \ GetOptions()->SetValue(opt, v = (int)def); } #define DefaultStrOption(opt, def) { LVariant v; if (!GetOptions()->GetValue(opt, v)) \ GetOptions()->SetValue(opt, v = def); } DefaultIntOption(OPT_DefaultAlternative, 1); DefaultIntOption(OPT_BoldUnread, 1); DefaultIntOption(OPT_PreviewLines, 1); DefaultIntOption(OPT_AutoDeleteExe, 1); DefaultIntOption(OPT_DefaultReplyAllSetting, MAIL_ADDR_BCC); DefaultIntOption(OPT_BlinkNewMail, 1); DefaultIntOption(OPT_MarkReadAfterSeconds, 5); DefaultStrOption(OPT_BayesThreshold, "0.9"); DefaultIntOption(OPT_SoftwareUpdate, 1); DefaultIntOption(OPT_ResizeImgAttachments, false); DefaultIntOption(OPT_ResizeJpegQual, 80); DefaultIntOption(OPT_ResizeMaxPx, 1024); DefaultIntOption(OPT_ResizeMaxKb, 200); DefaultIntOption(OPT_RegisterWindowsClient, 1); DefaultIntOption(OPT_HasTemplates, 0); DefaultIntOption(OPT_HasCalendar, 1); DefaultIntOption(OPT_HasGroups, 1); DefaultIntOption(OPT_HasFilters, 1); DefaultIntOption(OPT_HasSpam, 0); LVariant GlyphSub; if (GetOptions()->GetValue(OPT_GlyphSub, GlyphSub)) { bool UseGlyphSub = GlyphSub.CastInt32() != 0; LSysFont->SubGlyphs(UseGlyphSub); LSysBold->SubGlyphs(UseGlyphSub); LFontSystem::Inst()->SetDefaultGlyphSub(UseGlyphSub); } else { GetOptions()->SetValue(OPT_GlyphSub, GlyphSub = LFontSystem::Inst()->GetDefaultGlyphSub()); } { // Limit the size of the 'Scribe.txt' log file char p[MAX_PATH_LEN]; if (LgiTraceGetFilePath(p, sizeof(p))) { int64 Sz = LFileSize(p); #define MiB * 1024 * 1024 if (Sz > (3 MiB)) FileDev->Delete(p); } } // Process pre-UI options LVariant SizeAdj; int SzAdj = SizeAdj.CastInt32(); if (GetOptions()->GetValue(OPT_UiFontSize, SizeAdj) && (SzAdj = SizeAdj.CastInt32()) >= 0 && SzAdj < 5) { SzAdj -= 2; if (SzAdj) { int Pt = LSysFont->PointSize(); LSysFont->PointSize(Pt + SzAdj); LSysFont->Create(); LSysBold->PointSize(Pt + SzAdj); LSysBold->Create(); LFont *m = LMenu::GetFont(); if (m) { m->PointSize(m->PointSize() + SzAdj); m->Create(); } } } else { GetOptions()->SetValue(OPT_UiFontSize, SizeAdj = 2); } // Resources and languages SetLanguage: LVariant LangId; if (GetOptions()->GetValue(OPT_UiLanguage, LangId)) { // Set the language to load... LAppInst->SetConfig("Language", LangId.Str()); } LResources::SetLoadStyles(true); // Load the resources (with the current lang) if (!LgiGetResObj(true, "Scribe")) { LgiMsg(0, "The resource file 'Scribe.lr8' is missing.", AppName); ScribeState = ScribeExiting; return; } // If no language set... if (!GetOptions()->GetValue(OPT_UiLanguage, LangId)) { // Ask the user... LanguageDlg Dlg(this); if (Dlg.Ok && Dlg.DoModal()) { // Set the language in the options file GetOptions()->SetValue(OPT_UiLanguage, LangId = (char*)Dlg.Lang); // Reload the resource file... to get the new lang. LResources *Cur = LgiGetResObj(false); DeleteObj(Cur); goto SetLanguage; } } #if 1 auto CurRes = LgiGetResObj(false); LVariant Theme; if (CurRes && GetOptions()->GetValue(OPT_Theme, Theme)) { auto Paths = ScribeThemePaths(); auto NoTheme = LLoadString(IDS_DEFAULT); if (Theme.Str() && Stricmp(NoTheme, Theme.Str())) { for (auto p: Paths) { LFile::Path Inst(p); Inst += Theme.Str(); if (Inst.Exists()) { CurRes->SetThemeFolder(Inst); d->Static->OnSystemColourChange(); break; } } } } #endif LoadCalendarStringTable(); ZeroObj(DefaultFolderNames); DefaultFolderNames[FOLDER_INBOX] = LLoadString(IDS_FOLDER_INBOX, "Inbox"); DefaultFolderNames[FOLDER_OUTBOX] = LLoadString(IDS_FOLDER_OUTBOX, "Outbox"); DefaultFolderNames[FOLDER_SENT] = LLoadString(IDS_FOLDER_SENT, "Sent"); DefaultFolderNames[FOLDER_TRASH] = LLoadString(IDS_FOLDER_TRASH, "Trash"); DefaultFolderNames[FOLDER_CONTACTS] = LLoadString(IDS_FOLDER_CONTACTS, "Contacts"); DefaultFolderNames[FOLDER_TEMPLATES] = LLoadString(IDS_FOLDER_TEMPLATES, "Templates"); DefaultFolderNames[FOLDER_FILTERS] = LLoadString(IDS_FOLDER_FILTERS, "Filters"); DefaultFolderNames[FOLDER_CALENDAR] = LLoadString(IDS_FOLDER_CALENDAR, "Calendar"); DefaultFolderNames[FOLDER_GROUPS] = LLoadString(IDS_FOLDER_GROUPS, "Groups"); DefaultFolderNames[FOLDER_SPAM] = LLoadString(IDS_SPAM, "Spam"); LStringPipe RfXml; RfXml.Print(DefaultRfXml, LLoadString(IDS_ORIGINAL_MESSAGE), LLoadString(FIELD_TO), LLoadString(FIELD_FROM), LLoadString(FIELD_SUBJECT), LLoadString(IDS_DATE)); { LAutoString Xml(RfXml.NewStr()); UpgradeRfOption(this, OPT_TextReplyFormat, "ReplyXml", Xml); UpgradeRfOption(this, OPT_TextForwardFormat, "ForwardXml", Xml); } LFontType t; if (t.GetSystemFont("small")) { d->PreviewFont = t.Create(); if (d->PreviewFont) { #if defined WIN32 d->PreviewFont->PointSize(8); #endif } } MoveOnScreen(); // Load global graphics LoadImageResources(); // Load time threads // Window name Name(AppName); SetSnapToEdge(true); ClearTempPath(); #if WINNATIVE SetStyle(GetStyle() & ~WS_VISIBLE); SetExStyle(GetExStyle() & ~WS_EX_ACCEPTFILES); CreateClassW32(AppName, LoadIcon(LProcessInst(), MAKEINTRESOURCE(IDI_APP))); #endif #if defined LINUX SetIcon("About64px.png"); LFinishXWindowsStartup(this); #endif } int StrSort(char *a, char *b, int d) { return _stricmp(a, b); } ScribeWnd::~ScribeWnd() { LAppInst->AppWnd = 0; SearchView = NULL; ScribeState = ScribeExiting; LScribeScript::Inst->ShowScriptingWindow(false); // Other cleanup... ClearTempPath(); ShutdownIpc(); SetPulse(); // Save anything thats still dirty in the folders... // just in case we crash during the shutdown phase. ScribeFolder *Cur = GetCurrentFolder(); if (Cur) Cur->SerializeFieldWidths(); SaveDirtyObjects(5000); // Tell the UI not to reference anything in the folders if (PreviewPanel) { PreviewPanel->OnThing(0, false); } Mail::NewMailLst.Empty(); // ~AccountStatusItem references the account list... must be before we // delete the accounts. DeleteObj(StatusPanel); // ~Accountlet needs to reference the root container... so // it has to go before unloading of folders. Accounts.DeleteObjects(); UnLoadFolders(); DeleteObj(PreviewPanel); SaveOptions(); DeleteObj(Commands); DeleteObj(d->PreviewFont); DeleteObj(d->SubSplit); DeleteObj(Splitter); MailList = NULL; CmdSend.ToolButton = NULL; CmdReceive.ToolButton = NULL; CmdPreview.ToolButton = NULL; CmdSend.MenuItem = NULL; CmdReceive.MenuItem = NULL; CmdPreview.MenuItem = NULL; // This could be using the OpenSSL library for HTTPS connections. So // close it before calling EndSSL. DeleteObj(d->ImageLoader); // This has to be after we close all the accounts... otherwise // they might still be using SSL functions, e.g. an IMAP/SSL connect. EndSSL(); DeleteObj(d); } LString ScribeWnd::GetResourceFile(SribeResourceType Type) { auto File = d->ResFiles.Find(Type); if (!File) LgiTrace("%s:%i - No file for resource type %i\n", _FL, Type); return File; } void ScribeWnd::LoadImageResources() { auto Res = LgiGetResObj(); LString::Array Folders; if (Res) { auto p = Res->GetThemeFolder(); if (p) Folders.Add(p); } Folders.Add(ScribeResourcePath()); for (auto p: Folders) { LDirectory Dir; LgiTrace("%s:%i - Loading resource folder '%s'\n", _FL, p.Get()); for (auto b = Dir.First(p); b; b = Dir.Next()) { if (Dir.IsDir()) continue; auto Name = Dir.GetName(); if (MatchStr("Toolbar-*.png", Name)) { if (!d->ResFiles.Find(ResToolbarFile)) d->ResFiles.Add(ResToolbarFile, Dir.FullPath()); } else if (MatchStr("xgate-icons-*.png", Name)) d->ResFiles.Add(ResToolbarFile, Dir.FullPath()); else if (MatchStr("Icons-*.png", Name)) d->ResFiles.Add(ResIconsFile, Dir.FullPath()); } } ToolbarImgs.Reset(LLoadImageList(GetResourceFile(ResToolbarFile))); ImageList.Reset(LLoadImageList(GetResourceFile(ResIconsFile))); if (!ImageList) LgiTrace("%s:%i - Failed to load toolbar image ('xgate-icons-32.png' or 'Toolbar-24.png')\n", _FL); } int ScribeWnd::GetEventHandle() { return d->AppWndHnd; } void ScribeWnd::OnCloseInstaller() { d->Bar = NULL; if (InThread()) { PourAll(); } else LAssert(0); } void ScribeWnd::OnInstall(CapsHash *Caps, bool Status) { } bool ScribeWnd::NeedsCapability(const char *Name, const char *Param) { #if DEBUG_CAPABILITIES LgiTrace("ScribeWnd::NeedsCapability(%s, %s)\n", Name, Param); #endif if (!InThread()) { #if DEBUG_CAPABILITIES LgiTrace("%s:%i - Posting M_NEEDS_CAP\n", _FL); #endif PostEvent(M_NEEDS_CAP, (LMessage::Param)NewStr(Name), (LMessage::Param)NewStr(Param)); } else { if (!Name) return false; if (d->MissingCaps.Find(Name)) { #if DEBUG_CAPABILITIES LgiTrace("%s:%i - Already in MissingCaps\n", _FL); #endif return true; } d->MissingCaps.Add(Name, true); LStringPipe MsgBuf(256); int i = 0; // const char *k; // for (bool b=d->MissingCaps.First(&k); b; b=d->MissingCaps.Next(&k), i++) for (auto k : d->MissingCaps) { MsgBuf.Print("%s%s", i?", ":"", k.key); } LVariant Actions; if (stristr(Name, "OpenSSL")) { MsgBuf.Print(LLoadString(IDS_ERROR_SERVER_CONNECT)); if (Param) MsgBuf.Print("\n%s", Param); Actions.Add(new LVariant(LLoadString(IDS_INSTALL))); } else if (stristr(Name, "Registry")) { MsgBuf.Print(LLoadString(IDS_ERROR_REG_WRITE)); Actions.Add(new LVariant(LLoadString(IDS_DONT_SHOW_AGAIN))); } else if (stristr(Name, "SpellingDictionary")) { MsgBuf.Print(LLoadString(IDS_ERROR_NEED_INSTALL), Param); Actions.Add(new LVariant(LLoadString(IDS_DOWNLOAD))); } Actions.Add(new LVariant(LLoadString(IDS_OK))); #if DEBUG_CAPABILITIES LgiTrace("%s:%i - Actions.Length()=%i, Bar=%p\n", _FL, Actions.Length(), d->Bar); #endif if (Actions.Length()) { LAutoString Msg(MsgBuf.NewStr()); // Check the script hook here... bool ShowInstallBar = true; LArray Callbacks; if (GetScriptCallbacks(LBeforeInstallBar, Callbacks)) { for (unsigned i=0; iCastInt32()) ShowInstallBar = false; else Msg.Reset(TheMsg.ReleaseStr()); } } } // Now create the capability install bar... if (!d->Bar && ShowInstallBar && Actions.Type == GV_LIST) { // FYI Capabilities are handled in ScribeWnd::StartAction. LArray Act; for (auto v : *Actions.Value.Lst) Act.Add(v->Str()); d->Bar = new MissingCapsBar(this, &d->MissingCaps, Msg, this, Act); AddView(d->Bar, 2); AttachChildren(); OnPosChange(); } } } return true; } LAutoString ScribeWnd::GetDataFolder() { LVariant v; GetOptions()->GetValue(OPT_IsPortableInstall, v); char p[MAX_PATH_LEN]; if (LGetSystemPath(v.CastInt32() ? LSP_APP_INSTALL : LSP_APP_ROOT, p, sizeof(p))) { if (!LDirExists(p)) FileDev->CreateFolder(p); return LAutoString(NewStr(p)); } else LgiTrace("%s:%i - LgiGetSystemPath failed (portable=%i).\n", _FL, v.CastInt32()); return LAutoString(); } LScriptEngine *ScribeWnd::GetScriptEngine() { return d->Engine; } LScriptCallback ScribeWnd::GetCallback(char *CallbackMethodName) { LScriptCallback Cb; auto Cur = d->CurrentScript(); if (Cur && Cur->Code) { Cb.Script = Cur; Cb.Func = Cur->Code->GetMethod(CallbackMethodName); } if (!Cb.Func) { for (auto s: d->Scripts) { Cb.Script = s; if ((Cb.Func = s->Code->GetMethod(CallbackMethodName))) break; } } return Cb; } bool ScribeWnd::RegisterCallback(LScriptCallbackType Type, LScriptArguments &Args) { if (!d->CurrentScript()) { LgiTrace("%s:%i - No current script.\n", _FL); return false; } char *Fn = Args[1]->Str(); LScriptCallback Cb = GetCallback(Fn); if (!Cb.Func) { LgiTrace("%s:%i - No callback '%s'.\n", _FL, Fn); return false; } switch (Type) { case LToolsMenu: { char *Menu = Args[0]->Str(); auto Cur = d->CurrentScript(); if (!Menu || !Fn || !Cur) { LgiTrace("%s:%i - menu=%s, fn=%s.\n", _FL, Menu, Fn); return false; } LScriptCallback &c = Cur->Callbacks.New(); c = Cb; c.Type = Type; c.Param = d->NextToolMenuId; LMenuItem *Tools = GetMenu()->FindItem(IDM_TOOLS_MENU); auto ToolSub = Tools ? Tools->Sub() : 0; if (ToolSub) { if (d->NextToolMenuId == IDM_TOOL_SCRIPT_BASE) { ToolSub->AppendSeparator(); } ToolSub->AppendItem(Menu, c.Param, true); d->NextToolMenuId++; } break; } case LThingContextMenu: case LFolderContextMenu: case LThingUiToolbar: case LMailOnBeforeSend: case LMailOnAfterReceive: case LApplicationToolbar: case LBeforeInstallBar: case LInstallComponent: case LOnTimer: case LRenderMail: case LOnLoad: { auto Cur = d->CurrentScript(); LAssert(d->Scripts.HasItem(Cur)); LScriptCallback &c = Cur->Callbacks.New(); c = Cb; c.Type = Type; if (Args.Length() > 2) c.Data = *Args[2]; break; } default: { LAssert(!"Not a known callback type"); return false; } } return true; } bool ScribeWnd::GetScriptCallbacks(LScriptCallbackType Type, LArray &Callbacks) { for (auto s: d->Scripts) { for (auto &c: s->Callbacks) { if (c.Type == Type) Callbacks.Add(&c); } } return Callbacks.Length() > 0; } bool ScribeWnd::ExecuteScriptCallback(LScriptCallback &c, LScriptArguments &Args, bool ReturnArgs) { if (!c.Func || !c.Script) return false; // Setup LVirtualMachine Vm(d); d->CurrentScripts.Add(c.Script); // Call the method bool Status = Vm.ExecuteFunction( c.Script->Code, c.Func, Args, LScribeScript::Inst->GetLog(), ReturnArgs ? &Args : NULL) != ScriptError; // Cleanup d->CurrentScripts.PopLast(); return Status; } LStream *ScribeWnd::ShowScriptingConsole() { auto Item = Menu->FindItem(IDM_SCRIPTING_CONSOLE); if (Item) { Item->Checked(!Item->Checked()); LScribeScript::Inst->ShowScriptingWindow(Item->Checked()); LVariant v; GetOptions()->SetValue(OPT_ShowScriptConsole, v = Item->Checked()); } return LScribeScript::Inst->GetLog(); } LOptionsFile::PortableType ScribeWnd::GetPortableType() { return d->GetInstallMode(); } void ScribeWnd::RemoteContent_AddSender(const char *Addr, bool WhiteList) { if (!Addr) return; auto Opt = WhiteList ? OPT_RemoteContentWhiteList : OPT_RemoteContentBlackList; LVariant v; GetOptions()->GetValue(Opt, v); // Not an error if not there... auto existing = LString(v.Str()).SplitDelimit(" ,\r\n"); for (auto p: existing) { if (MatchStr(p, Addr)) { LgiTrace("%s:%i - '%s' is already in '%s'\n", _FL, Addr, Opt); return; // Already in list... } } existing.SetFixedLength(false); existing.Add(Addr); auto updated = LString("\n").Join(existing); GetOptions()->SetValue(Opt, v = updated.Get()); LgiTrace("%s:%i - Added '%s' to '%s'\n", _FL, Addr, Opt); d->RemoteContent_Init = false; } ScribeRemoteContent ScribeWnd::RemoteContent_GetSenderStatus(const char *Addr) { if (!d->RemoteContent_Init) { LVariant v; if (GetOptions()->GetValue(OPT_RemoteContentWhiteList, v)) d->RemoteWhiteLst = LString(v.Str()).SplitDelimit(" ,\r\n"); if (GetOptions()->GetValue(OPT_RemoteContentBlackList, v)) d->RemoteBlackLst = LString(v.Str()).SplitDelimit(" ,\r\n"); d->RemoteContent_Init = true; } for (auto p: d->RemoteWhiteLst) if (MatchStr(p, Addr)) return RemoteAlwaysLoad; for (auto p: d->RemoteBlackLst) if (MatchStr(p, Addr)) return RemoteNeverLoad; return RemoteDefault; } void ScribeWnd::RemoteContent_ClearCache() { d->RemoteWhiteLst.Empty(); d->RemoteBlackLst.Empty(); d->RemoteContent_Init = false; } void ScribeWnd::OnSpellerSettingChange() { // Kill the current thread d->SpellerThread.Reset(); // Setup the new thread LSpellCheck *t = GetSpellThread(); if (t) { // Trigger an install if needed t->Check(d->AppWndHnd, "thisisamispeltword", 0, 18); } } bool ScribeWnd::SetSpellThreadParams(LSpellCheck *Thread) { if (!Thread) return false; LVariant Lang, Dict; GetOptions()->GetValue(OPT_SpellCheckLanguage, Lang); GetOptions()->GetValue(OPT_SpellCheckDictionary, Dict); LAutoPtr Params(new LSpellCheck::Params); if (!Params) return false; Params->IsPortable = GetPortableType(); Params->OptionsPath = GetOptions()->GetFile(); Params->Lang = Lang.Str(); Params->Dict = Dict.Str(); Params->CapTarget = this; Thread->SetParams(Params); return true; } LSpellCheck *ScribeWnd::CreateSpellObject() { LVariant PrefAspell; GetOptions()->GetValue(OPT_PreferAspell, PrefAspell); LAutoPtr Obj; if (PrefAspell.CastInt32()) Obj = CreateAspellObject(); #if defined(MAC) if (!Obj) Obj = CreateAppleSpellCheck(); #elif defined(WINDOWS) LArray Ver; int Os = LGetOs(&Ver); if ( !Obj && (Os == LGI_OS_WIN32 || Os == LGI_OS_WIN64) && ( Ver.Length() > 1 && ( Ver[0] > 6 || (Ver[0] == 6 && Ver[1] > 1) ) ) ) Obj = CreateWindowsSpellCheck(); #endif if (!Obj) Obj = CreateAspellObject(); SetSpellThreadParams(Obj); return Obj.Release(); } LSpellCheck *ScribeWnd::GetSpellThread(bool OverrideOpt) { LVariant Use; if (OverrideOpt) Use = true; else GetOptions()->GetValue(OPT_SpellCheck, Use); #if USE_SPELLCHECKER if ((Use.CastInt32() != 0) ^ (d->SpellerThread.Get() != 0)) d->SpellerThread.Reset(Use.CastInt32() ? CreateSpellObject() : NULL); #endif return d->SpellerThread; } LAutoString ScribeWnd::GetHttpProxy() { LAutoString Proxy; LVariant v; if (GetOptions()->GetValue(OPT_HttpProxy, v) && ValidStr(v.Str())) { Proxy.Reset(v.ReleaseStr()); } else { LProxyUri p; if (p.sHost) Proxy.Reset(NewStr(p.ToString())); } return Proxy; } InstallProgress *ScribeWnd::StartAction(MissingCapsBar *Bar, LCapabilityTarget::CapsHash *Components, const char *ActionParam) { if (!ActionParam) { LgiTrace("%s:%i - No action supplied.\n", _FL); return NULL; } LArray Callbacks; LVariant Action(ActionParam); if (GetScriptCallbacks(LInstallComponent, Callbacks)) { bool StartInstall = true; for (unsigned i=0; iCastInt32()) StartInstall = false; } } if (!Action.Str()) { LgiTrace("%s:%i - GInstallComponent removed action name.\n", _FL); return NULL; } if (!StartInstall) { LgiTrace("%s:%i - GInstallComponent script canceled install of '%s'.\n", _FL, ActionParam); return NULL; } } if (!_stricmp(Action.Str(), LLoadString(IDS_OK))) { // Do nothing d->MissingCaps.Empty(); } else if (!_stricmp(Action.Str(), LLoadString(IDS_DONT_SHOW_AGAIN))) { // Turn off registering as a client. LVariant No(false); GetOptions()->SetValue(OPT_RegisterWindowsClient, No); GetOptions()->SetValue(OPT_CheckDefaultEmail, No); } else if (!_stricmp(Action.Str(), LLoadString(IDS_INSTALL))) { #ifdef WINDOWS bool IsSsl = false; for (auto c: *Components) { if (!_stricmp(c.key, "openssl")) { IsSsl = true; break; } } if (IsSsl) { LString s; s.Printf(LLoadString(IDS_WINDOWS_SSL_INSTALL), LGetOsName()); LAlert q(this, AppName, s, "Open Website", LLoadString(IDS_CANCEL)); switch (q.DoModal()) { case 1: LExecute("https://slproweb.com/products/Win32OpenSSL.html"); break; default: break; } return NULL; } #endif return CapabilityInstaller::StartAction(Bar, Components, Action.Str()); } else if (!_stricmp(Action.Str(), LLoadString(IDS_SHOW_CONSOLE))) { ShowScriptingConsole(); } else if (!_stricmp(Action.Str(), LLoadString(IDS_OPEN_SOURCE))) { if (d->ErrSource) LExecute(d->ErrSource); else if (d->ErrFilter) d->ErrFilter->DoUI(); d->ErrSource.Empty(); d->ErrFilter = NULL; } else if ( !Stricmp(Action.Str(), LLoadString(IDS_SHOW_REMOTE_CONTENT)) || !Stricmp(Action.Str(), LLoadString(IDS_ALWAYS_SHOW_REMOTE_CONTENT))) { auto c = Components->begin(); LWindow *w = Bar->GetWindow(); if ((*c).key && !Stricmp((*c).key, "RemoteContent") && w) { LVariant Ret, Always(!Stricmp(Action.Str(), LLoadString(IDS_ALWAYS_SHOW_REMOTE_CONTENT))); LArray Args; Args[0] = &Always; w->CallMethod(DomToStr(SdShowRemoteContent), &Ret, Args); } } else if (!_stricmp(Action.Str(), LLoadString(IDS_DOWNLOAD))) { LSpellCheck *t = GetSpellThread(); if (t) t->InstallDictionary(); else LgiTrace("%s:%i - No spell thread.\n", _FL); } else LAssert(!"Unknown action."); return NULL; } HttpImageThread *ScribeWnd::GetImageLoader() { if (!d->ImageLoader) { LAutoString Proxy = GetHttpProxy(); d->ImageLoader = new HttpImageThread(this, Proxy, 0); } return d->ImageLoader; } char *ScribeWnd::GetUiTags() { if (!d->UiTags) { char UiTags[256] = "inscribe" #if defined WIN32 " win32" #elif defined LINUX " linux" #elif defined MAC " mac" #endif ; LVariant Tags; if (GetOptions()->GetValue("tags", Tags)) { size_t Len = strlen(UiTags); sprintf_s(UiTags+Len, sizeof(UiTags)-Len, " %s", Tags.Str()); } d->UiTags.Reset(NewStr(UiTags)); } return d->UiTags; } void ScribeWnd::OnCreate() { // Load the styles LResources::StyleElement(this); // Main menu Menu = new LMenu(AppName); if (Menu) { Menu->Attach(this); if (Menu->Load(this, "ID_MENU", GetUiTags())) { LAssert(ImageList != NULL); Menu->SetImageList(ImageList, false); auto IdentityItem = Menu->FindItem(IDM_NO_IDENTITIES); if (IdentityItem) { IdentityMenu = IdentityItem->GetParent(); } CmdSend.MenuItem = Menu->FindItem(IDM_SEND_MAIL); auto NewMailMenu = Menu->FindItem(IDM_NEW_EMAIL); if (NewMailMenu) { MailMenu = NewMailMenu->GetParent(); } LVariant v; WorkOffline = Menu->FindItem(IDM_WORK_OFFLINE); if (WorkOffline && GetOptions()->GetValue(OPT_WorkOffline, v)) { WorkOffline->Checked(v.CastInt32() != 0); } if ((d->DisableUserFilters = Menu->FindItem(IDM_FILTERS_DISABLE))) { if (GetOptions()->GetValue(OPT_DisableUserFilters, v)) { d->DisableUserFilters->Checked(v.CastInt32() != 0); } } #if RUN_STARTUP_SCRIPTS // Run scripts in './Scripts' folder char s[MAX_PATH_LEN]; LMakePath(s, sizeof(s), ScribeResourcePath(), "Scripts"); if (!LDirExists(s)) LMakePath(s, sizeof(s), LGetSystemPath(LSP_APP_INSTALL), #if defined(LINUX) || defined(WINDOWS) "..\\" #endif "Scripts"); if (!LDirExists(s)) LgiTrace("%s:%i - Error: the scripts folder '%s' doesn't exist.\n", _FL, s); else { bool ErrorDsp = false; LDirectory Dir; for (int b = Dir.First(s); b; b = Dir.Next()) { if (Dir.IsDir()) continue; char *Ext = LGetExtension(Dir.GetName()); if (!Ext || _stricmp(Ext, "script") != 0) continue; Dir.Path(s, sizeof(s)); LStringPipe Log; char *Source = LReadTextFile(s); if (Source) { LScript *Cur = new LScript; if (Cur) { char Msg[256]; d->CurrentScripts.Add(Cur); LScribeScript::Inst->GetLog()->Write(Msg, sprintf_s(Msg, sizeof(Msg), "Compiling '%s'...\n", Dir.GetName())); LCompiler c; if (c.Compile( Cur->Code, d->Engine->GetSystemContext(), LScribeScript::Inst, s, Source, NULL)) { LFunctionInfo *Main = Cur->Code->GetMethod("Main"); if (Main) { LVirtualMachine Vm(d); LScriptArguments Args(&Vm); Args.New() = new LVariant((LDom*)this); d->Scripts.Add(Cur); if (Vm.ExecuteFunction( Cur->Code, Main, Args, LScribeScript::Inst->GetLog()) && Args.GetReturn()->CastInt32()) { d->CurrentScripts.Delete(Cur, true); Cur = NULL; } else { LgiTrace("Error: Script's main failed (%s)\n", Cur->Code->GetFileName()); if (Cur->Callbacks.Length()) d->DeleteCallbacks(Cur->Callbacks); d->Scripts.Delete(Cur); } Args.DeleteObjects(); } } else if (!ErrorDsp) { ErrorDsp = true; OnScriptCompileError(Source, NULL); } if (Cur) { d->CurrentScripts.Delete(Cur, true); DeleteObj(Cur); } } DeleteArray(Source); } } } #endif #define EnableItem(id, en) { auto i = Menu->FindItem(id); if (i) i->Enabled(en); } #define SetMenuIcon(id, ico) { auto i = Menu->FindItem(id); if (i) i->Icon(ico); } EnableItem(IDM_IMPORT_OUTLOOK_ITEMS, true); // SetMenuIcon(IDM_OPEN_FOLDERS, ICON_OPEN_FOLDER); SetMenuIcon(IDM_OPTIONS, ICON_OPTIONS); SetMenuIcon(IDM_SECURITY, ICON_LOCK); SetMenuIcon(IDM_CUT, ICON_CUT); SetMenuIcon(IDM_COPY, ICON_COPY); SetMenuIcon(IDM_PASTE, ICON_PASTE); SetMenuIcon(IDM_LAYOUT1, ICON_LAYOUT1); SetMenuIcon(IDM_LAYOUT2, ICON_LAYOUT2); SetMenuIcon(IDM_LAYOUT3, ICON_LAYOUT3); SetMenuIcon(IDM_LAYOUT4, ICON_LAYOUT4); SetMenuIcon(IDM_NEW_EMAIL, ICON_UNSENT_MAIL); SetMenuIcon(IDM_SET_READ, ICON_READ_MAIL); SetMenuIcon(IDM_SET_UNREAD, ICON_UNREAD_MAIL); SetMenuIcon(IDM_NEW_CONTACT, ICON_CONTACT); SetMenuIcon(IDM_NEW_GROUP, ICON_CONTACT_GROUP); SetMenuIcon(IDM_REPLY, ICON_FLAGS_REPLY); SetMenuIcon(IDM_REPLY_ALL, ICON_FLAGS_REPLY); SetMenuIcon(IDM_FORWARD, ICON_FLAGS_FORWARD); SetMenuIcon(IDM_BOUNCE, ICON_FLAGS_BOUNCE); SetMenuIcon(IDM_NEW_FILTER, ICON_FILTER); SetMenuIcon(IDM_FILTER_CURRENT_FOLDER, ICON_FOLDER_FILTERS); SetMenuIcon(IDM_MEMECODE, ICON_LINK); SetMenuIcon(IDM_HOMEPAGE, ICON_LINK); SetMenuIcon(IDM_SCRIBE_FAQ, ICON_LINK); SetMenuIcon(IDM_INSCRIBE_LINK, ICON_LINK); SetMenuIcon(IDM_VERSION_HISTORY, ICON_LINK); SetMenuIcon(IDM_DEBUG_INFO, ICON_LINK); SetMenuIcon(IDM_TUTORIALS, ICON_LINK); SetMenuIcon(IDM_FEEDBACK, ICON_UNREAD_MAIL); SetMenuIcon(IDM_HELP, ICON_HELP); LMenuItem *mi; if ( GetOptions()->GetValue(OPT_EditControl, v) && (mi = Menu->FindItem(IDM_HTML_EDITOR)) ) mi->Checked(v.CastInt32() != 0); Menu->SetPrefAndAboutItems(IDM_OPTIONS, IDM_ABOUT); } } // Initialize user interface SetupUi(); // Get some of the base submenu pointers. These are needed // for SetupAccounts to work correctly, e.g. populate the // send/receive/preview submenus. Folders need to be loaded // before this for the templates folder BuildDynMenus(); // Load accounts SetupAccounts(); // Recursively load folder tree LoadFolders(); // Redo it for the templates... now that load folders has completed. BuildDynMenus(); if (ScribeState == ScribeExiting) return; // Process command line OnCommandLine(); // Update the templates sub-menu now that the folders are loaded BuildDynMenus(); // Check registry settings SetDefaultHandler(); // Run on load scripts... LArray OnLoadCallbacks; if (GetScriptCallbacks(LOnLoad, OnLoadCallbacks)) { for (auto r: OnLoadCallbacks) { LVirtualMachine Vm; LScriptArguments Args(&Vm); Args.New() = new LVariant(this); ExecuteScriptCallback(*r, Args); Args.DeleteObjects(); } } ScribeState = ScribeRunning; } ScribeAccount *ScribeWnd::GetAccountByEmail(const char *Email) { if (!Email) return NULL; for (auto a : *GetAccounts()) { LVariant e = a->Identity.Email(); if (e.Str() && !_stricmp(e.Str(), Email)) { return a; } } return 0; } ScribeAccount *ScribeWnd::GetAccountById(int Id) { for (auto a : *GetAccounts()) { if (a->Receive.Id() == Id) { return a; } } return 0; } const char *ScribeWnd::EditCtrlMimeType() { LVariant Html; GetOptions()->GetValue(OPT_EditControl, Html); return Html.CastInt32() ? sTextHtml : sTextPlain; } LAutoString ScribeWnd::GetReplyXml(const char *MimeType) { bool IsHtml = MimeType && !_stricmp(MimeType, sTextHtml); LVariant s; GetOptions()->GetValue(IsHtml ? OPT_HtmlReplyFormat : OPT_TextReplyFormat, s); return LAutoString(s.ReleaseStr()); } LAutoString ScribeWnd::GetForwardXml(const char *MimeType) { bool IsHtml = MimeType && !_stricmp(MimeType, sTextHtml); LVariant s; GetOptions()->GetValue(IsHtml ? OPT_HtmlForwardFormat : OPT_TextForwardFormat, s); return LAutoString(s.ReleaseStr()); } LVmDebuggerCallback *ScribeWnd::GetDebuggerCallback() { return d; } GpgConnector *ScribeWnd::GetGpgConnector() { if (!d->GpgInst) { if (!GpgConnector::IsInstalled()) return NULL; d->GpgInst.Reset(new GpgConnector()); } return d->GpgInst; } LFont *ScribeWnd::GetPreviewFont() { return d->PreviewFont; } bool ScribeWnd::IsValid() { #if 0 try { for (ScribeAccount *a = Accounts.First(); a; a = Accounts.Next()) { } } catch(...) { return false; } #endif return true; } bool ScribeWnd::ShutdownIpc() { // Remove our instance from the shared memory if (ScribeIpc && ScribeIpc->GetPtr()) { ScribeIpcInstance *InstLst = (ScribeIpcInstance*) ScribeIpc->GetPtr(); if (ThisInst) memset(ThisInst, 0, sizeof(*ThisInst)); int c = 0; for (int i=0; iDestroy(); } ThisInst = 0; DeleteObj(ScribeIpc); return true; } bool ScribeWnd::GetVariant(const char *Name, LVariant &Value, const char *Array) { ScribeDomType Fld = StrToDom(Name); switch (Fld) { case SdQuote: // Type: String { return GetOptions()->GetValue(OPT_QuoteReplyStr, Value); } case SdName: // Type: String { Value = AppName; break; } case SdHome: // Type: String { Value = LGetExePath().Get(); break; } case SdNow: // Type: String { LDateTime Now; Now.SetNow(); char s[64]; Now.Get(s, sizeof(s)); Value = s; break; } case SdFolder: // Type: ScribeFolder[] // Pass system folder index or string as array parameter. { ScribeFolder *f = 0; if (!Array) return false; if (IsDigit(*Array)) { f = GetFolder(atoi(Array)); } else { f = GetFolder(Array); } if (!f) return false; Value = (LDom*)f; break; } case SdAppName: // Type: String { Value = AppName; break; } case SdCalendarToday: // Type: String { return Calendar::SummaryOfToday(this, Value); } case SdInboxSummary: { LStringPipe p; // Iterate through the mail stores for (auto m: Folders) { if (!m.Store) continue; auto Inbox = m.Store->GetObj(FIELD_INBOX); if (!Inbox) continue; auto Unread = Inbox->GetInt(FIELD_UNREAD); auto Name = Inbox->GetStr(FIELD_FOLDER_NAME); LString Path; Path.Printf("/%s/%s", m.Name.Get(), Name); if (Unread) p.Print("%s: %i unread
", m.Name.Get(), Path.Get(), Unread); else p.Print("%s: 0 unread
", m.Name.Get()); } // And also the IMAP full folders for (auto a: Accounts) { ScribeProtocol Protocol = a->Receive.ProtocolType(); if (Protocol == ProtocolImapFull) { LDataStoreI *Store = a->Receive.GetDataStore(); if (Store) { auto Inbox = Store->GetObj(FIELD_INBOX); if (Inbox) { auto Unread = Inbox->GetInt(FIELD_UNREAD); auto Name = Inbox->GetStr(FIELD_FOLDER_NAME); auto m = a->Receive.Name(); if (m.Str()) { LString Path; Path.Printf("/%s/%s", m.Str(), Name); if (Unread) p.Print("%s: %i unread
", m.Str(), Path.Get(), Unread); else p.Print("%s: 0 unread
", m.Str()); } } } } } Value = p.NewGStr().Get(); break; } case SdExecute: // Type: String { if (!Array) return false; const char *s = Array; char *Exe = LTokStr(s); if (!Exe) return false; while (*s && *s == ' ') s++; LStringPipe Out; LSubProcess p(Exe, (char*)s); if (p.Start()) { p.Communicate(&Out); LAutoString o(Out.NewStr()); LAutoString t(TrimStr(o)); Value = t; } DeleteArray(Exe); break; } case SdBuildType: // Type: String { #ifdef _DEBUG Value = "Debug"; #else Value = "Release"; #endif break; } case SdPlatform: // Type: String { LArray Ver; LGetOs(&Ver); LString::Array Va; for (auto i: Ver) Va.New().Printf("%i", i); #if defined __GTK_H__ auto Api = "GTK3"; #elif LGI_SDL auto Api = "SDL"; #elif LGI_COCOA auto Api = "Cocoa"; #elif LGI_CARBON auto Api = "Carbon"; #elif defined WIN32 auto Api = "WinApi"; #else #error "Impl me." auto Api = "#err"; #endif LString s; s.Printf("%s, v%s, %s", LGetOsName(), LString(".").Join(Va).Get(), Api); Value = s.Get(); break; } case SdVersion: // Type: String { char Ver[32]; sprintf_s(Ver, sizeof(Ver), "v%s", ScribeVer); Value = Ver; break; } case SdBuild: // Type: String { char s[128]; sprintf_s(s, sizeof(s), "%s, %s, %ibit", __DATE__, __TIME__, (int)(sizeof(NativeInt)*8)); Value = s; break; } case SdLanguage: // Type: String { LLanguage *l = LGetLanguageId(); if (!l) return false; char s[256]; sprintf_s(s, sizeof(s), "%s \"%s\"", l->Name, l->Id); Value = s; break; } case SdString: // Type: String { if (!Array) { LAssert(!"Missing string ID"); return false; } int Id = atoi(Array); Value = LLoadString(Id); break; } case SdCurrentFolder: // Type: ScribeFolder { Value = (LDom*) GetCurrentFolder(); break; } case SdView: // Type: LView { Value = (LView*)this; break; } case SdNoContact: // Type: Contact { Value = (NoContactType*)d->NoContact; break; } case SdAccounts: { if (Array) { if (IsDigit(*Array)) { auto i = atoi(Array); if (i >= 0 && i < (ssize_t)Accounts.Length()) { Value = (LDom*)Accounts[i]; } else return false; } else { for (auto a : Accounts) { LVariant nm = a->Send.Name(); if (nm.Str() && !_stricmp(nm.Str(), Array)) { Value = (LDom*)a; break; } } } } else { Value = (int32)Accounts.Length(); } break; } case SdOptions: { Value = GetOptions(); break; } case SdMailStorePaths: { if (!Value.SetList()) return false; for (auto Ms : Folders) Value.Add(new LVariant(Ms.Path)); break; } case SdRootFolders: { if (!Value.SetList() || !Tree) return false; for (auto *i = Tree->GetChild(); i; i = i->GetNext()) { ScribeFolder *c = dynamic_cast(i); if (c) { auto p = c->GetPath(); Value.Add(new LVariant(p)); } } break; } default: { return false; } } return true; } bool ScribeWnd::CallMethod(const char *MethodName, LVariant *ReturnValue, LArray &Args) { ScribeDomType m = StrToDom(MethodName); switch (m) { case SdGrowlOnMail: // Type: (Mail Obj) { if (Args.Length() != 1) { LgiTrace("%s:%i - Wrong arg count: %i.\n", _FL, (int)Args.Length()); return false; } Mail *m = dynamic_cast(Args[0]->CastDom()); if (!m) { LgiTrace("%s:%i - Invalid object.\n", _FL); return false; } GrowlOnMail(m); break; } case SdGrowlInfo: { auto Title = Args.Length() > 0 ? Args[0] : NULL; auto Text = Args.Length() > 1 ? Args[1] : NULL; GrowlInfo(Title ? Title->Str() : NULL, Text ? Text->Str() : NULL); break; } case SdGetClipboardText: // Type: () { LClipBoard c(this); *ReturnValue = c.Text(); break; } case SdSetClipboardText: // Type: (String Text) { if (Args.Length() != 1) { LgiTrace("%s:%i - Wrong arg count: %i.\n", _FL, (int)Args.Length()); return false; } char *Str = Args[0]->CastString(); LClipBoard c(this); if (ValidStr(Str)) *ReturnValue = c.Text(Str); else *ReturnValue = c.Empty(); break; } case SdLookupContactGroup: // Type: (String GroupName) { if (Args.Length() != 1) { LgiTrace("%s:%i - Wrong arg count: %i.\n", _FL, (int)Args.Length()); return false; } ContactGroup *Grp = LookupContactGroup(this, Args[0]->Str()); *ReturnValue = dynamic_cast(Grp); break; } case SdGetUserString: // Type: (LView ParentView, String PromptMessage[, Bool ObsurePassword[, String DefaultValue]]) { if (Args.Length() < 2) return false; LView *View = Args[0]->CastView(); char *Prompt = Args[1]->CastString(); bool Pass = Args.Length() > 2 ? Args[2]->CastInt32() != 0 : false; char *Default = Args.Length() > 3 ? Args[3]->CastString() : NULL; LInput i(View ? View : this, Default, Prompt, AppName, Pass); if (!i.DoModal()) return false; if (ReturnValue) *ReturnValue = i.GetStr(); break; } case SdCreateAccount: // Type: () { ScribeAccount *a = new ScribeAccount(this, (int)Accounts.Length()); if (a) { if (ReturnValue) *ReturnValue = (LDom*)a; Accounts.Insert(a); a->Create(); } else return false; break; } case SdDeleteAccount: // Type: (ScribeAccount AccountToDelete) { if (Args.Length() != 1) return false; ScribeAccount *a = dynamic_cast(Args[0]->CastDom()); if (!a) { if (ReturnValue) *ReturnValue = false; } else { int Idx = (int)Accounts.IndexOf(a); if (Idx < 0 || a->IsOnline()) { if (ReturnValue) *ReturnValue = false; } else { Accounts.Delete(a); a->Delete(); delete a; // delete actual account object // Reindex remaining items so their are no gaps int i=0; auto it = Accounts.begin(); for (a = *it; a; a = *++it, i++) { a->ReIndex(i); } } } break; } case SdShowRemoteContent: { if (PreviewPanel) return PreviewPanel->CallMethod(MethodName, ReturnValue, Args); else return false; break; } case SdSearchHtml: // Type(Html, SearchExp, ResultExp) { if (Args.Length() != 3) { LgiTrace("%s:%i - SearchHtml requires 3 arguments.\n", _FL); *ReturnValue = false; return true; } auto Html = Args[0]->Str(); auto SearchExp = Args[1]->Str(); auto ResultExp = Args[2]->Str(); if (!Html || !SearchExp || !ResultExp) { LgiTrace("%s:%i - SearchHtml got non-string argument.\n", _FL); *ReturnValue = false; return true; } SearchHtml(ReturnValue, Html, SearchExp, ResultExp); return true; } case SdGetUri: // Type(UriToDownload, CallbackName) { if (Args.Length() < 2) { LgiTrace("%s:%i - GetUri requires at least 2 arguments.\n", _FL); *ReturnValue = false; return true; } auto Uri = Args[0]->Str(); auto Callback = Args[1]->Str(); LVariant *UserData = Args.Length() > 2 ? Args[2] : NULL; new ScriptDownloadContentThread(this, Uri, Callback, UserData); *ReturnValue = true; return true; } default: { LAssert(!"Unsupported method."); return false; } } return true; } LOptionsFile *ScribeWnd::GetOptions(bool Create) { if (!d->Options && Create) { d->Options = new LOptionsFile(d->GetInstallMode(), OptionsFileName); ScribeOptionsDefaults(d->Options); } return d->Options; } int OptionsFileCmp(OptionsInfo *a, OptionsInfo *b) { int64 Diff = b->Mod - a->Mod; return Diff < 0 ? -1 : (Diff > 0 ? 1 : 0); } OptionsInfo::OptionsInfo() { Score = 0; Mod = 0; Leaf = NULL; Usual = false; } OptionsInfo &OptionsInfo::operator =(char *p) { File = p; Leaf = LGetLeaf(File); if (Leaf) { char n[64]; sprintf_s(n, sizeof(n), "%s.xml", OptionsFileName); Usual = !_stricmp(n, Leaf); } return *this; } LOptionsFile *OptionsInfo::Load() { // Read the file... LAutoPtr Of(new LOptionsFile(File)); if (!Of) return NULL; if (!Of->SerializeFile(false)) return NULL; // Sanity check the options... LXmlTag *Acc = Of->LockTag(OPT_Accounts, _FL); if (!Acc) return NULL; Of->Unlock(); LXmlTag *Stores = Of->LockTag(OPT_MailStores, _FL); if (!Stores) return NULL; auto Count = Stores->Children.Length(); Of->Unlock(); if (Count == 0) Of.Reset(); return Of.Release(); } #define DEBUG_OPTS_SCAN 0 bool ScribeWnd::ScanForOptionsFiles(LArray &Files, LSystemPath PathType) { LString Root = LGetSystemPath(PathType); LDirectory Dir; char p[MAX_PATH_LEN]; if (IsUnitTest) Root += ".unittest"; #if DEBUG_OPTS_SCAN LgiTrace("%s:%i - Root='%s'\n", _FL, Root.Get()); #endif for (int b = Dir.First(Root); b; b = Dir.Next()) { if ( !Dir.IsDir() && Dir.Path(p, sizeof(p)) ) { LResolveShortcut(p, p, sizeof(p)); char *Ext = LGetExtension(Dir.GetName()); if (stristr(Dir.GetName(), OptionsFileName) != NULL && Ext && (!_stricmp(Ext, "xml") || !_stricmp(Ext, "bak"))) { OptionsInfo &i = Files.New(); i = p; i.Mod = Dir.GetLastWriteTime(); #if DEBUG_OPTS_SCAN LgiTrace("%s:%i - File='%s'\n", _FL, p); #endif } } } Files.Sort(OptionsFileCmp); // Scan through the results and pick out the normal file for (unsigned i=0; iOptions; i++) { if (Files[i].Usual) { d->Options = Files[i].Load(); #if DEBUG_OPTS_SCAN LgiTrace("%s:%i - Attempt '%s' = %p\n", _FL, Files[i].File.Get(), d->Options); #endif } } if (!d->Options) { // Scan through the alternative files and look for something // we can use. #if DEBUG_OPTS_SCAN LgiTrace("%s:%i - Scanning backups\n", _FL); #endif for (unsigned i=0; iOptions; i++) { if (!Files[i].Usual) { d->Options = Files[i].Load(); if (d->Options) { // Lets rename this baby back to the real filename LString Xml = OptionsFileName; Xml += ".xml"; LFile::Path Normal(Root, Xml); if (LFileExists(Normal)) FileDev->Delete(Normal); d->Options->SetFile(Normal); d->Options->SerializeFile(true); // sets to clean after changing filename. } } } } if (d->Options) { // Load OK: Clear out any old options files... #if DEBUG_OPTS_SCAN LgiTrace("%s:%i - Files.len=" LPrintfSizeT "\n", _FL, Files.Length()); #endif while (Files.Length() > 6) { auto Idx = Files.Length() - 1; auto &f = Files[Idx]; #if DEBUG_OPTS_SCAN LgiTrace("%s:%i - Delete '%s'\n", _FL, f.File.Get()); #endif FileDev->Delete(f.File); Files.DeleteAt(Idx); } } return d->Options != NULL; } bool ScribeWnd::IsUnitTest = false; bool ScribeWnd::LoadOptions() { bool Load = false; // Check if we are running unit tests... if ((IsUnitTest = LAppInst->GetOption("unittest"))) { d->UnitTestServer.Reset(new LUnitTestServer(this)); } // Look in the XGate folder #if WINNATIVE && !defined(_DEBUG) LRegKey xgate(false, "HKEY_CURRENT_USER\\Software\\XGate"); if (xgate.IsOk()) { char *Spool = xgate.GetStr("SpoolDir"); if (LDirExists(Spool)) { char File[MAX_PATH_LEN]; LMakePath(File, sizeof(File), Spool, "spool"); LMakePath(File, sizeof(File), File, OptionsFileName); strcat_s(File, sizeof(File), ".xml"); if (LFileExists(File)) { d->SetInstallMode(LOptionsFile::DesktopMode); LgiTrace("Selecting xgate mode based on options file path.\n"); d->Options = new LOptionsFile(File); } } } #endif // Now look in the application install folder LArray Files; if (!d->Options && ScanForOptionsFiles(Files, LSP_APP_INSTALL)) { // File is in the install folder... d->SetInstallMode(LOptionsFile::PortableMode); LgiTrace("Selecting portable mode based on options file path.\n"); } // Look in the app root if ( !d->Options && ScanForOptionsFiles(Files, LSP_APP_ROOT) ) { // Desktop mode d->SetInstallMode(LOptionsFile::DesktopMode); LgiTrace("Selecting desktop mode based on options file path.\n"); } // Do multi-instance stuff if (d->Options && d->Options->GetFile()) { // Search for other instances of Scribe if (ScribeIpc) { int i; ScribeIpcInstance *InstLst = (ScribeIpcInstance*) ScribeIpc->GetPtr(); ScribeIpcInstance *Mul = 0; for (i=0; iMulPassword = Mul->Password; break; } } for (i=0; iIsScribe(), InstLst->Magic, InstLst->Pid); if (InstLst->IsScribe() && InstLst != ThisInst) { // LgiTrace("%s, %s\n", InstLst->OptionsPath, d->Options->GetFile()); if (Mul || _stricmp(InstLst->OptionsPath, d->Options->GetFile()) == 0) { int Pid = InstLst->Pid; OsAppArguments *Args = LAppInst->GetAppArgs(); if (!LIsProcess(Pid)) { continue; } char *Utf8 = 0; #if WINNATIVE Utf8 = WideToUtf8(Args->lpCmdLine); #else LStringPipe p; for (int i=1; iArgs; i++) { if (i > 1) p.Push(" "); char *Sp = strchr(Args->Arg[i], ' '); if (Sp) p.Push("\""); p.Push(Args->Arg[i]); if (Sp) p.Push("\""); } Utf8 = p.NewStr(); #endif if (Utf8) { size_t Len = strlen(Utf8); if (Len > 255) { InstLst->Flags |= SCRIBE_IPC_LONG_ARGS; InstLst->Flags &= ~SCRIBE_IPC_CONTINUE_ARGS; for (char *u = Utf8; Len > 0; u += 255) { ssize_t Part = MIN(sizeof(InstLst->Args)-1, Len); memcpy(InstLst->Args, u, Part); Len -= Part; int64 Start = LCurrentTime(); while (LCurrentTime() - Start < 60000) { if (TestFlag(InstLst->Flags, SCRIBE_IPC_CONTINUE_ARGS) && InstLst->Args[0] == 0) { Start = 0; break; } LSleep(10); } if (Start) { LgiTrace("%s:%i - SendLA timed out.\n", _FL); break; } } InstLst->Flags &= ~(SCRIBE_IPC_CONTINUE_ARGS | SCRIBE_IPC_LONG_ARGS); } else { strcpy_s(InstLst->Args, sizeof(InstLst->Args), Utf8); } DeleteArray(Utf8); ShutdownIpc(); LgiTrace("Passed args to the other running instance of Scribe (pid=%i)\n", Pid); LCloseApp(); return false; } else LgiTrace("%s:%i - No arguments to pass.\n", _FL); } } } if (Mul && LFileExists(Mul->OptionsPath)) { // No instance of Scribe is running, but MUL may be keeping // the previously run instance around. So we should run that // by using the options file and password from MUL's instance // record. DeleteObj(d->Options); d->Options = new LOptionsFile(Mul->OptionsPath); } } // Insert ourselves into the instance list if (ThisInst) { strcpy_s(ThisInst->OptionsPath, sizeof(ThisInst->OptionsPath), d->Options->GetFile()); } } // Open file and load.. if (!Load && d->Options) { LOptionsFile *Opts = GetOptions(); Load = Opts->SerializeFile(false); if (Load) { LVariant v = d->GetInstallMode() == LOptionsFile::PortableMode; GetOptions()->SetValue(OPT_IsPortableInstall, v); } else { auto err = GetOptions()->GetError(); LgiMsg( this, LLoadString(IDS_ERROR_LR8_FAILURE), AppName, MB_OK, err); } } if (!d->Options) { d->Options = new LOptionsFile(d->GetInstallMode(), OptionsFileName); } if (d->Options) { LVariant v; if (!d->Options->GetValue(OPT_IsPortableInstall, v) && d->GetInstallMode() != LOptionsFile::UnknownMode) { v = d->GetInstallMode() == LOptionsFile::PortableMode; d->Options->SetValue(OPT_IsPortableInstall, v); } ScribeOptionsDefaults(d->Options); if (Load) { if (GetOptions()->GetValue(OPT_PrintSettings, v)) { auto *p = GetPrinter(); if (p) { LString s = v.Str(); p->Serialize(s, false); } } } if (GetOptions()->GetValue(OPT_PreviewLines, v)) { Mail::PreviewLines = v.CastInt32() != 0; } // upgrade smtp password const char *Pw = "SmtpPsw"; if (!GetOptions()->GetValue(OPT_EncryptedSmtpPassword, v)) { // no encrypted password, look for unencrypted password if (GetOptions()->GetValue(Pw, v)) { GPassword p; p.Set(v.Str()); p.Serialize(GetOptions(), OPT_EncryptedSmtpPassword, true); } } // if old un-encrypted password exists... // delete the key, we are now storing an encrypted // password if (GetOptions()->GetValue(Pw, v)) GetOptions()->DeleteValue(Pw); if (GetOptions()->GetValue(OPT_AdjustDateTz, v)) Mail::AdjustDateTz = !v.CastInt32(); if (!GetOptions()->GetValue(OPT_ConfirmDelete, v)) GetOptions()->SetValue(OPT_ConfirmDelete, v = true); if (!GetOptions()->GetValue(OPT_DelDirection, v)) GetOptions()->SetValue(OPT_DelDirection, v = DeleteActionPrev); if (GetOptions()->GetValue(OPT_SizeInKiB, v)) OptionSizeInKiB = v.CastInt32() != 0; if (GetOptions()->GetValue(OPT_RelativeDates, v)) ShowRelativeDates = v.CastInt32() != 0; // date format if (GetOptions()->GetValue(OPT_DateFormat, v)) { int Idx = v.CastInt32(); if (Idx >= 0 && Idx < CountOf(DateTimeFormats)) LDateTime::SetDefaultFormat(DateTimeFormats[Idx]); } // SSL debug logging if (GetOptions()->GetValue(OPT_DebugSSL, v)) SslSocket::DebugLogging = v.CastInt32() != 0; // Growl if (GetOptions()->GetValue(OPT_GrowlEnabled, v) && v.CastInt32()) { LVariant Ver, Bld; GetVariant(DomToStr(SdVersion), Ver); GetVariant("Build", Bld); LString n; n.Printf("%s\n%s", Ver.Str(), Bld.Str()); GrowlInfo("Scribe has started up...", n); } } #if LGI_EXCEPTIONS try { #endif // Default the font settings to the system font // if they don't already exist const char *OptFont[] = { OPT_EditorFont, OPT_PrintFont, OPT_HtmlFont, 0 }; int Index = 0; for (const char **Opt=OptFont; *Opt; Opt++, Index++) { LVariant v; if (!GetOptions()->GetValue(*Opt, v)) { LFontType Type; if (Type.GetSystemFont("System")) { if (Index == 2) { int Pt = Type.GetPointSize(); Type.SetPointSize(Pt+3); } Type.Serialize(GetOptions(), *Opt, true); } } } #if LGI_EXCEPTIONS } catch (...) { LgiMsg( this, LLoadString(IDS_ERROR_FONT_SETTINGS), AppName, MB_OK); } #endif return true; } bool ScribeWnd::SaveOptions() { LStringPipe Log(256); bool Status = false; bool WriteFailed = false; bool WndStateSet = false; RestartSave: if (d->Options && !d->Options->GetFile()) { bool PortableOk = true; char Path[MAX_PATH_LEN]; char Leaf[32]; sprintf_s(Leaf, sizeof(Leaf), "%s.xml", OptionsFileName); Log.Print("No current path for '%s', creating...\n", Leaf); LVariant v; GetOptions()->GetValue(OPT_IsPortableInstall, v); if (v.CastInt32()) { if (!LGetSystemPath(LSP_APP_INSTALL, Path, sizeof(Path))) { PortableOk = false; Log.Print("Error: LgiGetSystemPath(LSP_APP_INSTALL) failed.\n"); } else { LMakePath(Path, sizeof(Path), Path, Leaf); // Do write test to confirm we are good to go LFile f; if (f.Open(Path, O_WRITE)) { f.Close(); FileDev->Delete(Path, false); d->Options->SetFile(Path); } else { PortableOk = false; Log.Print("Warning: '%s' is not writable.\n", Path); } } } if (!v.CastInt32() || !PortableOk) { // Desktop mode then. if (v.CastInt32()) { const char *Msg = "Switching to desktop mode because the install folder is not writable."; Log.Print("%s\n", Msg); LgiMsg(this, Msg, AppName, MB_OK); GetOptions()->SetValue(OPT_IsPortableInstall, v = false); } if (!LGetSystemPath(LSP_APP_ROOT, Path, sizeof(Path))) { Log.Print("Error: LgiGetSystemPath(LSP_APP_ROOT) failed.\n"); } else { LMakePath(Path, sizeof(Path), Path, LAppInst->LBase::Name()); if (!LDirExists(Path)) { if (!FileDev->CreateFolder(Path)) { Log.Print("Error: CreateFolder('%s') failed.\n", Path); } } LMakePath(Path, sizeof(Path), Path, Leaf); // Do write test to confirm we are good to go LFile f; if (f.Open(Path, O_WRITE)) { f.Close(); FileDev->Delete(Path, false); d->Options->SetFile(Path); } else { Log.Print("Error: '%s' is not writable.\n", Path); } } } } if (d->Options && d->Options->GetFile() && d->Options->IsValid()) { // Backup options file char Backup[MAX_PATH_LEN]; strcpy_s(Backup, sizeof(Backup), d->Options->GetFile()); char *Ext = LGetExtension(Backup); if (Ext) { *--Ext = 0; LString s; for (int i=1; i<100; i++) { s.Printf("%s_%i.bak", Backup, i); if (!LFileExists(s)) break; } if (!LFileExists(s)) FileDev->Move(d->Options->GetFile(), s); } // Update some settings... #if LGI_VIEW_HANDLE if (Handle()) #endif WndStateSet = SerializeState(GetOptions(), OPT_ScribeWndPos, false); LVariant v; if (Splitter) GetOptions()->SetValue(OPT_SplitterPos, v = (int)Splitter->Value()); if (d->SubSplit) { auto First = d->SubSplit->GetViewAt(0); if (First == (LViewI*)SearchView) { auto Lst = (SearchView) ? d->SubSplit->GetViewAt(1) : NULL; if (Lst) GetOptions()->SetValue(OPT_SubSplitPos, v = (int)Lst->GetPos().Y()); } else GetOptions()->SetValue(OPT_SubSplitPos, v = (int)d->SubSplit->Value()); } // Write them... if (GetOptions()->SerializeFile(true)) { Status = true; } else { // We probably don't have write permissions to the install folder... Log.Print("Error: Options.Serialize failed.\n"); if (!WriteFailed) { // This blocks any possibility of an infinite loop WriteFailed = true; d->Options->SetFile(NULL); // Set desktop mode explicitly LVariant v; GetOptions()->GetValue(OPT_IsPortableInstall, v = false); Log.Print("Restarting save after setting desktop mode...\n"); goto RestartSave; } } } if (!Status) { LString a = Log.NewGStr(); LgiMsg(this, "Saving options failed:\n%s", AppName, MB_OK, a.Get()); } if (!WndStateSet) { LRect r(10, 10, 790, 590); SetPos(r); MoveToCenter(); } return Status; } // // Command Line Options: // // -m, -t : To recipient(s) // -f : The filename of the attachment // -b : Attach as a binary // -c : CC'd recipient(s) // -s : Subject for the email // -n : Send now... else UI is shown // -p : Print the file // -upgrade_folders : trigger a folder upgrade // -o : Load the following options file // -u : Load the following URL/file // void ScribeWnd::OnCommandLine() { // check command line args LString Str, File; bool CreateMail = false; CreateMail = LAppInst->GetOption("m", Str); if (!CreateMail) CreateMail = LAppInst->GetOption("t", Str); bool HasFile = LAppInst->GetOption("f", File); if (!CreateMail) CreateMail = HasFile; LString OpenArg; if (LAppInst->GetOption("u", OpenArg)) { LUri u(OpenArg); if (u.sProtocol) { OnUrl(OpenArg); } else if (LFileExists(OpenArg)) { LArray Files; Files.Add(OpenArg); OnReceiveFiles(Files); } } Mail *NewEmail = 0; if (CreateMail && Str) { // strip off quotes if needed char *In = Str, *Out = Str; for (; In && *In; In++) { if (!strchr("\'\"", *In)) { *Out++ = *In; } } *Out++ = 0; // create object NewEmail = dynamic_cast(CreateItem(MAGIC_MAIL, NULL, false)); if (NewEmail) { Mailto mt(this, Str); mt.Apply(NewEmail); // cc's? if (LAppInst->GetOption("c", Str)) { SetRecipients(this, Str, NewEmail->GetObject()->GetList(FIELD_TO), MAIL_ADDR_CC); } // attach a file? if (File) { if (LAppInst->GetOption("b")) { // attach as a binary file NewEmail->AttachFile(this, &File[0]); } else { // insert as the body LAutoString b(LReadTextFile(&File[0])); if (b) { NewEmail->SetBody(b); } } } // subject? if (LAppInst->GetOption("s", Str)) { NewEmail->SetSubject(Str); } // Send now or later? if (LAppInst->GetOption("n")) { // Check for exit after send option d->ExitAfterSend = LAppInst->GetOption("exit"); // now NewEmail->SetFlags(MAIL_CREATED | MAIL_READY_TO_SEND | NewEmail->GetFlags()); NewEmail->Save(); OnCommand(IDM_SEND_MAIL, 0, #ifndef __GTK_H__ Handle() #else NULL #endif ); } else { // later NewEmail->DoUI(); } } } // Pop3 on startup option LVariant n; if (GetOptions()->GetValue(OPT_Pop3OnStart, n) && n.CastInt32()) { OnCommand(IDM_RECEIVE_MAIL, 0, NULL); } } void ScribeWnd::SetCurrentIdentity(int i) { LVariant v = i; GetOptions()->SetValue(OPT_CurrentIdentity, v); if (DefaultIdentityItem) DefaultIdentityItem->Checked(i < 0); for (auto a: Accounts) { a->SetCheck(i == a->GetIndex()); } } ScribeAccount *ScribeWnd::GetCurrentAccount() { auto Idx = GetCurrentIdentity(); ScribeAccount *a = (Idx >= 0 && Idx < (ssize_t)Accounts.Length()) ? Accounts.ItemAt(Idx) : NULL; bool ValidId = a != NULL && a->IsValid(); if (!ValidId) { LAssert(!"No current identity?"); // Find a valid account to be the identity... for (auto a : Accounts) { if (!a->Send.Disabled() && a->Identity.IsValid()) { break; } } } return a; } int ScribeWnd::GetCurrentIdentity() { LVariant i; if (GetOptions()->GetValue(OPT_CurrentIdentity, i)) return i.CastInt32(); else if (ScribeState != ScribeInitializing) LgiTrace("%s:%i - No OPT_CurrentIdentity set.\n", _FL); return -1; } void ScribeWnd::SetupAccounts() { int i, CurrentIdentity = GetCurrentIdentity(); if (StatusPanel) { StatusPanel->Empty(); } #if !defined(COCOA) // FIXME LAssert(ReceiveMenu && PreviewMenu); #endif if (SendMenu) SendMenu->Empty(); if (ReceiveMenu) ReceiveMenu->Empty(); if (PreviewMenu) PreviewMenu->Empty(); if (IdentityMenu) { IdentityMenu->Empty(); } static bool Startup = true; bool ResetDefault = false; LArray Enabled; for (i=0; true; i++) { // char *s = 0; ScribeAccount *a = Startup ? new ScribeAccount(this, i) : Accounts[i]; if (a) { if (i == 0) { a->Create(); } a->Register(this); LVariant ReceiveName = a->Receive.Name(); LVariant ReceiveServer = a->Receive.Server(); LVariant SendServer = a->Send.Server(); if (i == 0 || ValidStr(ReceiveName.Str()) || ValidStr(ReceiveServer.Str()) || ValidStr(SendServer.Str()) ) { a->Send.SendItem = SendItem; a->Receive.ReceiveItem = ReceiveItem; a->Receive.PreviewItem = PreviewItem; if (!Accounts.HasItem(a)) { Accounts.Insert(a); } if (i) a->Create(); a->InitMenus(); // Identity Menu Item LVariant IdEmail = a->Identity.Email(); LVariant IdName = a->Identity.Name(); if (IdentityMenu && ValidStr(IdEmail.Str())) { char s[256]; if (IdName.Str()) sprintf_s(s, sizeof(s), "%s <%s>", IdName.Str(), IdEmail.Str()); else sprintf_s(s, sizeof(s), "<%s>", IdEmail.Str()); a->SetMenuItem(IdentityMenu->AppendItem(s, IDM_IDENTITY_BASE+i+1, !a->Send.Disabled())); if (a->Send.Disabled()) { a->SetCheck(false); if (i == CurrentIdentity) ResetDefault = true; } else { a->SetCheck(i == CurrentIdentity); Enabled[i] = a; } } } else { Accounts.Delete(a); DeleteObj(a); } } if (!a) break; } if ((ResetDefault || CurrentIdentity < 0) && Enabled.Length()) { for (unsigned i=0; iSetCheck(true); LVariant v; GetOptions()->SetValue(OPT_CurrentIdentity, v = (int)i); break; } } } Startup = false; if (ReceiveMenu && i == 0) { ReceiveMenu->AppendItem(LLoadString(IDS_NO_ITEMS), 0, false); } if (StatusPanel) { StatusPanel->OnAccountListChange(); } SetPulse(100); } ////////////////////////////////////////////////////////////////////////////// class LShutdown : public LDialog { LTextLabel *Msg; LButton *KillBtn; LButton *CancelBtn; bool Disconnected; public: ScribeAccount *Wait; List *Accounts; LShutdown(List *accounts) { Wait = 0; Disconnected = false; Accounts = accounts; LRect r( 0, 0, 320 + LAppInst->GetMetric(LGI_MET_DECOR_X), 70 + LAppInst->GetMetric(LGI_MET_DECOR_Y)); SetPos(r); MoveToCenter(); char Str[256]; sprintf_s(Str, sizeof(Str), "%s exiting...", AppName); LView::Name(Str); AddView(Msg = new LTextLabel(-1, 10, 10, 300, -1, "None")); AddView(KillBtn = new LButton(IDC_KILL, 70, 35, 60, 20, "Kill")); AddView(CancelBtn = new LButton(IDCANCEL, 140, 35, 60, 20, "Cancel")); if (KillBtn) { KillBtn->Enabled(false); } } void OnCreate() { SetPulse(100); } void OnPulse() { if (Accounts) { if (!Wait) { Wait = (*Accounts)[0]; if (Wait) { Disconnected = false; char s[256]; LVariant v = Wait->Receive.Name(); sprintf_s(s, sizeof(s), "Waiting for '%s'", v.Str() ? v.Str() : (char*)"Untitled..."); Msg->Name(s); Accounts->Delete(Wait); Wait->Stop(); KillBtn->Enabled(true); } else { SetPulse(); EndModal(true); } } if (Wait && !Wait->IsOnline()) { Wait = 0; Msg->Name("None"); KillBtn->Enabled(false); } } else { SetPulse(); EndModal(false); } } int OnNotify(LViewI *Ctrl, LNotification n) { switch (Ctrl->GetId()) { case IDC_KILL: { if (Wait) { if (!Disconnected) { Disconnected = true; Wait->Disconnect(); } else { Wait->Kill(); } } break; } case IDCANCEL: { EndModal(false); break; } } return 0; } }; bool ScribeWnd::OnRequestClose(bool OsShuttingDown) { if (FolderTasks.Length() > 0) { LgiTrace("%s:%i - %i folder tasks still busy...\n", _FL, FolderTasks.Length()); return false; } LString OnClose = LAppInst->GetConfig("Scribe.OnClose"); if (!d->IngoreOnClose && !OsShuttingDown && !Stricmp(OnClose.Get(), "minimize")) { SetZoom(LZoomMin); return false; } Visible(false); if (ScribeState != ScribeRunning) { // Inside a folder load/unload or initialization // Tell the loader to quit out... ScribeState = ScribeExiting; // Leave now, we can exit when we're ready return false; } else if (IsSending() || GetActiveThreads() > 0) { // whack up a shutdown window List Online; for (auto i: Accounts) { i->OnEndSession(); if (i->IsOnline()) { Online.Insert(i); } } LShutdown Dlg(&Online); if (Dlg.DoModal()) { ScribeState = ScribeExiting; } else { ScribeState = ScribeRunning; Visible(true); return false; } } else { // End all sessions if any... for (auto i: Accounts) { i->OnEndSession(); } } // close all the other top level windows while (ThingUi::All.Length() > 0) { ThingUi *Ui = ThingUi::All.First(); if (!Ui->OnRequestClose(OsShuttingDown)) { ScribeState = ScribeRunning; Visible(true); return false; } size_t Start = ThingUi::All.Length(); Ui->Quit(); if (ThingUi::All.Length() >= Start) { LAssert(0); break; } } SerializeState(GetOptions(), OPT_ScribeWndPos, false); LCloseApp(); return LWindow::OnRequestClose(OsShuttingDown); } void ScribeWnd::DoOnTimer(LScriptCallback *c) { if (!c) return; auto Now = LCurrentTime(); if (c->PrevTs) { auto Since = Now - c->PrevTs; double Sec = (double)Since / 1000.0; if (Sec >= c->fParam) { // Call the function c->PrevTs = Now; LVirtualMachine Vm; LScriptArguments Args(&Vm); LVariant This((LDom*)this); Args.Add(&This); ExecuteScriptCallback(*c, Args); } } else { c->PrevTs = Now; } } void ScribeWnd::OnMinute() { if (Folders.Length() == 0) return; // Check for calendar event alarms... Calendar::CheckReminders(); // Check for any outgoing email that should be re-attempted... ScribeFolder *Outbox = GetFolder(FOLDER_OUTBOX); if (Outbox) { bool Resend = false; for (auto t : Outbox->Items) { Mail *m = t->IsMail(); if (m && !TestFlag(m->GetFlags(), MAIL_SENT) && TestFlag(m->GetFlags(), MAIL_READY_TO_SEND) && m->SendAttempts > 0) { Resend = true; break; } } if (Resend) Send(); } LArray Cb; if (GetScriptCallbacks(LOnTimer, Cb)) { for (auto c: Cb) { if (!c->Func) continue; if (c->fParam == 0.0) { // Work out the period from 'Data' char *s = c->Data.Str(); while (*s && IsWhiteSpace(*s)) s++; char *u = s; while (*u && !IsAlpha(*u)) u++; double v = atof(s); switch (*u) { case 's': case 'S': // seconds c->fParam = v; break; case 'm': case 'M': // mins c->fParam = v * LDateTime::MinuteLength; break; case 'h': case 'H': // hours c->fParam = v * LDateTime::HourLength; break; case 'd': case 'D': // days c->fParam = v * LDateTime::DayLength; break; default: { LgiTrace("%s:%i - Couldn't understand period '%s'\n", _FL, c->Data.Str()); c->Data.Empty(); break; } } if ((c->OnSecond = c->fParam < 60.0)) { d->OnSecondTimerCallbacks.Add(c); } } if (!c->OnSecond) DoOnTimer(c); } } } void ScribeWnd::OnHour() { // Force time zone update in case of daylight savings change. LDateTime::SystemTimeZone(true); // Check if we need should be doing a software update check static bool InSoftwareCheck = false; if (!InSoftwareCheck) { char s[64]; InSoftwareCheck = true; LVariant v; if (GetOptions()->GetValue(OPT_SoftwareUpdate, v) && v.CastInt32()) { LDateTime Now, Last; Now.SetFormat(GDTF_YEAR_MONTH_DAY); Last.SetFormat(Now.GetFormat()); Now.SetNow(); if (!GetOptions()->GetValue(OPT_SoftwareUpdateLast, v) || !Last.Set(v.Str())) { // Record now as the last check point Now.Get(s, sizeof(s)); GetOptions()->SetValue(OPT_SoftwareUpdateLast, v = s); } else if (GetOptions()->GetValue(OPT_SoftwareUpdateTime, v)) { // Valid last check date/time. switch (v.CastInt32()) { case 0: // Week Last.AddDays(7); break; case 1: // Month Last.AddMonths(1); break; case 2: // Year Last.AddMonths(12); break; default: LgiTrace("%s:%i - The option '%s' is not valid\n", _FL, OPT_SoftwareUpdateTime); return; } if (Last < Now) { // Save the last date for next time... Now.Get(s, sizeof(s)); GetOptions()->SetValue(OPT_SoftwareUpdateLast, v = s); // Check for update now... LSoftwareUpdate::UpdateInfo Info; GetOptions()->GetValue(OPT_SoftwareUpdateIncBeta, v); IsSoftwareUpToDate(Info, this, false, v.CastInt32() != 0, [Info, this](auto s) { if (s == SwOutOfDate) if (UpgradeSoftware(Info, this, true)) LCloseApp(); }); } } } InSoftwareCheck = false; } } bool ScribeWnd::SaveDirtyObjects(int TimeLimitMs) { bool Status = false; if (Thing::DirtyThings.Length() > 0) { static bool SavingObjects = false; if (!SavingObjects) { SavingObjects = true; LArray WriteTimes; // ssize_t StartDirty = Thing::DirtyThings.Length(); uint64 Start = LCurrentTime(); for (unsigned i=0; iSave(NULL)) { WriteTimes.Add((int) (LCurrentTime() - WriteStart)); LAssert(!ThingType::DirtyThings.HasItem(t)); Status = true; } else { LgiTrace("Failed to save thing type 0x%x\n", t->Type()); FailedWrites++; if (FailedWrites > 2) { while (ThingType::DirtyThings.Length()) ThingType::DirtyThings[0]->SetDirty(false); FailedWrites = 0; } } } } SavingObjects = false; /* if (Status) { LStringPipe p; p.Print("WriteTimes: "); for (unsigned i=0; iLastTs >= 1000) { d->LastTs = Now; OnPulseSecond(); } } } void ScribeWnd::OnPulseSecond() { #if PROFILE_ON_PULSE LProfile Prof("NewMailLst handling"); Prof.HideResultsIfBelow(50); #endif if (Mail::NewMailLst.Length() > 0) { LVariant Blink; if (GetOptions()->GetValue(OPT_BlinkNewMail, Blink) && Blink.CastInt32()) { TrayIcon.Value((TrayIcon.Value() == TRAY_ICON_MAIL) ? TRAY_ICON_NONE : TRAY_ICON_MAIL); } } else { bool Err = false; for (auto a: Accounts) { if (!a->Receive.GetStatus() || !a->Send.GetStatus()) { Err = true; } } TrayIcon.Value(Err ? TRAY_ICON_ERROR : TRAY_ICON_NORMAL); } #if PROFILE_ON_PULSE Prof.Add("StatusPanel handling"); #endif if (StatusPanel) { StatusPanel->OnPulse(); } #if PROFILE_ON_PULSE Prof.Add("OnXXXX handling"); #endif LDateTime Now; Now.SetNow(); if (d->LastMinute != Now.Minutes()) // Check every minute... { d->LastMinute = Now.Minutes(); OnMinute(); } if (d->LastHour != Now.Hours()) // Check every hour... { d->LastHour = Now.Hours(); OnHour(); } { // These timers need to be checked every second... for (auto c: d->OnSecondTimerCallbacks) DoOnTimer(c); } #if PROFILE_ON_PULSE Prof.Add("Instance handling"); #endif if (ThisInst && ValidStr(ThisInst->Args)) { LStringPipe p; p.Push(ThisInst->Args); if (ThisInst->Flags & SCRIBE_IPC_LONG_ARGS) { ThisInst->Flags |= SCRIBE_IPC_CONTINUE_ARGS; int64 Start = LCurrentTime(); while ( TestFlag(ThisInst->Flags, SCRIBE_IPC_LONG_ARGS) && LCurrentTime() - Start < 60000) { ZeroObj(ThisInst->Args); while ( TestFlag(ThisInst->Flags, SCRIBE_IPC_LONG_ARGS) && !ThisInst->Args[0] && LCurrentTime() - Start < 60000) { LSleep(10); } p.Push(ThisInst->Args); } } ZeroObj(ThisInst->Args); LAutoString Arg(p.NewStr()); if (Arg) { OsAppArguments AppArgs(0, 0); LgiTrace("Received cmd line: %s\n", Arg.Get()); AppArgs.Set(Arg); LAppInst->SetAppArgs(AppArgs); if (LAppInst->GetOption("m") && LAppInst->GetOption("f")) ; else LAppInst->OnCommandLine(); OnCommandLine(); if (GetZoom() == LZoomMin) SetZoom(LZoomNormal); Visible(true); } } #if PROFILE_ON_PULSE Prof.Add("PreviewPanel handling"); #endif if (PreviewPanel) { PreviewPanel->OnPulse(); } } void ScribeWnd::AddFolderToMru(char *FileName) { if (FileName) { // read MRU List Files; int i; for (i=0; i<10; i++) { char Key[32]; LVariant f; sprintf_s(Key, sizeof(Key), "FolderMru.%i", i); if (GetOptions()->GetValue(Key, f)) { Files.Insert(NewStr(f.Str())); GetOptions()->DeleteValue(Key); } } // remove FileName if present for (auto f: Files) { if (_stricmp(f, FileName) == 0) { Files.Delete(f); DeleteArray(f); break; } } // insert FileName at the start of the list Files.Insert(NewStr(FileName)); // write MRU for (i=0; i<10; i++) { char *n = Files.ItemAt(i); if (n) { char Key[32]; sprintf_s(Key, sizeof(Key), "FolderMru.%i", i); LVariant f; GetOptions()->SetValue(Key, f = n); } else break; } // Clean up Files.DeleteArrays(); } } bool ScribeWnd::CleanFolders(ScribeFolder *f) { if (!f) return false; if (f->Select()) { f->SerializeFieldWidths(); } for (ScribeFolder *c = f->GetChildFolder(); c; c = c->GetNextFolder()) { CleanFolders(c); } return true; } void ScribeWnd::OnFolderChanged(LDataFolderI *folder) { } bool ScribeWnd::OnFolderTask(LEventTargetI *Ptr, bool Add) { if (Add) { if (FolderTasks.HasItem(Ptr)) { LAssert(!"Can't add task twice."); return false; } FolderTasks.Add(Ptr); return true; } else { if (!FolderTasks.HasItem(Ptr)) { LAssert(!"Item not part of task list."); return false; } FolderTasks.Delete(Ptr); return true; } } LMailStore *ScribeWnd::GetDefaultMailStore() { LMailStore *Def = 0; for (unsigned i=0; i Def->Priority()) { Def = &Folders[i]; } } } } return Def; } bool HasMailStore(LXmlTag *MailStores, char *Name) { for (auto t : MailStores->Children) { char *StoreName = t->GetAttr(OPT_MailStoreName); if (StoreName && Name && !_stricmp(StoreName, Name)) return true; } return false; } LDataStoreI *ScribeWnd::CreateDataStore(char *Full, bool CreateIfMissing) { char *Ext = LGetExtension(Full); if (Ext) { if (!_stricmp(Ext, "mail2")) { LgiMsg(this, LLoadString(IDS_MAIL2_DEPRECATED), AppName, MB_OK, Full); } else if (!_stricmp(Ext, "mail3")) { return OpenMail3(Full, this, CreateIfMissing); } else if (!_stricmp(Ext, "sqlite")) { LTrimDir(Full); return OpenMail3(Full, this, CreateIfMissing); } else { LgiTrace("%s:%i - Not a valid mail store extension: %s\n", _FL, Full); LAssert(!"Not a valid mail store extension."); } } else LgiTrace("%s:%i - No extension for CreateDataStore: %s\n", _FL, Full); return NULL; } class MailStoreUpgrade : public LProgressDlg, public LThread, public LDataPropI { public: ScribeWnd *App = NULL; LDataStoreI *Ds = NULL; int Status = -1; LString Error; MailStoreUpgrade(ScribeWnd *app, LDataStoreI *ds) : LThread("MailStoreUpgrade") { App = app; Ds = ds; SetCanCancel(false); SetDescription("Upgrading mail store..."); Run(); } ~MailStoreUpgrade() { Cancel(); WaitForExit(); } void OnPulse() override { if (IsCancelled()) { EndModal(0); return; } return LProgressDlg::OnPulse(); } LDataPropI &operator =(LDataPropI &p) { LAssert(0); return *this; } Store3Status SetStr(int id, const char *str) override { switch (id) { case Store3UiError: Error = str; break; default: LAssert(!"Impl me."); return Store3Error; break; } return Store3Success; } int Main() override { Status = Ds->Upgrade(this, this); Cancel(); return 0; } }; bool ScribeWnd::LoadMailStores() { bool Status = false; LXmlTag *MailStores = GetOptions()->LockTag(OPT_MailStores, _FL); if (!MailStores) return false; bool OptionsDirty = false; int StoreIdx = 0; for (auto MailStore: MailStores->Children) { if (!MailStore->IsTag(OPT_MailStore)) continue; // Read the folders.. auto Path = MailStore->GetAttr(OPT_MailStoreLocation); auto ContactUrl = MailStore->GetAttr(OPT_MailStoreContactUrl); auto CalUrl = MailStore->GetAttr(OPT_MailStoreCalendarUrl); if (!Path && !ContactUrl && !CalUrl) { LgiTrace("%s:%i - No mail store path (%i).\n", _FL, StoreIdx); continue; } char *StoreName = MailStore->GetAttr(OPT_MailStoreName); if (!StoreName) { char Tmp[256]; for (int i=1; true; i++) { sprintf_s(Tmp, sizeof(Tmp), "Folders%i", i); if (!HasMailStore(MailStores, Tmp)) break; } MailStore->SetAttr(OPT_MailStoreName, Tmp); StoreName = MailStore->GetAttr(OPT_MailStoreName); OptionsDirty = true; } Folders[StoreIdx].Name = StoreName; if (MailStore->GetAsInt(OPT_MailStoreDisable) > 0) { // LgiTrace("%s:%i - Mail store '%i' is disabled.\n", _FL, StoreIdx); continue; } if (Path) { // Mail3 folders on disk... char Full[MAX_PATH_LEN]; if (LIsRelativePath(Path)) { LMakePath(Full, sizeof(Full), GetOptions()->GetFile(), ".."); LMakePath(Full, sizeof(Full), Full, Path); } else { strcpy_s(Full, sizeof(Full), Path); } LVariant CreateFoldersIfMissing; GetOptions()->GetValue(OPT_CreateFoldersIfMissing, CreateFoldersIfMissing); // Sniff type... char *Ext = LGetExtension(Full); if (!Ext) continue; if (!Folders[StoreIdx].Store) Folders[StoreIdx].Store = CreateDataStore(Full, CreateFoldersIfMissing.CastInt32() != 0); if (!Folders[StoreIdx].Store) { LgiTrace("%s:%i - Failed to create data store for '%s'\n", _FL, Full); continue; } Folders[StoreIdx].Path = Full; } else if (ContactUrl || CalUrl) { // Remove Webdav folders... Folders[StoreIdx].Store = new WebdavStore(this, this, StoreName); } else break; LDataStoreI *&Store = Folders[StoreIdx].Store; auto ex = MailStore->GetAsInt(OPT_MailStoreExpanded); if (ex >= 0) Folders[StoreIdx].Expanded = ex != 0; // check if the mail store requires upgrading... Store3Status MsState = (Store3Status)Store->GetInt(FIELD_STATUS); if (MsState == Store3UpgradeRequired) { const char *Details = Store->GetStr(FIELD_STATUS); if (LgiMsg(this, LLoadString(IDS_MAILSTORE_UPGRADE_Q), AppName, MB_YESNO, Folders[StoreIdx].Path.Get(), ValidStr(Details)?Details:"n/a") == IDYES) { MailStoreUpgrade Prog(this, Store); Prog.DoModal(); } else { continue; } } else if (MsState == Store3Error) { auto ErrMsg = Store->GetStr(FIELD_ERROR); LAlert a(this, AppName, ErrMsg ? ErrMsg : LLoadString(IDS_ERROR_FOLDERS_STATUS), LLoadString(IDS_EDIT_MAIL_STORES), LLoadString(IDS_OK)); int Btn = a.DoModal(); if (Btn == 1) { PostEvent(M_COMMAND, IDM_MANAGE_MAIL_STORES); break; } continue; } // check password const char *FolderPsw; if ((FolderPsw = Store->GetStr(FIELD_STORE_PASSWORD))) { bool Verified = false; if (ValidStr(d->MulPassword)) { Verified = d->MulPassword.Equals(FolderPsw, false); d->MulPassword.Empty(); } if (!Verified) { LInput Dlg(this, "", LLoadString(IDS_ASK_FOLDER_PASS), AppName, true); if (Dlg.DoModal() == IDOK) { GPassword User; User.Set(Dlg.GetStr()); Verified = strcmp(Dlg.GetStr(), FolderPsw) == 0; } } if (!Verified) { DeleteObj(Store); } } // process folders if (Store->GetInt(FIELD_VERSION) == 0) { // version error LgiMsg(this, LLoadString(IDS_ERROR_FOLDERS_VERSION), AppName, MB_OK, 0, Store->GetInt(FIELD_VERSION)); } else { if (Store->GetInt(FIELD_READONLY)) { LgiMsg(this, LLoadString(IDS_ERROR_READONLY_FOLDERS), AppName); } // get root item LDataFolderI *Root = Store->GetRoot(); if (Root) { ScribeFolder *&Mailbox = Folders[StoreIdx].Root; Mailbox = new ScribeFolder; if (Mailbox) { Mailbox->App = this; Mailbox->SetObject(Root, false, _FL); Root->SetStr(FIELD_FOLDER_NAME, StoreName); Root->SetInt(FIELD_FOLDER_TYPE, MAGIC_NONE); } #ifdef TEST_OBJECT_SIZE // debug/repair code if (Root->StoreSize != Root->Sizeof()) { SizeErrors[0]++; Root->StoreSize = Root->Sizeof(); if (Root->Object) { Root->Object->StoreDirty = true; } } #endif // Insert the root object and then... Tree->Insert(Mailbox); // Recursively load the rest of the tree { LProfile p("Loadfolders"); Mailbox->LoadFolders(); } // This forces a re-pour to re-order the folders according to their // sort settings. Tree->UpdateAllItems(); if (ScribeState != ScribeExiting) { // Show the tree Mailbox->Expanded(Folders[StoreIdx].Expanded); // Checks the folders for a number of required objects // and creates them if required auto StoreType = Store->GetInt(FIELD_STORE_TYPE); if (StoreType == Store3Sqlite) Validate(&Folders[StoreIdx]); else if (StoreType < 0) LAssert(!"Make sure you impl the FIELD_STORE_TYPE field in the store."); // FIXME // AddFolderToMru(Full); } // LYield(); Status = true; } } StoreIdx++; } if (Status) { // Force load some folders... ScribeFolder *Folder = GetFolder(FOLDER_CALENDAR); if (Folder) Folder->LoadThings(); Folder = GetFolder(FOLDER_FILTERS); if (Folder) Folder->LoadThings(); for (auto ms: Folders) { if (!ms.Root) continue; for (auto c = ms.Root->GetChildFolder(); c; c = c->GetNextFolder()) { if (c->GetItemType() == MAGIC_CONTACT || c->GetItemType() == MAGIC_FILTER) c->LoadThings(); } } List c; GetContacts(c); // Set selected folder to inbox by default // if the user hasn't selected a folder already if (ScribeState != ScribeExiting && Tree && !Tree->Selection()) { LVariant StartInFolder; GetOptions()->GetValue(OPT_StartInFolder, StartInFolder); ScribeFolder *Start = 0; if (ValidStr(StartInFolder.Str())) { Start = GetFolder(StartInFolder.Str()); } if (!Start) { Start = GetFolder(FOLDER_INBOX); } if (Start && Tree) { Tree->Select(Start); } } } GetOptions()->DeleteValue(OPT_CreateFoldersIfMissing); if (OptionsDirty) SaveOptions(); GetOptions()->Unlock(); // Set system folders ScribeFolder *f = GetFolder(FOLDER_INBOX); if (f) f->SetSystemFolderType(Store3SystemInbox); f = GetFolder(FOLDER_OUTBOX); if (f) f->SetSystemFolderType(Store3SystemOutbox); f = GetFolder(FOLDER_SENT); if (f) f->SetSystemFolderType(Store3SystemSent); f = GetFolder(FOLDER_SPAM); if (f) f->SetSystemFolderType(Store3SystemSpam); return Status; } bool ScribeWnd::LoadFolders() { bool Status = false; AppState PrevState = ScribeState; ScribeState = ScribeLoadingFolders; // Setup Mailstores tag { LXmlTag *MailStores = GetOptions()->LockTag(OPT_MailStores, _FL); if (!MailStores) { // Check if we can upgrade the old folder tag char n[32]; sprintf_s(n, sizeof(n), "%s-Folders", LGetOsName()); LVariant OldFolders; GetOptions()->GetValue(n, OldFolders); // Create mail store element.. GetOptions()->CreateTag(OPT_MailStores); if ((MailStores = GetOptions()->LockTag(OPT_MailStores, _FL))) { if (OldFolders.Str()) { LXmlTag *Store = MailStores->CreateTag(OPT_MailStore); if (Store) { char Opts[MAX_PATH_LEN]; LMakePath(Opts, sizeof(Opts), GetOptions()->GetFile(), ".."); auto Rel = LMakeRelativePath(Opts, OldFolders.Str()); Store->SetAttr(OPT_MailStoreLocation, Rel ? Rel.Get() : OldFolders.Str()); // No need to ask the user for a store name, it'll be // asked later in this method anyway... // Leave the old folder tag in the xml in case the user // downgrades to v1.xx } } } } if (!MailStores) return false; GetOptions()->Unlock(); } // Set loading flags CmdSend.Enabled(false); CmdReceive.Enabled(false); CmdPreview.Enabled(false); Status = LoadMailStores(); if (Tree) { for (auto a: Accounts) { if (!a->Receive.Disabled() && a->Receive.IsPersistant()) a->Receive.Connect(0, false); } } if (Folders.Length() == 0) { ScribeFolderDlg Dlg(this); if (Dlg.DoModal() == IDOK) { bool CreateMailStore = false; if (Dlg.Create) { // create folders if (LFileExists(Dlg.FolderFile)) { if (LgiMsg(this, LLoadString(IDS_ERROR_FOLDERS_ALREADY_EXIST), AppName, MB_YESNO) == IDYES) { CreateMailStore = true; } else { LgiMsg(this, LLoadString(IDS_ERROR_WONT_OVERWRITE_FOLDERS), AppName); } } else if ((Status = CreateFolders(Dlg.FolderFile))) { CreateMailStore = true; } } else { CreateMailStore = true; } if (CreateMailStore) { LXmlTag *MailStores = GetOptions()->LockTag(OPT_MailStores, _FL); if (MailStores) { LXmlTag *Store = MailStores->CreateTag(OPT_MailStore); if (Store) { char p[MAX_PATH_LEN]; LMakePath(p, sizeof(p), GetOptions()->GetFile(), ".."); auto RelPath = LMakeRelativePath(p, Dlg.FolderFile); Store->SetAttr(OPT_MailStoreLocation, RelPath ? RelPath.Get() : Dlg.FolderFile.Get()); } GetOptions()->Unlock(); LoadMailStores(); } } } } if (ScribeState == ScribeExiting) { LCloseApp(); } else { d->FoldersLoaded = true; PostEvent(M_SCRIBE_LOADED); } if (ScribeState == ScribeExiting) LCloseApp(); ScribeState = PrevState; return Status; } bool ScribeWnd::UnLoadFolders() { if (FolderTasks.Length() > 0 || ScribeState == ScribeLoadingFolders) { // Um, we can't unload folders right now // something is already happening... return false; } AppState PrevState = ScribeState; ScribeState = ScribeUnloadingFolders; OnSelect(); if (MailList) { ScribeFolder *Container = MailList->GetContainer(); if (Container) { // save folder settings Container->SerializeFieldWidths(); } MailList->SetContainer(NULL); MailList->RemoveAll(); } int Error = 0; while (Thing::DirtyThings.Length() > 0) { if (!SaveDirtyObjects()) { Error++; LgiTrace("%s:%i - SaveDirtyObjects failed.\n", _FL); if (Error > 100) { // I think we're stuck... return false; } } } // Unload IMAP folders... for (auto a: Accounts) { if (!a->Receive.Disabled() && a->Receive.IsPersistant()) { a->Receive.Disconnect(); } } // Unload local folders... LXmlTag *MailStores = GetOptions()->LockTag(OPT_MailStores, _FL); for (size_t i=0; iExpanded(); for (auto ms: MailStores->Children) { char *StoreName = ms->GetAttr(OPT_MailStoreName); if (Folders[i].Name.Equals(StoreName)) { ms->SetAttr(OPT_MailStoreExpanded, Expanded); break; } } } DeleteObj(Folders[i].Root); DeleteObj(Folders[i].Store); } if (MailStores) { GetOptions()->Unlock(); MailStores = NULL; } Folders.Length(0); d->FoldersLoaded = false; if (ScribeState == ScribeExiting) LCloseApp(); ScribeState = PrevState; return true; } void ScribeWnd::BuildDynMenus() { if (MailMenu) { LString SendMail = LLoadString(IDS_SEND_MAIL); LString ReceiveMail = LLoadString(IDS_RECEIVE_MAIL); LString PreviewMail = LLoadString(IDS_PREVIEW_ON_SERVER); auto ReceiveAll = LLoadString(IDS_RECEIVE_ALL_ACCOUNTS); if (!CmdReceive.MenuItem && ReceiveAll) CmdReceive.MenuItem = MailMenu->AppendItem(ReceiveAll, IDM_RECEIVE_ALL, true); if (!SendMenu && SendMail) { auto s = SendMail.SplitDelimit("\t"); SendMenu = MailMenu->AppendSub(s[0]); } if (!ReceiveMenu && ReceiveMail) { auto s = ReceiveMail.SplitDelimit("\t"); ReceiveMenu = MailMenu->AppendSub(s[0]); } if (!PreviewMenu && PreviewMail) { auto s = PreviewMail.SplitDelimit("\t"); PreviewMenu = MailMenu->AppendSub(s[0]); } } if (!NewTemplateMenu) { auto i = Menu->FindItem(IDM_NO_TEMPLATES); if (i) { NewTemplateMenu = i->GetParent(); } } if (NewTemplateMenu) { NewTemplateMenu->Empty(); int d = 0; ScribeFolder *Templates = GetFolder(FOLDER_TEMPLATES, NULL, true); if (Templates) { Templates->LoadThings(); for (auto i: Templates->Items) { Mail *m = i->IsMail(); if (m) { NewTemplateMenu->AppendItem(m->GetSubject() ? m->GetSubject() : (char*)"(no subject)", IDM_NEW_FROM_TEMPLATE + d, true); d++; } } if (d == 0) { NewTemplateMenu->AppendItem(LLoadString(IDS_NO_ITEMS_IN_FOLDER), -1, false); } } else { NewTemplateMenu->AppendItem(LLoadString(IDS_NO_TEMPLATES), -1, false); } } } int ScribeWnd::GetToolbarHeight() { return (Commands) ? MAX(Commands->Y()-1, 20) : 20; } LToolBar *ScribeWnd::LoadToolbar(LViewI *Parent, const char *File, LAutoPtr &Img) { if (!Img) Img.Reset(LLoadImageList(File)); if (!Img) { LAssert(!"Missing image resource."); return NULL; } LToolBar *Tools = NULL; if (Img) { Tools = new LToolBar; if (Tools) Tools->SetImageList(Img, Img->TileX(), Img->TileY(), false); } else { Tools = LgiLoadToolbar(Parent, File); } if (Tools) { LVariant SizeAdj; LFont *f = Tools->GetFont(); if (f) { if (GetOptions()->GetValue(OPT_UiFontSize, SizeAdj)) { SizeAdj.Cast(GV_INT32); SizeAdj.Value.Int -= 2; f->PointSize(f->PointSize()+SizeAdj.Value.Int); } } Tools->GetCss(true)->BorderSpacing(LCss::Len(LCss::LenPx, SCRIBE_TOOLBAR_BORDER_SPACING_PX)); Tools->TextLabels(ShowToolbarText()); } return Tools; } void ScribeWnd::SetListPane(LView *v) { ThingList *ThingLst = dynamic_cast(v); DynamicHtml *Html = dynamic_cast(v); if (!ThingLst) { DeleteObj(SearchView); if (MailList) MailList->RemoveAll(); } v->Sunken(SUNKEN_CTRL); if ((MailList = ThingLst)) { DeleteObj(TitlePage); if (GetCtrlValue(IDM_ITEM_FILTER)) { OnCommand(IDM_ITEM_FILTER, 0, NULL); } } else { TitlePage = Html; } SetLayout(); } bool ScribeWnd::SetItemPreview(LView *v) { v->Sunken(SUNKEN_CTRL); if (d->SubSplit->IsAttached()) { if (d->LastLayout == 2) { Splitter->SetViewAt(1, v); } else { d->SubSplit->SetViewAt(1, v); } return true; } return false; } ScribeWnd::LayoutMode ScribeWnd::GetEffectiveLayoutMode() { LVariant Mode; GetOptions()->GetValue(OPT_LayoutMode, Mode); ScribeFolder *Cur = GetCurrentFolder(); if (Cur && Cur->IsRoot()) { Mode = FoldersAndList; } if (Mode.CastInt32() == 0) { Mode = FoldersListAndPreview; } return (LayoutMode) Mode.CastInt32(); } void ScribeWnd::SetLayout(LayoutMode Mode) { // int TreeWidth = Tree ? Tree->X() : 200; // int PreviewHt = PreviewPanel ? PreviewPanel->Y() : 200; if (Mode > 0) { LVariant v; GetOptions()->SetValue(OPT_LayoutMode, v = (int)Mode); } Mode = GetEffectiveLayoutMode(); if (!Splitter) return; bool JustPreviewPane = (Mode == FoldersAndList && d->LastLayout == FoldersListAndPreview) || (Mode == FoldersListAndPreview && d->LastLayout == FoldersAndList); LView *Content = NULL; if (TitlePage) Content = TitlePage; else if (MailList) Content = MailList; if (JustPreviewPane) { // Optimized path for hide/show the preview pane that doesn't destroy the tree // control and cause it to lose focus... otherwise we can't set it's focus due // to some weird windows interaction. switch (Mode) { default: case FoldersListAndPreview: { if (Content) Content->Sunken(SUNKEN_CTRL); if (PreviewPanel) PreviewPanel->Sunken(SUNKEN_CTRL); Splitter->SetViewAt(1, d->SubSplit); d->SubSplit->SetVertical(true); int Idx = 0; if (SearchView) d->SubSplit->SetViewAt(Idx++, SearchView); d->SubSplit->SetViewAt(Idx++, Content); d->SubSplit->SetViewAt(Idx++, PreviewPanel); break; } case FoldersAndList: { if (Content) Content->Sunken(SUNKEN_CTRL); if (SearchView) { #if LGI_VIEW_HANDLE if (!d->SubSplit->Handle()) Splitter->SetViewAt(1, d->SubSplit); #endif d->SubSplit->SetVertical(true); Splitter->SetViewAt(1, d->SubSplit); int Idx = 0; if (SearchView) d->SubSplit->SetViewAt(Idx++, SearchView); d->SubSplit->SetViewAt(Idx++, Content); } else { d->SubSplit->Detach(); Splitter->SetViewAt(1, Content); } break; } } } else { if (Tree) Tree->Sunken(SUNKEN_CTRL); if (Content) Content->Sunken(SUNKEN_CTRL); if (PreviewPanel) PreviewPanel->Sunken(SUNKEN_CTRL); switch (Mode) { default: case FoldersListAndPreview: { Splitter->SetVertical(false); d->SubSplit->SetVertical(true); Splitter->SetViewAt(0, Tree); Splitter->SetViewAt(1, d->SubSplit); int Idx = 0; if (SearchView) d->SubSplit->SetViewAt(Idx++, SearchView); d->SubSplit->SetViewAt(Idx++, Content); d->SubSplit->SetViewAt(Idx++, PreviewPanel); DeleteObj(d->SearchSplit); break; } case PreviewOnBottom: { Splitter->SetVertical(true); d->SubSplit->SetVertical(false); Splitter->SetViewAt(0, d->SubSplit); Splitter->SetViewAt(1, PreviewPanel); d->SubSplit->SetViewAt(0, Tree); if (SearchView) { if (!d->SearchSplit) d->SearchSplit = new LBox; d->SubSplit->SetViewAt(1, d->SearchSplit); d->SearchSplit->SetVertical(true); d->SearchSplit->SetViewAt(0, SearchView); d->SearchSplit->SetViewAt(1, Content); } else { d->SubSplit->SetViewAt(1, Content); DeleteObj(d->SearchSplit); } break; } case FoldersAndList: { Splitter->SetVertical(false); Splitter->SetViewAt(0, Tree); if (SearchView) { d->SubSplit->SetVertical(true); Splitter->SetViewAt(1, d->SubSplit); d->SubSplit->SetViewAt(0, SearchView); d->SubSplit->SetViewAt(1, Content); } else { d->SubSplit->Detach(); Splitter->SetViewAt(1, Content); } DeleteObj(d->SearchSplit); break; } case ThreeColumn: { Splitter->SetVertical(false); Splitter->SetViewAt(0, Tree); if (SearchView) { d->SubSplit->SetVertical(true); Splitter->SetViewAt(1, d->SubSplit); d->SubSplit->SetViewAt(0, SearchView); d->SubSplit->SetViewAt(1, Content); } else { d->SubSplit->Detach(); Splitter->SetViewAt(1, Content); } Splitter->SetViewAt(2, PreviewPanel); DeleteObj(d->SearchSplit); break; } } } if (!SearchView) { LVariant Pos = 200; GetOptions()->GetValue(OPT_SplitterPos, Pos); if (Pos.CastInt32() < 10) Pos = 200; Splitter->Value(Pos.CastInt32()); LRect r = Splitter->GetPos(); r.x2++; Splitter->SetPos(r); if (d->SubSplit->IsAttached()) { Pos = 200; GetOptions()->GetValue(OPT_SubSplitPos, Pos); if (Pos.CastInt32() < 10) Pos = 200; d->SubSplit->Value(Pos.CastInt32()); } } PourAll(); #ifdef LINUX LYield(); #endif d->LastLayout = Mode; } void ScribeWnd::SetupUi() { // Show the window if (!SerializeState(GetOptions(), OPT_ScribeWndPos, true)) { LRect r(0, 0, 1023, 767); SetPos(r); MoveToCenter(); } Visible(true); // Main toolbar Commands = LoadToolbar(this, GetResourceFile(ResToolbarFile), ToolbarImgs); if (Commands) { Commands->Attach(this); #ifdef MAC Commands->Raised(false); #else Commands->Raised(RAISED_LOOK); #endif Commands->AppendButton(RemoveAmp(LLoadString(IDS_NEW_EMAIL)), IDM_NEW_EMAIL, TBT_PUSH, true, IMG_NEW_MAIL); Commands->AppendButton(RemoveAmp(LLoadString(IDS_NEW_CONTACT)), IDM_NEW_CONTACT, TBT_PUSH, true, IMG_NEW_CONTACT); Commands->AppendSeparator(); CmdSend.ToolButton = Commands->AppendButton(RemoveAmp(LLoadString(IDS_SEND)), IDM_SEND_MAIL, TBT_PUSH, true, IMG_SEND); Commands->AppendButton("+", IDM_RECEIVE_AND_SEND, TBT_PUSH, true, -2); CmdReceive.ToolButton = Commands->AppendButton(RemoveAmp(LLoadString(IDS_RECEIVE)), IDM_RECEIVE_MAIL, TBT_PUSH, true, IMG_RECEIVE); CmdPreview.ToolButton = Commands->AppendButton(RemoveAmp(LLoadString(IDS_PREVIEW)), IDM_PREVIEW_POP3, TBT_PUSH, true, IMG_PREVIEW); Commands->AppendSeparator(); Commands->AppendButton(RemoveAmp(LLoadString(IDS_DELETE)), IDM_DELETE, TBT_PUSH, true, IMG_TRASH); Commands->AppendButton(RemoveAmp(LLoadString(IDS_SPAM)), IDM_DELETE_AS_SPAM, TBT_PUSH, true, IMG_DELETE_SPAM); Commands->AppendSeparator(); Commands->AppendButton(RemoveAmp(LLoadString(IDS_REPLY)), IDM_REPLY, TBT_PUSH, true, IMG_REPLY); Commands->AppendButton(RemoveAmp(LLoadString(IDS_REPLYALL)), IDM_REPLY_ALL, TBT_PUSH, true, IMG_REPLY_ALL); Commands->AppendButton(RemoveAmp(LLoadString(IDS_FORWARD)), IDM_FORWARD, TBT_PUSH, true, IMG_FORWARD); Commands->AppendButton(RemoveAmp(LLoadString(IDS_BOUNCE)), IDM_BOUNCE, TBT_PUSH, true, IMG_BOUNCE); Commands->AppendSeparator(); Commands->AppendButton(RemoveAmp(LLoadString(IDS_PRINT)), IDM_PRINT, TBT_PUSH, true, IMG_PRINT); Commands->AppendButton(RemoveAmp(LLoadString(IDS_CALENDAR)), IDM_CALENDAR, TBT_PUSH, true, IMG_CALENDAR); Commands->AppendButton(RemoveAmp(LLoadString(IDS_ITEM_FILTER)), IDM_ITEM_FILTER, TBT_TOGGLE, true, IMG_SEARCH); Commands->AppendButton(RemoveAmp(LLoadString(IDS_THREAD)), IDM_THREAD, TBT_TOGGLE, true, IMG_THREADS); d->ShowConsoleBtn = Commands->AppendButton(RemoveAmp(LLoadString(IDS_SHOW_CONSOLE)), IDM_SHOW_CONSOLE, TBT_PUSH, true, IMG_CONSOLE_NOMSG); Commands->AppendSeparator(); Commands->AppendButton(RemoveAmp(LLoadString(IDS_HELP)), IDM_HELP, TBT_PUSH, true, IMG_HELP); Commands->Customizable(GetOptions(), OPT_ScribeWndToolbar); if (d->ScriptToolbar.Reset(new LScriptUi(Commands))) d->ScriptToolbar->SetupCallbacks(this, 0, 0, LApplicationToolbar); PourAll(); } CmdSend.Enabled(false); CmdReceive.Enabled(false); // Preview and status windows PreviewPanel = new LPreviewPanel(this); StatusPanel = new AccountStatusPanel(this, ImageList); if (PreviewPanel && StatusPanel) { #ifdef MAC StatusPanel->Raised(false); #else StatusPanel->Raised(RAISED_LOOK); #endif StatusPanel->Attach(this); PourAll(); } // Splitter window, for folders and item list Tree = new MailTree(this); if (ImageList) { Tree->AskImage(true); Tree->SetImageList(ImageList, false); } d->SubSplit = new LBox; Splitter = new LBox; if (Splitter) { Splitter->Attach(this); #ifdef MAC Splitter->Raised(false); #else Splitter->Raised(RAISED_LOOK); #endif SetLayout(); } #if WINNATIVE TrayIcon.Load(MAKEINTRESOURCE(IDI_SMALL)); TrayIcon.Load(MAKEINTRESOURCE(IDI_ERR)); TrayIcon.Load(MAKEINTRESOURCE(IDI_MAIL)); TrayIcon.Load(MAKEINTRESOURCE(IDI_BLANK)); #else TrayIcon.Load(_T("tray_small.png")); TrayIcon.Load(_T("tray_error.png")); TrayIcon.Load(_T("tray_mail.png")); #endif LStringPipe s(256); LVariant UserName; s.Print("%s", AppName); if (GetOptions()->GetValue(OPT_UserName, UserName)) { s.Print(" [%s]", UserName.Str()); } auto AppTitle = s.NewGStr(); TrayIcon.Name(AppTitle); Name(AppTitle); TrayIcon.Value(TRAY_ICON_NORMAL); TrayIcon.Visible(true); auto Item = Menu->FindItem(IDM_SCRIPTING_CONSOLE); if (Item) { LVariant v; if (GetOptions()->GetValue(OPT_ShowScriptConsole, v)) { Item->Checked(v.CastInt32() != 0); LScribeScript::Inst->ShowScriptingWindow(Item->Checked()); } } if (Tree) { Tree->Focus(true); } } Thing *ScribeWnd::CreateItem(int Type, ScribeFolder *Folder, bool Ui) { auto FolderStore = Folder && Folder->GetObject() ? Folder->GetObject()->GetStore() : NULL; auto DefaultStore = GetDefaultMailStore(); auto Store = FolderStore ? FolderStore : (DefaultStore ? DefaultStore->Store : NULL); if (!Store) { LAssert(!"no store"); LgiTrace("%s:%i - No store for creating calendar object.\n", _FL); return NULL; } auto Obj = Store->Create(Type); if (!Obj) { LAssert(!"create failed"); LgiTrace("%s:%i - store failed to create object.\n", _FL); return NULL; } #define HANDLE_CREATE_ITEM(Magic, Type) \ case Magic: \ { \ auto o = new Type(this, Obj); \ if (!o) \ { \ LgiTrace("%s:%i - Alloc failed.\n", _FL); \ break; \ } \ if (Folder) o->SetParentFolder(Folder); \ if (Ui) o->DoUI(); \ return o; \ } switch ((uint32_t)Type) { case MAGIC_MAIL: { // create a new mail message Mail *m = new Mail(this, Obj); if (!m) { LgiTrace("%s:%i - Alloc failed.\n", _FL); break; } if (!m->GetObject()) { m->DecRef(); return 0; } m->OnCreate(); if (Folder) m->SetParentFolder(Folder); if (Ui) m->DoUI(); return m; } HANDLE_CREATE_ITEM(MAGIC_CONTACT, Contact) HANDLE_CREATE_ITEM(MAGIC_CALENDAR, Calendar) HANDLE_CREATE_ITEM(MAGIC_FILTER, Filter) HANDLE_CREATE_ITEM(MAGIC_GROUP, ContactGroup) default: LAssert(!"Unhandled object type."); break; } return NULL; } void ScribeWnd::OnPaint(LSurface *pDC) { #if 0 pDC->Colour(LColour::Red); pDC->Rectangle(); #else LCssTools Tools(this); auto c = GetClient(); // c.Offset(0, -26); Tools.PaintContent(pDC, c); #endif } int CompareContacts(Contact **a, Contact **b) { if (a && b) { auto A = (*a)->GetFirst(); auto B = (*b)->GetFirst(); if (A && B) return _stricmp(A, B); } return 0; } bool ScribeWnd::OpenAMail(ScribeFolder *Folder) { if (Folder && Tree) { Folder->LoadThings(); for (auto i: Folder->Items) { Mail *m = i->IsMail(); if (m && !(m->GetFlags() & MAIL_READ)) { Tree->Select(Folder); m->DoUI(); return true; } } for (auto *t=Folder->GetChild(); t; t=t->GetNext()) { ScribeFolder *f = dynamic_cast(t); if (OpenAMail(f)) { return true; } } } return false; } void AddContactToMenu(LSubMenu *Menu, Contact *c, ssize_t Index) { if (!c || Index < 0) return; auto Email = c->GetEmail(); if (!Email) return; // has an email, list it auto First = c->GetFirst(); auto Last = c->GetLast(); if (First || Last) { char Buf[256]; sprintf_s(Buf, sizeof(Buf), "%s %s", (First)?First:"", (Last)?Last:""); auto Item = Menu->AppendItem(Buf, TRAY_CONTACT_BASE + (int)Index, true); if (Item) Item->Icon(ICON_CONTACT); } } void ScribeWnd::AddContactsToMenu(LSubMenu *Menu) { if (!Menu) return; d->TrayMenuContacts.Sort(CompareContacts); if (((ssize_t)d->TrayMenuContacts.Length() << 4) > GdcD->Y() - 200) { // Group contacts by starting letter LArray Alpha[26]; LArray Other; for (auto c: d->TrayMenuContacts) { auto First = c->GetFirst(); auto Last = c->GetLast(); auto Email = c->GetEmail(); if (Email) { // has an email, list it if (First || Last) { auto Name = First?First:Last; if ( (*Name >= 'a' && *Name <= 'z') || (*Name >= 'A' && *Name <= 'Z') ) { int Ind = tolower(*Name) - 'a'; if (Ind >= 0 && Ind < CountOf(Alpha)) Alpha[Ind].Add(c); else Other.Add(c); } else Other.Add(c); } } } for (int i=0; i 0) { char Group[64]; sprintf_s(Group, sizeof(Group), "%c...", 'a' + i); auto Sub = Menu->AppendSub(Group); if (Sub) { for (auto c: Alpha[i]) AddContactToMenu(Sub, c, d->TrayMenuContacts.IndexOf(c)); } } } if (Other.Length()) { auto Sub = Menu->AppendSub("Other..."); if (Sub) { for (auto c: Other) AddContactToMenu(Sub, c, d->TrayMenuContacts.IndexOf(c)); } } } else { // Display all... for (size_t i=0; iTrayMenuContacts.Length(); i++) { AddContactToMenu(Menu, d->TrayMenuContacts[i], i); } } } void ScribeWnd::OnUrl(const char *Url) { LUri u(Url); if (u.sProtocol && !_stricmp(u.sProtocol, "mailto")) { CreateMail(0, Url, 0); } } void ScribeWnd::OnReceiveFiles(LArray &Files) { int UnknownFormats = 0; int64 Period = LCurrentTime() - LastDrop; if (Period > 500) // Lock out drops within 500ms of an LGI drop { LString sSend, sPages; bool HasSend = LAppInst->GetOption("send", sSend); bool HasPrint = LAppInst->GetOption("p", sPages); LArray NewMail; for (unsigned i=0; i str(new LFile); if (str->Open(f, O_READ)) { if (t->Import(t->AutoCast(str), MimeType)) { if (HasSend) { Mail *m = t->IsMail(); if (m) { NewMail.Add(m); m->SetFlags(m->GetFlags() | MAIL_CREATED | MAIL_READ); } } else { t->DoUI(); } } } } } else UnknownFormats++; } bool SendNow = sSend ? atoi(sSend) != 0 : false; for (unsigned i=0; iSetFlags(m->GetFlags() | MAIL_READY_TO_SEND); m->Save(); } else if (HasPrint) { ThingPrint(m, GetPrinter(), 0, sPages ? atoi(sPages) : 0); } else { m->DoUI(); } } if (SendNow) Send(); } } void ScribeWnd::OnTrayClick(LMouse &m) { if (m.Down()) { #ifndef MAC // No support for different mouse button info so the default // action is to show the sub-menu of contacts and actions. if (m.IsContextMenu()) #endif { LWindow::OnTrayClick(m); } #ifndef MAC else if (m.Left()) { if (m.Double()) { if (Mail::NewMailLst.Length() > 0) { ScribeFolder *InBox = GetFolder(FOLDER_INBOX); OpenAMail(InBox); } } else { if (GetZoom() == LZoomMin) { SetZoom(LZoomNormal); } else { if (Obscured()) SetZoom(LZoomNormal); // Bounce in front, first. else SetZoom(LZoomMin); } Visible(true); MoveOnScreen(); Raise(); if (MailList) { MailList->Focus(true); } } } else if (m.Middle()) { Mail::NewMailLst.Empty(); } #endif } } void ScribeWnd::OnTrayMenu(LSubMenu &m) { m.SetImageList(ImageList, false); d->TrayMenuContacts.Length(0); #ifdef MAC m.AppendItem(LLoadString(IDS_OPEN), IDM_OPEN); m.AppendSeparator(); #endif LHashTbl,Contact*> Added; LArray Srcs = GetThingSources(MAGIC_CONTACT); for (auto c: Srcs) { c->LoadThings(); for (auto i: c->Items) { Contact *c = i->IsContact(); if (!c) continue; bool IsAdded = false; auto Emails = c->GetEmails(); for (auto e: Emails) { if (Added.Find(e)) IsAdded = true; } if (Emails.Length() && !IsAdded) { for (auto e: Emails) Added.Add(e, c); d->TrayMenuContacts.Add(c); } } } AddContactsToMenu(&m); m.AppendSeparator(); if (Mail::NewMailLst.Length() > 0) { int i=0; for (auto ml: Mail::NewMailLst) { LStringPipe p; // This code figures out how many UTF characters to print. We // can't split a UTF character because downstream conversions // will fail. const char *Subj = ml->GetSubject(); if (!Subj) Subj = "(No Subject)"; LUtf8Ptr u((uint8_t*)Subj); while ((int32)u && u.GetPtr() - (uchar*)Subj < 64) u++; ssize_t Bytes = u.GetPtr() - (uchar*)Subj; p.Print("%.*s, %s <%s>", Bytes, Subj?Subj:(char*)"(No Subject)", ml->GetFromStr(FIELD_NAME), ml->GetFromStr(FIELD_EMAIL)); LAutoString a(p.NewStr()); LAssert(LIsUtf8(a)); auto Item = m.AppendItem(a, TRAY_MAIL_BASE+i++, true); if (Item) { Item->Icon(ICON_UNREAD_MAIL); } } m.AppendSeparator(); } if (GetZoom() == LZoomMin) { m.AppendItem(LLoadString(IDS_OPEN), IDM_OPEN, true); } auto NewMail = m.AppendItem(LLoadString(IDS_NEW_EMAIL), IDM_NEW_EMAIL); if (NewMail) NewMail->Icon(ICON_UNSENT_MAIL); m.AppendItem(LLoadString(IDS_EXIT), IDM_EXIT); } void ScribeWnd::OnTrayMenuResult(int MenuId) { switch (MenuId) { case IDM_OPEN: { if (GetZoom() == LZoomMin) { SetZoom(LZoomNormal); } Visible(true); Raise(); if (MailList) { MailList->Focus(true); } break; } case IDM_NEW_EMAIL: { CreateMail(); break; } case IDM_EXIT: { d->IngoreOnClose = true; PostEvent(M_CLOSE); break; } default: { auto i = MenuId - TRAY_CONTACT_BASE; Contact *c = d->TrayMenuContacts.IdxCheck(i) ? d->TrayMenuContacts[i] : NULL; if (c) { CreateMail(c); } Mail *m = Mail::NewMailLst[MenuId - TRAY_MAIL_BASE]; if (m) { Mail::NewMailLst.Delete(m); m->DoUI(); } break; } } } void ScribeWnd::OnZoom(LWindowZoom Action) { if (Action == LZoomMin) { LVariant i; if (GetOptions()->GetValue(OPT_MinimizeToTray, i) && i.CastInt32()) { Visible(false); } } } struct UserInput { LView *Parent; LString Result; LString Msg; bool Password, Done; UserInput() { Password = Done = false; } }; LString ScribeWnd::GetUserInput(LView *Parent, LString Msg, bool Password) { if (InThread()) { LInput Inp(Parent ? Parent : this, "", Msg, AppName, Password); return Inp.DoModal() ? Inp.GetStr() : NULL; } UserInput i; i.Parent = Parent; i.Msg = Msg; i.Password = Password; if (!PostEvent(M_GET_USER_INPUT, (LMessage::Param)&i)) { LAssert(!"PostEvent failed."); return NULL; } while (!i.Done) LSleep(50); return i.Result; } LMessage::Result ScribeWnd::OnEvent(LMessage *Msg) { TrayIcon.OnEvent(Msg); BayesianFilter::OnEvent(Msg); switch (Msg->Msg()) { case M_UNIT_TEST: { LAutoPtr j((LJson*)Msg->A()); if (!j) break; auto cmd = j->Get("cmd"); if (!cmd) break; if (cmd.Equals("somecmd")) { } break; } case M_CALENDAR_SOURCE_EVENT: { CalendarSource *cs = (CalendarSource*)Msg->A(); LAutoPtr m((LMessage*)Msg->B()); if (cs && m) cs->OnEvent(m); break; } case M_GET_USER_INPUT: { UserInput *i = (UserInput*)Msg->A(); LAssert(i); LAssert(InThread()); // Least we get stuck in an infinite loop i->Result = GetUserInput(i->Parent, i->Msg, i->Password); i->Done = true; break; } case M_SET_HTML: { LAutoPtr Html((LString*)Msg->A()); if (PreviewPanel && Html) { LVariant Ret; LArray Arg; Arg.Add(new LVariant(Html->Get())); PreviewPanel->CallMethod(DomToStr(SdSetHtml), &Ret, Arg); Arg.DeleteObjects(); } break; } case M_NEW_CONSOLE_MSG: { if (d->ShowConsoleBtn) d->ShowConsoleBtn->Image(IDM_CONSOLE_MSGS); break; } case M_NEEDS_CAP: { LAutoString c((char*)Msg->A()); LAutoString param((char*)Msg->B()); NeedsCapability(c, param); return 0; break; } case M_STORAGE_EVENT: { LDataStoreI *Store = LDataStoreI::Map.Find((int)Msg->A()); if (Store) Store->OnEvent((void*)Msg->B()); break; } case M_SCRIBE_SET_MSG_FLAG: { LAutoString p((char*)Msg->A()); if (p) { char *d = strrchr(p, '/'); if (d) { *d++ = 0; ScribeFolder *f = GetFolder(p); if (f) { LUri u; LString a = u.DecodeStr(d); Mail *r = f->GetMessageById(a); if (r) { int ExistingFlags = r->GetFlags(); int NewFlag = (int)Msg->B(); r->SetFlags(ExistingFlags | NewFlag); } } } } break; } case M_SCRIBE_DEL_THING: { Thing *t = (Thing*)Msg->A(); DeleteObj(t); break; } case M_SCRIBE_LOADED: { if (d->FoldersLoaded) { // Ok let the user in CmdSend.Enabled(true); CmdReceive.Enabled(true); CmdPreview.Enabled(true); } break; } case M_SCRIBE_THREAD_DONE: { // Finialize connection AccountThread *Thread = (AccountThread*) Msg->A(); Accountlet *Acc = (Accountlet*) Msg->B(); if (Thread && Acc) { OnAfterConnect(Acc->GetAccount(), Acc->IsReceive()); d->NewMailTimeout = 2; Acc->OnThreadDone(); } else { LAssert(0); } break; } case M_SCRIBE_MSG: { char *m = (char*)Msg->A(); if (m) { if (Msg->B()) { if (LgiMsg(this, m, AppName, MB_YESNO) == IDYES) { PostEvent(M_COMMAND, IDM_OPTIONS, 0); } } else { LgiMsg(this, "%s", AppName, MB_OK, m); } DeleteArray(m); } break; } /* case M_SCRIBE_LOG_MSG: { List *Log = (List*)Msg->A(); LAutoPtr Entry((LogEntry*)Msg->B()); if (ScribeState != ScribeExiting && Log && Entry) { Log->Insert(Entry.Release()); // Trim long list... while (Log->Length() > 1024) { LogEntry *e = Log->First(); Log->Delete(e); DeleteObj(e); } } break; } */ case M_SCRIBE_NEW_MAIL: { if (Lock(_FL)) { if (NewMailDlg) { NewMailDlg->AddThings(&Mail::NewMailLst); } Unlock(); } break; } case M_SCRIBE_OPEN_THING: { Thing *t = (Thing*) Msg->A(); if (t) { t->DoUI(); } break; } case M_SCRIBE_ITEM_SELECT: { if (!MailList || !IsAttached()) break; List Things; MailList->GetSelection(Things); OnSelect(&Things); break; } case M_URL: { LAutoPtr Url((LString*)Msg->A()); if (Url) { LUri u(*Url); if (u.sProtocol && !_stricmp(u.sProtocol, "mailto")) { Mailto mt(this, *Url); if (mt.To.Length() > 0) { Thing *t = CreateItem(MAGIC_MAIL, NULL, false); if (t) { Mail *m = t->IsMail(); if (m) { mt.Apply(m); m->DoUI(); } else DeleteObj(t); } } } } break; } } return LWindow::OnEvent(Msg); } bool ScribeWnd::IsMyEmail(const char *Email) { if (Email) { LVariant e; for (auto a : *GetAccounts()) { LVariant e = a->Identity.Email(); if (e.Str() && _stricmp(Email, e.Str()) == 0) { return true; } } } return false; } int ScribeWnd::GetMaxPages() { return d->PrintMaxPages; } bool ScribeWnd::ThingPrint(ThingType *m, LPrinter *Printer, LView *Parent, int MaxPages) { int Status = 0; d->PrintMaxPages = MaxPages; if (!Printer) Printer = GetPrinter(); if (!Printer) return false; Thing *t = dynamic_cast(m); if (!t) return false; ScribePrintContext Events(this, t); Status = Printer->Print(&Events, AppName, -1, Parent ? Parent : this); if (Status == Events.OnBeginPrintError) LgiMsg(Parent, "Printing failed: %s", AppName, MB_OK, Printer->GetErrorMsg().Get()); else Status = true; return Status > 0; // Number of pages printed.. } bool ScribeWnd::MailReplyTo(Mail *m, bool All) { bool Status = false; if (m) { LDataStoreI *Store = m->GetObject() ? m->GetObject()->GetStore() : NULL; LDataI *NewMailObj = Store && Store->GetInt(FIELD_STORE_TYPE) == Store3Sqlite ? Store->Create(MAGIC_MAIL) : NULL; Mail *NewMail = new Mail(this, NewMailObj); if (NewMail) { if (NewMail->GetObject()) { NewMail->OnReply(m, All, true); LView *w = NewMail->DoUI(); if (w) { LViewI *t = w->FindControl(IDC_TEXT); if (t) { t->Focus(true); } } Status = true; } else DeleteObj(NewMail); } } return Status; } bool ScribeWnd::MailForward(Mail *m) { bool Status = false; if (m) { Mail *NewMail = new Mail(this); if (NewMail) { if (NewMail->OnForward(m, true)) { NewMail->DoUI(); Status = true; } else { NewMail->DecRef(); } } } return Status; } bool ScribeWnd::MailBounce(Mail *m) { bool Status = false; if (m) { Mail *NewMail = new Mail(this); if (NewMail) { if (NewMail->OnBounce(m, true)) { NewMail->DoUI(); Status = true; } else { DeleteObj(NewMail); } } } return Status; } Mail *ScribeWnd::CreateMail(Contact *c, const char *Email, const char *Name) { Mail *m = dynamic_cast(CreateItem(MAGIC_MAIL, NULL, false)); if (m) { bool IsMailTo = false; if (Email) { IsMailTo = _strnicmp(Email, "mailto:", 7) == 0; if (IsMailTo) { Mailto mt(this, Email); mt.Apply(m); } } MailUi *UI = dynamic_cast(m->DoUI()); if (UI) { if (c) { UI->AddRecipient(c); } if (Email && !IsMailTo) { UI->AddRecipient(Email, Name); } } } return m; } Mail *ScribeWnd::LookupMailRef(const char *MsgRef, bool TraceAllUids) { if (!MsgRef) return 0; LAutoString p(NewStr(MsgRef)); char *RawUid = strrchr(p, '/'); if (RawUid) { *RawUid++ = 0; LUri u; LString Uid = u.DecodeStr(RawUid); // Try the mail message map first... Mail *m = Mail::GetMailFromId(Uid); if (m) return m; // Ok, not found, so look in last known folder... ScribeFolder *f = GetFolder(p); if (f) { for (auto t : f->Items) { Mail *m = t->IsMail(); if (m) { auto s = m->GetMessageId(); if (s && !strcmp(s, Uid)) { return m; } if (TraceAllUids) LgiTrace("\t%s\n", s); } } } } return 0; } void ScribeWnd::OnBayesAnalyse(const char *Msg, const char *WhiteListEmail) { LString s, q; s.Printf("
%s
", Msg); if (WhiteListEmail) { q.Printf(LLoadString(IDS_REMOVE_WHITELIST), WhiteListEmail); s += LString("
") + q; } s += ""; auto result = LHtmlMsg(this, s, AppName, WhiteListEmail ? MB_YESNO : MB_OK); if (result == IDYES) { RemoveFromWhitelist(WhiteListEmail); } } bool ScribeWnd::OnBayesResult(const char *MailRef, double Rating) { Mail *m = LookupMailRef(MailRef); if (m) return OnBayesResult(m, Rating); #ifdef _DEBUG else { LgiTrace("%s:%i - error finding mail ref: %s\n", _FL, MailRef); LookupMailRef(MailRef, true); LAssert(!"We should always be able to resolve the reference, unless m is completely deleted"); } #endif return false; } bool ScribeWnd::OnBayesResult(Mail *m, double Rating) { if (!m) return false; LVariant v; GetOptions()->GetValue(OPT_BayesThreshold, v); double BayesThresh = v.CastDouble(); if (BayesThresh < 0.1) BayesThresh = 0.1; if (BayesThresh > 1.0) BayesThresh = 1.0; if (Rating < BayesThresh) { // Not spam, so we continue new mail processing if (m->NewEmail == Mail::NewEmailBayes) { List Nm; Nm.Insert(m); m->NewEmail = Mail::NewEmailGrowl; OnNewMail(&Nm, true); } return false; } // Spam is pink! m->SetMarkColour(Rgb32(255, 0, 0)); m->SetFlags(m->GetFlags() | MAIL_BAYES_SPAM); ScribeBayesianFilterMode FilterMode = BayesOff; if (GetOptions()->GetValue(OPT_BayesFilterMode, v)) FilterMode = (ScribeBayesianFilterMode)v.CastInt32(); if (FilterMode == BayesTrain) { // Move to folder LVariant MoveToPath; if (!GetOptions()->GetValue(OPT_BayesMoveTo, MoveToPath)) { MoveToPath = "/Spam/Probably"; } ScribeFolder *f = GetFolder(MoveToPath.Str()); if (f) { LArray Items; Items.Add(m); f->MoveTo(Items); List obj; obj.Insert(m); OnNewMail(&obj, false); } } else { m->DeleteAsSpam(this); } return true; } static int AccountCmp(ScribeAccount *a, ScribeAccount *b, int Data) { return a->Identity.Sort() - b->Identity.Sort(); } class ScribePasteState : public LProgressDlg { ScribeWnd *App = NULL; ScribeFolder *Folder = NULL; LAutoPtr Data; ssize_t Size = 0; LDataStoreI::StoreTrans Trans; LProgressPane *LoadPane = NULL, *SavePane = NULL; ScribeClipboardFmt *tl = NULL; uint32_t Errors = 0; ssize_t Idx = 0; enum PasteState { LoadingThings, SavingThings, } State = LoadingThings; public: ScribePasteState(ScribeWnd *app, ScribeFolder *folder, LAutoPtr data, ssize_t size) : LProgressDlg(app), App(app), Folder(folder), Data(data), Size(size) { // Paste 'ScribeThingList' tl = (ScribeClipboardFmt*)Data.Get(); Trans = Folder->GetObject()->GetStore()->StartTransaction(); LoadPane = ItemAt(0); LoadPane->SetDescription("Loading objects..."); LoadPane->SetRange(tl->Length()); SavePane = Push(); SavePane->SetRange(tl->Length()); SavePane->SetDescription("Saving: No errors..."); // LProgressDlg will do a SetPulse in it's OnCreate } void OnPulse() { auto Start = LCurrentTime(); static int TimeSlice = 300; //ms if (State == LoadingThings) { while ( Idx < tl->Length() && !IsCancelled() && LCurrentTime() - Start < TimeSlice) { Thing *t = tl->ThingAt(Idx++); if (!t) continue; auto Obj = t->GetObject(); if (Obj->GetInt(FIELD_LOADED) < Store3Loaded) Obj->SetInt(FIELD_LOADED, Store3Loaded); } Value(Idx); if (Idx >= tl->Length()) { State = SavingThings; Idx = 0; } } else if (State == SavingThings) { while ( Idx < tl->Length() && !IsCancelled() && LCurrentTime() - Start < TimeSlice) { Thing *t = tl->ThingAt(Idx++); if (!t) continue; auto Obj = t->GetObject(); LAssert(Obj->GetInt(FIELD_LOADED) == Store3Loaded); // Load loop should have done this already Thing *Dst = App->CreateItem(Obj->Type(), Folder, false); if (Dst) { *Dst = *t; Dst->Update(); if (!Dst->Save(Folder)) { LString s; s.Printf("Saving: " LPrintfSSizeT " error(s)", ++Errors); SetDescription(s); } } else Errors++; } SavePane->Value(Idx); if (Idx >= tl->Length()) { if (Errors > 0) LgiMsg(this, "Failed to save %i of %i objects.", AppName, MB_OK, Errors, tl->Length()); Quit(); return; } } LProgressDlg::OnPulse(); } }; int ScribeWnd::OnCommand(int Cmd, int Event, OsView WndHandle) { // Send mail multi-menu if (Cmd >= IDM_SEND_FROM && Cmd <= IDM_SEND_FROM + (ssize_t)Accounts.Length()) { Send(Cmd - IDM_SEND_FROM); return 0; } // Receive mail multi-menu if (Cmd >= IDM_RECEIVE_FROM && Cmd < IDM_RECEIVE_FROM + (ssize_t)Accounts.Length()) { Receive(Cmd - IDM_RECEIVE_FROM); return 0; } // Preview mail multi-menu if (Cmd >= IDM_PREVIEW_FROM && Cmd < IDM_PREVIEW_FROM + (ssize_t)Accounts.Length()) { Preview(Cmd - IDM_PREVIEW_FROM); return 0; } // Identity multi-menu if (Cmd >= IDM_IDENTITY_BASE && Cmd <= IDM_IDENTITY_BASE + (ssize_t)Accounts.Length()) { SetCurrentIdentity(Cmd - IDM_IDENTITY_BASE - 1); return 0; } // Is this a script tool? if (LScribeScript::Inst && Cmd >= IDM_TOOL_SCRIPT_BASE && Cmd < IDM_TOOL_SCRIPT_BASE + (int)d->Scripts.Length()) { // Do tools menu callback... find the right callback.... LArray c; if (GetScriptCallbacks(LToolsMenu, c)) { for (unsigned i=0; iFunc && c[i]->Param == Cmd) { // Call the callback char Msg[MAX_PATH_LEN]; LScribeScript::Inst->GetLog()->Write ( Msg, sprintf_s(Msg, sizeof(Msg), "\n\nRunning tool script '%s'...\n", c[i]->Script->Code->GetFileName()) ); // Setup the arguments... LScriptArguments Args(NULL); Args.New() = new LVariant((LDom*)this); Args.New() = new LVariant(Cmd); // Call the method ExecuteScriptCallback(*c[i], Args); // Cleanup Args.DeleteObjects(); break; } } } return 0; } // New from template multi-menu if (Cmd >= IDM_NEW_FROM_TEMPLATE && Cmd < IDM_NEW_FROM_TEMPLATE + 100) { int Index = Cmd - IDM_NEW_FROM_TEMPLATE; ScribeFolder *Templates = GetFolder(FOLDER_TEMPLATES); if (Templates) { Templates->LoadThings(); for (auto i: Templates->Items) { Mail *m = i->IsMail(); if (m) { if (Index == 0) { Thing *t = CreateItem(MAGIC_MAIL, 0, false); // GetFolder(FOLDER_OUTBOX) Mail *NewMail = IsMail(t); if (NewMail) { *NewMail = (Thing&)*m; NewMail->DoUI(); break; } } Index--; } } } return 0; } switch (Cmd) { // File menu case IDM_MANAGE_MAIL_STORES: { ManageMailStores Dlg(this); if (Dlg.DoModal()) { SaveOptions(); if (!UnLoadFolders()) break; LXmlTag *Ms = GetOptions()->LockTag(OPT_MailStores, _FL); if (Ms) { while (Ms->Children.Length()) delete Ms->Children[0]; LXmlTag *t = Dlg.Options.GetChildTag(OPT_MailStores); if (t) { for (auto c: t->Children) { LXmlTag *n = new LXmlTag; n->Copy(*c, true); Ms->InsertTag(n); } } GetOptions()->Unlock(); } LVariant v; GetOptions()->SetValue(OPT_CreateFoldersIfMissing, v = true); if (!Dlg.Options.GetValue(OPT_StartInFolder, v)) v.Empty(); GetOptions()->SetValue(OPT_StartInFolder, v); LoadFolders(); } break; } case IDM_REPLICATE: { ReplicateDlg Dlg(this); if (Dlg.DoModal()) { UnLoadFolders(); Dlg.StartProcess(); } break; } case IDM_SECURITY: { // Check for user perm password... // No point allow any old one to edit the security settings. bool Allow = true; GPassword p; if (p.Serialize(GetOptions(), OPT_UserPermPassword, false)) { Allow = GetAccessLevel(this, PermRequireUser, "Security Settings"); } if (Allow) { SecurityDlg Dlg(this); Dlg.DoModal(); } break; } case IDM_OPTIONS: { LVariant ShowTotals; GetOptions()->GetValue(OPT_ShowFolderTotals, ShowTotals); // do the dialog OptionsDlg Dlg(this); int Status = Dlg.DoModal(); if (Status) { // set up the POP3 accounts SetupAccounts(); SaveOptions(); // close any IMAP accounts that are now disabled. for (auto a : Accounts) { if (a->Receive.IsConfigured() && a->Receive.IsPersistant()) { if (a->Receive.Disabled()) a->Receive.Disconnect(); else Receive(a->GetIndex()); } } // List/Tree view options update LVariant i; if (GetOptions()->GetValue(OPT_ShowFolderTotals, i) && i.CastInt32() != ShowTotals.CastInt32()) { Tree->UpdateAllItems(); } if (GetOptions()->GetValue(OPT_PreviewLines, i)) { Mail::PreviewLines = i.CastInt32() != 0; } if (MailList) { if (GetOptions()->GetValue(OPT_GridLines, i)) { MailList->DrawGridLines(i.CastInt32() != 0); } MailList->Invalidate(); } // date formats if (GetOptions()->GetValue(OPT_DateFormat, i)) { int Idx = i.CastInt32(); if (Idx >= 0 && Idx < CountOf(DateTimeFormats)) { LDateTime::SetDefaultFormat(DateTimeFormats[Idx]); } } if (GetOptions()->GetValue(OPT_AdjustDateTz, i)) Mail::AdjustDateTz = i.CastInt32() == 0; // SSL debug logging if (GetOptions()->GetValue(OPT_DebugSSL, i)) SslSocket::DebugLogging = i.CastInt32() != 0; // Html edit menu if (GetOptions()->GetValue(OPT_EditControl, i)) { auto mi = Menu->FindItem(IDM_HTML_EDITOR); if (mi) mi->Checked(i.CastInt32() != 0); } } break; } case IDM_WORK_OFFLINE: { if (WorkOffline) { WorkOffline->Checked(!WorkOffline->Checked()); LVariant v; GetOptions()->SetValue(OPT_WorkOffline, v = WorkOffline->Checked()); if (!WorkOffline->Checked()) { // Offline -> Online transition. // Check if any pending messages are in the Outbox ScribeFolder *Outbox = GetFolder(FOLDER_OUTBOX); if (Outbox) { bool HasMailToSend = false; for (auto t: Outbox->Items) { Mail *m = t->IsMail(); if (m) { if (TestFlag(m->GetFlags(), MAIL_READY_TO_SEND)) { HasMailToSend = true; break; } } } if (HasMailToSend) { PostEvent(M_COMMAND, IDM_SEND_MAIL, #ifndef __GTK_H__ (LMessage::Param)Handle() #else 0 #endif ); } } } } break; } case IDM_ITEM_FILTER: { if (GetCtrlValue(IDM_ITEM_FILTER)) { if ((SearchView = new LSearchView(this))) { SearchView->Focus(true); SetLayout(); } } else { DeleteObj(SearchView); } ScribeFolder *Folder = GetCurrentFolder(); if (Folder) { Folder->Populate(MailList); } break; } case IDM_PRINT: { if (MailList) { List Sel; if (MailList->LList::GetSelection(Sel)) { for (auto i: Sel) { ThingType *t = dynamic_cast(i); ThingPrint(t); } } } break; } case IDM_PRINTSETUP: { auto *p = GetPrinter(); if (p && p->Browse(this)) { LString Str; if (p->Serialize(Str, true)) { LVariant v; GetOptions()->SetValue(OPT_PrintSettings, v = Str); } } break; } case IDM_PAGE_SETUP: { ScribePageSetup Dlg(this, GetOptions()); Dlg.DoModal(); break; } case IDM_EXIT: { LMouse m; GetMouse(m); d->IngoreOnClose = m.Ctrl(); LCloseApp(); break; } // Edit menu case IDM_FIND: { auto v = GetFocus(); LDocView *doc = dynamic_cast(v); if (doc) { doc->DoFind(); } else { ScribeFolder *Folder = GetCurrentFolder(); OpenFinder(this, Folder); } break; } case IDM_COPY: { if (MailList && MailList->Focus()) { List Lst; if (!MailList->GetSelection(Lst)) break; // Copy 'ScribeThingList' ScribeClipboardFmt *tl = ScribeClipboardFmt::Alloc(Lst); if (!tl) break; LClipBoard Clip(this); if (Clip.IsOpen()) { if (!Clip.Binary(d->ClipboardFormat, (uchar*)tl, tl->Sizeof(), true)) { LgiMsg(this, "Couldn't set the clipboard data.", AppName, MB_OK); } } else { LgiMsg(this, "Couldn't open the clipboard.", AppName, MB_OK); } free(tl); } else { LViewI *v = LAppInst->GetFocus(); if (v) v->PostEvent(M_COPY); } break; } case IDM_PASTE: { LViewI *v = LAppInst->GetFocus(); if (v && v->GetWindow() != (LWindow*)this) { v->PostEvent(M_PASTE); break; } if (!MailList->Focus() && !Tree->Focus()) { LgiTrace("%s:%i - List/Tree doesn't have focus.\n"); break; } ScribeFolder *Folder = dynamic_cast(Tree->Selection()); if (!Folder || !Folder->GetObject()) { LgiMsg(this, "No current folder.", AppName, MB_OK); break; } LClipBoard Clip(this); if (!Clip.IsOpen()) { LgiMsg(this, "Couldn't open the clipboard.", AppName, MB_OK); break; } LAutoPtr Data; ssize_t Size = 0; if (!Clip.Binary(d->ClipboardFormat, Data, &Size)) { LgiMsg(this, "Couldn't get the clipboard data.", AppName, MB_OK); break; } if (ScribeClipboardFmt::IsThing(Data.Get(), Size)) { new ScribePasteState(this, Folder, Data, Size); } break; } case IDM_DELETE: { LViewI *f = LAppInst->GetFocus(); LEdit *e = dynamic_cast(f); if (e) { // This handles the case where on a mac the menu eats the delete key, even // when the edit control needs it LKey k(LK_DELETE, 0); k.Down(true); f->OnKey(k); k.Down(false); f->OnKey(k); } else { OnDelete(); } break; } case IDM_DELETE_AS_SPAM: { if (MailList) { List Sel; MailList->GetSelection(Sel); int Index = -1; for (auto i: Sel) { Mail *m = IsMail(i); if (m) { if (Index < 0) { Index = MailList->IndexOf(i); } m->DeleteAsSpam(this); } } if (Index >= 0) { LListItem *i = MailList->ItemAt(Index); if (!i) i = MailList->ItemAt(MailList->Length()-1); if (i) i->Select(true); } } break; } case IDM_REFRESH: { ScribeFolder *f = GetCurrentFolder(); if (!f) break; const char *s = DomToStr(SdRefresh); f->GetFldObj()->OnCommand(s); break; } // Mail menu case IDM_NEW_EMAIL: { CreateMail(); break; } case IDM_SET_READ: case IDM_SET_UNREAD: { ScribeFolder *f = GetCurrentFolder(); if (!f) break; bool SetRead = Cmd == IDM_SET_READ; f->LoadThings(); LArray Change; for (auto t: f->Items) { Mail *m = t->IsMail(); if (m && m->Select()) Change.Add(m->GetObject()); } LVariant v = MAIL_READ; LDataStoreI *Store = f->GetObject()->GetStore(); if (Store->Change(Change, FIELD_FLAGS, v, SetRead ? OpPlusEquals : OpMinusEquals) == Store3Error) { for (auto t : f->Items) { Mail *m = t->IsMail(); if (!m) continue; if (!m->Select()) continue; if (SetRead) m->SetFlags(m->GetFlags() | MAIL_READ); else m->SetFlags(m->GetFlags() & ~MAIL_READ); } } break; } case IDM_REPLY: case IDM_REPLY_ALL: { if (MailList) MailReplyTo(IsMail(MailList->GetSelected()), (Cmd == IDM_REPLY_ALL)); break; } case IDM_FORWARD: { if (MailList) MailForward(IsMail(MailList->GetSelected())); break; } case IDM_BOUNCE: { if (MailList) MailBounce(IsMail(MailList->GetSelected())); break; } case IDM_SEND_MAIL: { Send(); break; } case IDM_RECEIVE_AND_SEND: { d->SendAfterReceive = true; PostEvent(M_COMMAND, IDM_RECEIVE_MAIL, (LMessage::Param)FindControl(IDM_RECEIVE_MAIL)); break; } case IDM_THREAD: { if (MailList) { ScribeFolder *f = GetCurrentFolder(); if (f) { f->SetThreaded(!f->GetThreaded()); f->Populate(MailList); } } break; } case IDM_RECEIVE_ALL: { #define LOG_RECEIVE_ALL 0 int i = 0; Accounts.Sort(AccountCmp); for (auto a : Accounts) { #if LOG_RECEIVE_ALL auto name = a->Identity.Name(); auto email = a->Identity.Email(); LString desc; desc.Printf("%s/%s", name.Str(), email.Str()); #endif if (!a->Receive.IsConfigured()) { #if LOG_RECEIVE_ALL LgiTrace("%s:%i - %i/%s not configured.\n", _FL, a->GetIndex(), desc.Get()); #endif } else if (a->Receive.Disabled() > 0) { #if LOG_RECEIVE_ALL LgiTrace("%s:%i - %i/%s is disabled.\n", _FL, a->GetIndex(), desc.Get()); #endif } else { #if LOG_RECEIVE_ALL LgiTrace("%s:%i - %i/%s will connect.\n", _FL, a->GetIndex(), desc.Get()); #endif Receive(a->GetIndex()); } i++; } break; } case IDM_RECEIVE_MAIL: { LVariant Def; if (GetOptions()->GetValue(OPT_Pop3DefAction, Def) && Def.CastInt32() == 0) return OnCommand(IDM_RECEIVE_ALL, 0, NULL); Receive(0); break; } case IDM_PREVIEW_POP3: { LArray Account; Accounts.Sort(AccountCmp); for (auto a: Accounts) { if (!a->Receive.IsConfigured()) continue; auto Protocol = ProtocolStrToEnum(a->Receive.Protocol().Str()); if (Protocol == ProtocolPop3) { Account.Add(a); break; } } if (Account.Length() == 1) OpenPopView(this, Account); break; } case IDM_CALENDAR: { extern void OpenCalender(ScribeFolder *folder); ScribeFolder *Folder = GetFolder(FOLDER_CALENDAR); if (Folder) { OpenCalender(Folder); } break; } // Contact menu case IDM_NEW_CONTACT: { CreateItem(MAGIC_CONTACT, NULL); break; } case IDM_NEW_GROUP: { CreateItem(MAGIC_GROUP, NULL); break; } // Filter menu case IDM_NEW_FILTER: { Thing *t = CreateItem(MAGIC_FILTER, NULL, false); if (t) { t->IsFilter()->SetIncoming(true); t->DoUI(); } break; } case IDM_FILTER_CURRENT_FOLDER: { ScribeFolder *Folder = GetCurrentFolder(); if (Folder) { List Filters; GetFilters(Filters, false, false, true); List Src; for (auto i: Folder->Items) { if (i->IsMail()) { Src.Insert(i->IsMail()); } } if (!Src[0]) { LgiMsg(this, LLoadString(IDS_NO_MAIL_TO_FILTER), AppName); } else { Filter::ApplyFilters(this, Filters, Src); } } break; } case IDM_FILTER_SELECTION: { ScribeFolder *Folder = GetCurrentFolder(); if (Folder) { List Filters; GetFilters(Filters, false, false, true); List Src; for (auto i: Folder->Items) { if (i->IsMail() && i->Select()) { Src.Insert(i->IsMail()); } } if (Src.Length()) { Filter::ApplyFilters(this, Filters, Src); } } break; } case IDM_DEBUG_FILTERS: { auto i = Menu->FindItem(IDM_DEBUG_FILTERS); if (i) { i->Checked(!i->Checked()); } break; } case IDM_HTML_EDITOR: { auto i = Menu->FindItem(IDM_HTML_EDITOR); if (i) { i->Checked(!i->Checked()); LVariant v; GetOptions()->SetValue(OPT_EditControl, v = i->Checked() ? 1 : 0); } break; } case IDM_FILTERS_DISABLE: { if (d->DisableUserFilters) { d->DisableUserFilters->Checked(!d->DisableUserFilters->Checked()); LVariant v; GetOptions()->SetValue(OPT_DisableUserFilters, v = d->DisableUserFilters->Checked()); } break; } case IDM_BUILD_BAYES_DB: { BuildSpamDb(); break; } case IDM_BAYES_STATS: { BuildStats(); break; } case IDM_BAYES_SETTINGS: { BayesDlg Dlg(this); if (Dlg.DoModal()) { LVariant i; if (GetOptions()->GetValue(OPT_BayesFilterMode, i)) { ScribeBayesianFilterMode m = ((ScribeBayesianFilterMode)i.CastInt32()); if (m != BayesOff) { LVariant SpamPath, ProbablyPath; GetOptions()->GetValue(OPT_SpamFolder, SpamPath); GetOptions()->GetValue(OPT_BayesMoveTo, ProbablyPath); if (m == BayesFilter) { ScribeFolder *Spam = GetFolder(SpamPath.Str()); if (!Spam) { LMailStore *RelevantStore = GetMailStoreForPath(SpamPath.Str()); if (RelevantStore) { LString p = SpamPath.Str(); LString::Array a = p.SplitDelimit("/"); Spam = RelevantStore->Root; for (unsigned i=1; iGetSubFolder(a[i]); if (!c) c = Spam->CreateSubDirectory(a[i], MAGIC_MAIL); Spam = c; } } } if (Spam) { LVariant v; GetOptions()->SetValue(OPT_HasSpam, v = 1); } } else if (m == BayesTrain) { ScribeFolder *Probably = GetFolder(ProbablyPath.Str()); if (!Probably) { LgiMsg(this, "Couldn't find the folder '%s'", AppName, MB_OK, ProbablyPath.Str()); } } } } } break; } case IDM_BAYES_CHECK: { List Sel; if (MailList) MailList->GetSelection(Sel); for (auto i: Sel) { Thing *t = dynamic_cast(i); if (t) { Mail *m = t->IsMail(); if (m) { d->BayesLog.Empty(); double SpamRating = 0.0; IsSpam(SpamRating, m, true); break; } } } break; } // Tools menu case IDM_SCRIPTING_CONSOLE: case IDM_SHOW_CONSOLE: { ShowScriptingConsole(); if (d->ShowConsoleBtn) d->ShowConsoleBtn->Image(IMG_CONSOLE_NOMSG); break; } case IDM_EXPORT_TEXT_MBOX: { Export_UnixMBox(this); break; } case IDM_IMPORT_CSV: { ImportCsv(this); break; } case IDM_EXPORT_CSV: { ExportCsv(this); break; } case IDM_IMPORT_EML: { ImportEml(this); break; } case IDM_EXPORT_SCRIBE: { extern void ExportScribe(ScribeWnd *App); ExportScribe(this); break; } case IDM_IMPORT_TEXT_MBOX: { Import_UnixMBox(this); break; } case IDM_IMP_EUDORA_ADDR: { Import_EudoraAddressBook(this); break; } case IDM_IMP_MOZILLA_ADDR: { Import_MozillaAddressBook(this); break; } case IDM_IMP_MOZILLA_MAIL: { Import_MozillaMail(this); break; } #if WINNATIVE case IDM_IMPORT_OUTLOOK_PAB: { Import_OutlookContacts(this); break; } case IDM_IMPORT_OUTLOOK_ITEMS: { Import_Outlook(this, IMP_OUTLOOK); break; } case IDM_EXPORT_OUTLOOK_ITEMS: { Export_Outlook(this); break; } #endif case IDM_IMP_MBX_EMAIL: { Import_OutlookExpress(this, false); // v4 break; } case IDM_IMP_DBX_EMAIL: { Import_OutlookExpress(this); // v5 break; } case IDM_IMPORT_NS_CONTACTS: { Import_NetscapeContacts(this); break; } case IDM_CHECK_UPDATE: { LVariant v; GetOptions()->GetValue(OPT_SoftwareUpdateIncBeta, v); SoftwareUpdate(this, true, v.CastInt32() != 0, [](auto goingToUpdate) { if (goingToUpdate) LCloseApp(); }); break; } case IDM_LOGOUT: { CurrentAuthLevel = PermRequireNone; auto i = Menu->FindItem(IDM_LOGOUT); if (i) { i->Enabled(false); } break; } case IDM_LAYOUT1: { LVariant v; int TwoThirds = GetClient().Y() >> 1; GetOptions()->SetValue(OPT_SplitterPos, v = 200); GetOptions()->SetValue(OPT_SubSplitPos, v = TwoThirds); SetLayout(FoldersListAndPreview); break; } case IDM_LAYOUT2: { LVariant v; int TwoThirds = GetClient().Y() >> 1; GetOptions()->SetValue(OPT_SplitterPos, v = TwoThirds); GetOptions()->SetValue(OPT_SubSplitPos, v = 200); SetLayout(PreviewOnBottom); break; } case IDM_LAYOUT3: { LVariant v; GetOptions()->SetValue(OPT_SplitterPos, v = 200); SetLayout(FoldersAndList); break; } case IDM_LAYOUT4: { LVariant v; GetOptions()->SetValue(OPT_SplitterPos, v = 200); GetOptions()->SetValue(OPT_SubSplitPos, v); SetLayout(ThreeColumn); break; } case IDM_CRASH: { int *Crash = 0; *Crash = true; break; } case IDM_DUMP_MEM: { LDumpMemoryStats(0); break; } case IDM_SCRIPT_DEBUG: { LVariant v; if (GetOptions()) GetOptions()->SetValue(OPT_ScriptDebugger, v = true); LVirtualMachine *vm = new LVirtualMachine(d); if (!vm) break; LVmDebugger *dbg = vm->OpenDebugger(); if (!dbg) break; dbg->OwnVm(true); break; } case IDM_SCRIPT_BREAK_ON_WARN: { auto mi = GetMenu()->FindItem(IDM_SCRIPT_BREAK_ON_WARN); if (!mi) break; LVirtualMachine::BreakOnWarning = !mi->Checked(); mi->Checked(LVirtualMachine::BreakOnWarning); break; } // Help menu case IDM_HELP: { LaunchHelp("index.html"); // LgiMsg(this, LLoadString(IDS_ERROR_NO_HELP), AppName, MB_OK); break; } case IDM_FEEDBACK: { LVariant e; if (GetOptions()->GetValue("author", e)) CreateMail(0, e.Str()); else CreateMail(0, AuthorEmailAddr); break; } case IDM_MEMECODE: { LExecute(AuthorHomepage); break; } case IDM_HOMEPAGE: { LVariant hp; if (GetOptions()->GetValue("homepage", hp)) LExecute(hp.Str()); else LExecute(ApplicationHomepage); break; } case IDM_VERSION_HISTORY: { LExecute("http://www.memecode.com/site/ver.php?id=445"); break; } case IDM_DEBUG_INFO: { char s[256]; sprintf_s(s, sizeof(s), "%s#debug", ApplicationHomepage); LExecute(s); break; } case IDM_TUTORIALS: { LExecute("http://www.memecode.com/scribe/tutorials"); break; } case IDM_INSCRIBE_LINK: { LExecute(CommercialHomepage); break; } case IDM_SCRIBE_FAQ: { LExecute(FaqHomepage); break; } case IDM_ABOUT: { extern void ScribeAbout(ScribeWnd *Parent); ScribeAbout(this); break; } default: { if (d->ScriptToolbar) d->ScriptToolbar->ExecuteCallbacks(this, 0, 0, Cmd); break; } } return 0; } void ScribeWnd::OnDelete() { LVariant ConfirmDelete; GetOptions()->GetValue(OPT_ConfirmDelete, ConfirmDelete); if (!ConfirmDelete.CastInt32() || LgiMsg(this, LLoadString(IDS_DELETE_ASK), AppName, MB_YESNO) == IDYES) { LArray Del; if (Tree && Tree->Focus()) { ScribeFolder *Item = dynamic_cast(Tree->Selection()); if (Item) { Tree->OnDelete(Item, false); } } else if (MailList #ifdef MAC && MailList->Focus() #endif ) { List Sel; MailList->GetSelection(Sel); for (auto i: Sel) { Thing *t = dynamic_cast(i); if (t) Del.Add(t->GetObject()); } if (Del.Length()) { auto Store = Del[0]->GetStore(); Store->Delete(Del, true); } else LgiTrace("%s:%i - Nothing to delete\n", _FL); #ifndef MAC MailList->Focus(true); #endif } } } int ScribeWnd::OnNotify(LViewI *Ctrl, LNotification n) { switch (Ctrl->GetId()) { case IDC_THING_LIST: { if (n.Type == LNotifyReturnKey) { LListItem *i = MailList ? MailList->GetSelected() : 0; Thing *t = dynamic_cast(i); if (t) { t->DoUI(); } } else if (n.Type == LNotifyDeleteKey) { /* This is now handled by the menu OnDelete(); return true; */ } if (SearchView && MailList) { SearchView->OnNotify(Ctrl, n); } break; } case IDC_TEXT: { if (PreviewPanel) { PreviewPanel->OnNotify(Ctrl, n); } break; } } return 0; } void ScribeWnd::AddThingSrc(ScribeFolder *src) { if (!d->ThingSources.HasItem(src)) d->ThingSources.Add(src); } void ScribeWnd::RemoveThingSrc(ScribeFolder *src) { d->ThingSources.Delete(src); } LArray ScribeWnd::GetThingSources(Store3ItemTypes Type) { LArray a; for (auto f: d->ThingSources) { if (f->GetItemType() == Type && !f->IsInTrash()) { a.Add(f); } } return a; } bool ScribeWnd::LogFilterActivity() { auto i = Menu->FindItem(IDM_DEBUG_FILTERS); return i ? i->Checked() : false; } bool ScribeWnd::CreateFolders(LAutoString &FileName) { bool Status = false; if (FileName) { char *Ext = LGetExtension(FileName); if (!Ext) { char File[300]; strcpy_s(File, sizeof(File), FileName); strcat(File, ".mail3"); FileName.Reset(NewStr(File)); } // Create objects, and then close the file.. it'll be reloaded later LAutoPtr m(CreateDataStore(FileName, true)); if (m) { m->GetRoot(true); Status = true; } else LgiTrace("%s:%i - CreateDataStore failed.\n", _FL); } else LgiTrace("%s:%i - No file name for CreateFolder.\n", _FL); return Status; } bool ScribeWnd::CompactFolders(LMailStore &Store, bool Interactive) { if (!Store.Store) return false; Store3Progress Dlg(this, Interactive); Dlg.SetDescription(LLoadString(IDS_CHECKING_OBJECTS)); bool Offline = false; if (WorkOffline) { Offline = WorkOffline->Checked(); WorkOffline->Checked(true); } - Store.Store->Compact(this, &Dlg); //Dlg.ItemAt(0), true - - if (WorkOffline) - { - WorkOffline->Checked(Offline); - } + Store.Store->Compact(this, &Dlg, [this, Offline](auto status) + { + if (WorkOffline) + WorkOffline->Checked(Offline); + }); return true; } CalendarSource *CalendarSource::Create(ScribeWnd *App, const char *ObjName, const char *Id) { if (!Stricmp(ObjName, "RemoteCalendarSource")) return new RemoteCalendarSource(App, Id); return new FolderCalendarSource(App, Id); } int ScribeWnd::GetCalendarSources(LArray &Out) { static bool Loaded = false; if (!Loaded) { Loaded = true; CalendarSource::SetCreateIn(NULL); LVariant Create; GetOptions()->GetValue(OPT_CalendarCreateIn, Create); // This should be a list of all calendar folders in ANY mail store... auto CalFlds = GetThingSources(MAGIC_CALENDAR); LXmlTag *t = GetOptions()->LockTag(OPT_CalendarSources, _FL); if (t) { bool AutoPopulate = t->Children.Length() == 0; for (auto c: t->Children) { auto s = CalendarSource::Create(this, c->GetAttr(CalendarSource::OptObject), c->GetTag()); if (s && s->Read()) { // Add known source... if (!Stricmp(Create.Str(), c->GetTag())) { CalendarSource::SetCreateIn(s); } // Remove from CalFlds FolderCalendarSource *Fcs = dynamic_cast(c); if (Fcs) { auto Path = Fcs->GetPath(); for (auto c: CalFlds) { if (c->GetPath().Equals(Path)) { CalFlds.Delete(c); break; } } } } } if (AutoPopulate) { // Now CalFlds should be a list of all calendar folders NOT in the source XML tag for (auto c: CalFlds) { FolderCalendarSource *s = new FolderCalendarSource(this); if (s) { // So add an entry to track it... auto Path = c->GetPath(); s->SetPath(Path); s->SetDisplay(true); s->SetColour(CalendarSource::FindUnusedColour()); s->Write(); } } } GetOptions()->Unlock(); } if (!CalendarSource::GetCreateIn() && CalendarSource::GetSources().Length()) { CalendarSource::SetCreateIn(CalendarSource::GetSources().ItemAt(0)); } } for (unsigned i=0; iPathOption; fi++) { bool Check = true; if (fi->HasOption) { LVariant v; if (GetOptions()->GetValue(fi->HasOption, v)) Check = v.CastInt32() != 0; } if (Check) { ScribeFolder *c = GetFolder(fi->Id); if (c == f) return fi->Id; } } return -1; } ScribeFolder *ScribeWnd::GetCurrentFolder() { if (Tree) { auto *Item = Tree->Selection(); if (Item) { return dynamic_cast(Item); } } return 0; } bool ScribeWnd::GetSystemPath(int Folder, LVariant &Path) { char KeyName[64]; sprintf_s(KeyName, sizeof(KeyName), "Folder-%i", Folder); return GetOptions()->GetValue(KeyName, Path); } LMailStore *ScribeWnd::GetMailStoreForIdentity(const char *IdEmail) { LVariant Tmp; if (!IdEmail) { // Get current identity ScribeAccount *Cur = GetCurrentAccount(); if (Cur) { Tmp = Cur->Identity.Email(); IdEmail = Tmp.Str(); } } if (!IdEmail) return NULL; ScribeAccount *a = NULL; for (auto Acc : Accounts) { LVariant e = Acc->Identity.Email(); if (e.Str() && !_stricmp(e.Str(), IdEmail)) { a = Acc; break; } } if (!a) return NULL; LVariant DestPath = a->Receive.DestinationFolder(); if (!DestPath.Str()) return NULL; return GetMailStoreForPath(DestPath.Str()); } ScribeFolder *ScribeWnd::GetFolder(int Id, LDataI *s) { if (s) { for (auto &f: Folders) if (s->GetStore() == f.Store) return GetFolder(Id, &f); } return GetFolder(Id); } ScribeFolder *ScribeWnd::GetFolder(int Id, LMailStore *Store, bool Quiet) { char KeyName[64]; sprintf_s(KeyName, sizeof(KeyName), "Folder-%i", Id); LVariant FolderName; bool NoOption = false; if (GetOptions()->GetValue(KeyName, FolderName)) { if (ValidStr(FolderName.Str()) && strlen(FolderName.Str()) > 0) { ScribeFolder *c = GetFolder(FolderName.Str(), Store); if (c) { return c; } else if (!Quiet) { LgiTrace("%s:%i - '%s' doesn't exist.\n", _FL, FolderName.Str()); } } } else if (!Quiet) { // LgiTrace("%s:%i - No option '%s'\n", _FL, KeyName); NoOption = true; } switch (Id) { case FOLDER_INBOX: case FOLDER_OUTBOX: case FOLDER_SENT: case FOLDER_TRASH: case FOLDER_CONTACTS: case FOLDER_TEMPLATES: case FOLDER_FILTERS: case FOLDER_CALENDAR: case FOLDER_GROUPS: case FOLDER_SPAM: { ScribeFolder *c = GetFolder(DefaultFolderNames[Id], Store); if (!c) { // if (!Quiet) // LgiTrace("%s:%i - Default folder '%s' doesn't exist.\n", _FL, DefaultFolderNames[Id]); } else if (NoOption) { auto p = c->GetPath(); GetOptions()->SetValue(KeyName, FolderName = p.Get()); } return c; } } return NULL; } bool ScribeWnd::OnMailStore(LMailStore **MailStore, bool Add) { if (!MailStore) { LAssert(!"No mail store pointer?"); return false; } if (Add) { *MailStore = &Folders.New(); if (*MailStore) return true; } else { ssize_t Idx = *MailStore - &Folders[0]; if (Idx >= 0 && Idx < (ssize_t)Folders.Length()) { Folders.DeleteAt(Idx, true); *MailStore = NULL; return true; } else { LAssert(!"Index out of range."); } } return false; } LMailStore *ScribeWnd::GetMailStoreForPath(const char *Path) { if (!Path) return NULL; LToken t(Path, "/"); if (t.Length() > 0) { const char *First = t[0]; // Find the mail store that that t[0] refers to for (unsigned i=0; iGetText(); if (RootStr && !_stricmp(RootStr, First)) { return &Folders[i]; } } } } return NULL; } ScribeFolder *ScribeWnd::GetFolder(const char *Name, LMailStore *s) { ScribeFolder *Folder = 0; if (ValidStr(Name)) { LString Sep("/"); auto t = LString(Name).Split(Sep); LMailStore tmp; LString TmpName; if (t.Length() > 0) { if (!s) { s = GetMailStoreForPath(Name); if (!s) { // IMAP folders? for (auto a: Accounts) { ScribeProtocol Proto = a->Receive.ProtocolType(); if (Proto == ProtocolImapFull) { ScribeFolder *Root = a->Receive.GetRootFolder(); if (Root) { const char *RootStr = Root->GetText(); if (RootStr && a->Receive.GetDataStore() && !_stricmp(RootStr, t[0])) { tmp.Root = Root; tmp.Store = a->Receive.GetDataStore(); s = &tmp; break; } } } } } if (s) { if (*Name == '/') Name++; Name = strchr(Name, '/'); if (!Name) Name = "/"; } } else if (s->Root) { // Check if the store name is on the start of the folder auto RootName = s->Root->GetName(true); if (RootName.Equals(t[0])) { LString::Array a; for (unsigned i=1; iRoot; Folder = s->Root ? s->Root->GetSubFolder(Name) : 0; } } return Folder; } void ScribeWnd::Update(int What) { if (What & UPDATE_TREE) { Tree->Invalidate(); return; } if (What & UPDATE_LIST) { if (MailList) MailList->Invalidate(); return; } } void ScribeWnd::DoDebug(char *s) { } Thing *ScribeWnd::CreateThingOfType(Store3ItemTypes Type, LDataI *obj) { Thing *t = NULL; switch (Type) { case MAGIC_CONTACT: { t = new Contact(this, obj); break; } case MAGIC_MAIL: { t = new Mail(this, obj); break; } case MAGIC_ATTACHMENT: { t = new Attachment(this, obj); break; } case MAGIC_FILTER: { t = new Filter(this, obj); break; } case MAGIC_CALENDAR: { t = new Calendar(this, obj); break; } case MAGIC_GROUP: { t = new ContactGroup(this, obj); break; } default: break; } if (t) { t->App = this; } return t; } void ScribeWnd::GetFilters(List &Filters, bool JustIn, bool JustOut, bool JustInternal) { auto Srcs = GetThingSources(MAGIC_FILTER); for (auto f: Srcs) { for (auto t: f->Items) { Filter *Ftr = t->IsFilter(); if (Ftr) { if (JustIn && !Ftr->GetIncoming()) continue; if (JustOut && !Ftr->GetOutgoing()) continue; if (JustInternal && !Ftr->GetInternal()) continue; Filters.Insert(Ftr); } } } extern int FilterCompare(Filter *a, Filter *b, NativeInt Data); Filters.Sort(FilterCompare); } bool ScribeWnd::ShowToolbarText() { LVariant i; if (GetOptions()->GetValue(OPT_ToolbarText, i)) { return i.CastInt32() != 0; } GetOptions()->SetValue(OPT_ToolbarText, i = true); return true; } void ScribeWnd::HashContacts(LHashTbl,Contact*> &Contacts, ScribeFolder *Folder, bool Deep) { if (!Folder) { // Default item is the contacts folder Folder = GetFolder(FOLDER_CONTACTS); } // recurse through each folder and make a list // of every contact object we find. if (Folder) { Folder->LoadThings(); for (auto t: Folder->Items) { Contact *c = t->IsContact(); if (c) { auto Emails = c->GetEmails(); for (auto e: Emails) if (e && !Contacts.Find(e)) Contacts.Add(e, c); } } for (ScribeFolder *f = Folder->GetChildFolder(); Deep && f; f = f->GetNextFolder()) { HashContacts(Contacts, f, Deep); } } } List *ScribeWnd::GetEveryone() { return &Contact::Everyone; } bool ScribeWnd::GetContacts(List &Contacts, ScribeFolder *Folder, bool Deep) { LArray Folders; if (!Folder) { Folders = GetThingSources(MAGIC_CONTACT); auto f = GetFolder(FOLDER_CONTACTS); if (f && !Folders.HasItem(f)) Folders.Add(f); } else Folders.Add(Folder); if (!Folders.Length()) return false; for (auto f: Folders) { // recurse through each folder and make a list // of every contact object we find. ScribePerm Perm = f->GetFolderPerms(ScribeReadAccess); bool Safe = CurrentAuthLevel >= Perm; if (Safe) { f->LoadThings(); for (auto t: f->Items) { Contact *c = t->IsContact(); if (c) Contacts.Insert(c); } for (ScribeFolder *c = f->GetChildFolder(); Deep && c; c = c->GetNextFolder()) GetContacts(Contacts, c, Deep); } } return true; } /* This function goes through the database and checks for some basic requirements and fixes things up if they aren't ok. */ bool ScribeWnd::ValidateFolder(LMailStore *s, int Id) { char OptName[32]; sprintf_s(OptName, sizeof(OptName), "Folder-%i", Id); LVariant Path; if (!GetOptions()->GetValue(OptName, Path)) { char Opt[256]; sprintf_s(Opt, sizeof(Opt), "/%s", DefaultFolderNames[Id]); GetOptions()->SetValue(OptName, Path = Opt); } // If the path name has the store name at the start, strip that off... LString Sep("/"); LString::Array Parts = LString(Path.Str()).Split(Sep); if (Parts.Length() > 1) { if (Parts[0].Equals(s->Name)) { Parts.DeleteAt(0, true); Path = Sep.Join(Parts); } else { LMailStore *ms = GetMailStoreForPath(Path.Str()); if (ms) { s = ms; } else { // Most likely the user has renamed something and broken the // path. Lets just error out instead of creating the wrong folder return false; } } } // Now resolve the path... ScribeFolder *Folder = GetFolder(Path.Str(), s); if (!Folder) { char *p = Path.Str(); if (_strnicmp(p, "/IMAP ", 6) != 0) { LAssert(DefaultFolderTypes[Id] != MAGIC_NONE); Folder = s->Root->CreateSubDirectory(*p=='/'?p+1:p, DefaultFolderTypes[Id]); } } if (!Folder) return false; Folder->SetDefaultFields(); return true; } void ScribeWnd::Validate(LMailStore *s) { // Check for all the basic folders int Errors = 0; for (SystemFolderInfo *fi = SystemFolders; fi->PathOption; fi++) { bool Check = true; if (fi->HasOption) { LVariant v; if (GetOptions()->GetValue(fi->HasOption, v)) Check = v.CastInt32() != 0; } if (Check) { if (!ValidateFolder(s, fi->Id)) Errors++; } } if (Errors && LgiMsg(this, "There were errors validating the system folders." "Would you like to review the mail store's system folder paths?", AppName, MB_YESNO) == IDYES) { PostEvent(M_COMMAND, IDM_MANAGE_MAIL_STORES); } } ThingFilter *ScribeWnd::GetThingFilter() { return SearchView; } ScribeAccount *ScribeWnd::GetSendAccount() { LVariant DefSendAcc = 0; if (!GetOptions()->GetValue(OPT_DefaultSendAccount, DefSendAcc)) { for (auto a : Accounts) if (a->Send.Server().Str()) return a; } ScribeAccount *i = Accounts.ItemAt(DefSendAcc.CastInt32()); if (i && i->Send.Server().Str()) return i; return NULL; } LPrinter *ScribeWnd::GetPrinter() { if (!d->PrintOptions) d->PrintOptions.Reset(new LPrinter); return d->PrintOptions; } int ScribeWnd::GetActiveThreads() { int Status = 0; for (auto i: Accounts) { if (i->IsOnline()) { Status++; } } return Status; } class DefaultClientDlg : public LDialog { public: bool DontWarn; DefaultClientDlg(LView *parent) { DontWarn = false; SetParent(parent); LoadFromResource(IDD_WARN_DEFAULT); MoveToCenter(); } int OnNotify(LViewI *Ctrl, LNotification n) { switch (Ctrl->GetId()) { case ID_YES: case ID_NO: { LCheckBox *DW; if (GetViewById(IDC_DONT_WARN, DW)) { DontWarn = DW->Value() != 0; } EndModal(Ctrl->GetId() == ID_YES); break; } } return 0; } }; #if WINNATIVE struct DefaultClient { char DefIcon[MAX_PATH_LEN]; char CmdLine[MAX_PATH_LEN]; char DllPath[MAX_PATH_LEN]; DefaultClient() { auto Exe = LGetExeFile(); sprintf_s(DefIcon, sizeof(DefIcon), "%s,1", Exe.Get()); sprintf_s(CmdLine, sizeof(CmdLine), "\"%s\" /m \"%%1\"", Exe.Get()); LMakePath(DllPath, sizeof(DllPath), Exe, "../ScribeMapi.dll"); } bool IsWindowsXp() { LArray Ver; int Os = LGetOs(&Ver); if ( ( Os == LGI_OS_WIN32 || Os == LGI_OS_WIN64 ) && Ver.Length() > 1 && Ver[0] == 5 && Ver[1] == 1 ) return true; return false; } bool InstallMailto(bool Write) { LAutoPtr mailto = CheckKey(Write, "HKCR\\mailto"); if (!mailto) return false; if (!CheckString(Write, mailto, NULL, "URL:MailTo Protocol")) return false; LAutoPtr deficon = CheckKey(Write, "HKCR\\mailto\\DefaultIcon"); if (!deficon) return false; if (!CheckString(Write, deficon, NULL, DefIcon)) return false; LAutoPtr shell = CheckKey(Write, "HKCR\\mailto\\shell"); if (!shell) return false; if (!CheckString(Write, shell, NULL, "open")) return false; LAutoPtr cmd = CheckKey(Write, "HKCR\\mailto\\shell\\open\\command"); if (!cmd) return false; if (!CheckString(Write, cmd, NULL, CmdLine)) return false; return true; } LAutoPtr CheckKey(bool Write, const char *Key, ...) { char Buffer[512]; va_list Arg; va_start(Arg, Key); vsprintf_s(Buffer, sizeof(Buffer), Key, Arg); va_end(Arg); LAutoPtr k(new LRegKey(Write, Buffer)); if (k && Write && !k->IsOk()) { if (!k->Create()) { k.Reset(); LgiTrace("%s:%i - Failed to create '%s'\n", _FL, Buffer); } } return k; } bool CheckInt(bool Write, LRegKey *k, const char *Name, uint32_t Value) { if (!k) { LgiTrace("%s:%i - No key: '%s'\n", _FL, Name); return false; } uint32_t Cur; if (!k->GetInt(Name, Cur)) Cur = Value + 1; if (Cur == Value) return true; if (Write) { bool Status = k->SetInt(Name, Value); if (!Status) LgiTrace("%s:%i - Failed to set key '%s': '%s' to %i\n", _FL, k->Name(), Name, Value); return Status; } return false; } bool CheckString(bool Write, LRegKey *k, const char *StrName, const char *StrValue) { if (!k) { LgiTrace("%s:%i - No key: '%s' to '%s'\n", _FL, StrName, StrValue); return false; } LString v; if (k->GetStr(StrName, v)) { bool Same = Stricmp(v.Get(), StrValue) == 0; if (Write && !Same) { bool Status = k->SetStr(StrName, StrValue); if (!Status) LgiTrace("%s:%i - Failed to set key '%s': '%s' to '%s'\n", _FL, k->Name(), StrName, StrValue); return Status; } return Same; } else if (Write) { bool Status = k->SetStr(StrName, StrValue); if (!Status) LgiTrace("%s:%i - Failed to set key '%s': '%s' to '%s'\n", _FL, k->Name(), StrName, StrValue); return Status; } return false; } bool IsDefault() { LAutoPtr mail = CheckKey(false, "HKCU\\Software\\Clients\\Mail"); if (!mail) return false; LString v; if (!mail->GetStr(NULL, v)) return false; return !_stricmp(v, "Scribe"); } bool SetDefault() { LAutoPtr mail = CheckKey(true, "HKCU\\Software\\Clients\\Mail"); if (!mail) return false; // Set the default client in the current user tree. mail->SetStr(NULL, "Scribe"); // Configure the mailto handler const char *Base = "HKEY_ROOT"; bool Error = false; LRegKey Mt(true, "%s\\mailto", Base); if (Mt.IsOk() || Mt.Create()) { if (!Mt.SetStr(0, "URL:MailTo Protocol") || !Mt.SetStr("URL Protocol", "")) Error = true; } else { LgiTrace("%s:%i - Couldn't open/create registry key (err=%i).\n", _FL, GetLastError()); Error = true; } LRegKey Di(true, "%s\\mailto\\DefaultIcon", Base); if (Di.IsOk() || Di.Create()) { if (!Di.SetStr(0, DefIcon)) Error = true; } else { LgiTrace("%s:%i - Couldn't open/create registry key (err=%i).\n", _FL, GetLastError()); Error = true; } LRegKey c(true, "%s\\mailto\\shell\\open\\command", Base); if (c.IsOk() || c.Create()) { if (!c.SetStr(NULL, CmdLine)) Error = true; } else { LgiTrace("%s:%i - Couldn't open/create registry key (err=%i).\n", _FL, GetLastError()); Error = true; } return Error; } bool InstallAsClient(char *Base, bool Write) { // Create software client entry, to put Scribe in the Internet Options for mail clients. LAutoPtr mail = CheckKey(Write, "%s\\Software\\Clients\\Mail", Base); if (!mail) return false; LAutoPtr app = CheckKey(Write, "%s\\Software\\Clients\\Mail\\Scribe", Base); if (!app) return false; if (!CheckString(Write, app, NULL, AppName)) return false; if (!CheckString(Write, app, "DllPath", DllPath)) return false; LAutoPtr shell = CheckKey(Write, "%s\\Software\\Clients\\Mail\\Scribe\\shell\\open\\command", Base); if (!shell) return false; if (!CheckString(Write, shell, NULL, CmdLine)) return false; LAutoPtr icon = CheckKey(Write, "%s\\Software\\Clients\\Mail\\Scribe\\DefaultIcon", Base); if (!icon) return false; if (!CheckString(Write, icon, NULL, DefIcon)) return false; LAutoPtr proto = CheckKey(Write, "%s\\Software\\Classes\\Protocol\\mailto", Base); if (!proto) return false; if (!CheckString(Write, proto, NULL, "URL:MailTo Protocol")) return false; if (!CheckString(Write, proto, "URL Protocol", "")) return false; if (!CheckInt(Write, proto, "EditFlags", 0x2)) return false; LAutoPtr proto_cmd = CheckKey(Write, "%s\\Software\\Classes\\Protocol\\mailto\\shell\\open\\command", Base); if (!proto_cmd) return false; if (!CheckString(Write, proto_cmd, NULL, CmdLine)) return false; return true; } struct FileType { char *Name; char *Desc; int Icon; }; static FileType FileTypes[]; bool Win7Install(bool Write) { // http://msdn.microsoft.com/en-us/library/windows/desktop/cc144154%28v=vs.85%29.aspx LArray Ver; int Os = LGetOs(&Ver); if ( ( Os == LGI_OS_WIN32 || Os == LGI_OS_WIN64 ) && Ver[0] >= 6) { char Path[MAX_PATH_LEN]; auto Exe = LGetExeFile(); for (int i=0; FileTypes[i].Name; i++) { LAutoPtr base = CheckKey(Write, "HKEY_CLASSES_ROOT\\%s", FileTypes[i].Name); if (!base) return false; if (!CheckString(Write, base, NULL, FileTypes[i].Desc)) return false; LAutoPtr r = CheckKey(Write, "HKEY_CLASSES_ROOT\\%s\\shell\\Open\\command", FileTypes[i].Name); if (!r) return false; sprintf_s(Path, sizeof(Path), "\"%s\" -u \"%%1\"", Exe.Get()); if (!CheckString(Write, r, NULL, Path)) return false; LAutoPtr ico = CheckKey(Write, "HKEY_CLASSES_ROOT\\%s\\DefaultIcon", FileTypes[i].Name); if (!ico) return false; sprintf_s(Path, sizeof(Path), "%s,%i", Exe.Get(), FileTypes[i].Icon); if (!CheckString(Write, ico, NULL, Path)) return false; } LAutoPtr r = CheckKey(Write, "HKEY_LOCAL_MACHINE\\SOFTWARE\\Clients\\Mail\\Scribe\\Capabilities"); if (!r) return false; if (!CheckString(Write, r, "ApplicationDescription", "Scribe is a small lightweight email client.") && !CheckString(Write, r, "ApplicationName", "Scribe") && !CheckString(Write, r, "ApplicationIcon", DefIcon)) return false; LAutoPtr as = CheckKey(Write, "HKEY_LOCAL_MACHINE\\SOFTWARE\\Clients\\Mail\\Scribe\\Capabilities\\FileAssociations"); if (!as) return false; if (!CheckString(Write, as, ".eml", "Scribe.Email") && !CheckString(Write, as, ".msg", "Scribe.Email") && !CheckString(Write, as, ".mbox", "Scribe.Folder") && !CheckString(Write, as, ".mbx", "Scribe.Folder") && !CheckString(Write, as, ".ics", "Scribe.Calendar") && !CheckString(Write, as, ".vcs", "Scribe.Calendar") && !CheckString(Write, as, ".vcf", "Scribe.Contact") && !CheckString(Write, as, ".mail3", "Scribe.MailStore")) return false; LAutoPtr ua = CheckKey(Write, "HKEY_LOCAL_MACHINE\\SOFTWARE\\Clients\\Mail\\Scribe\\Capabilities\\UrlAssociations"); if (!ua) return false; if (!CheckString(Write, ua, "mailto", "Scribe.Mailto")) return false; LAutoPtr a = CheckKey(Write, "HKEY_LOCAL_MACHINE\\SOFTWARE\\RegisteredApplications"); if (!a) return false; if (!CheckString(Write, a, "Scribe", "SOFTWARE\\Clients\\Mail\\Scribe\\Capabilities")) return false; } return true; } void Win7Uninstall() { for (int i=0; FileTypes[i].Name; i++) { LRegKey base(true, "HKEY_CLASSES_ROOT\\%s", FileTypes[i].Name); base.DeleteKey(); } } }; DefaultClient::FileType DefaultClient::FileTypes[] = { { "Scribe.Email", "Email", 2 }, { "Scribe.Folder", "Mailbox", 0 }, { "Scribe.Calendar", "Calendar Event", 6 }, { "Scribe.Contact", "Contact", 4 }, { "Scribe.MailStore", "Mail Store", 0 }, { "Scribe.Mailto", "Mailto Protocol", 0 }, { 0, 0 } }; #endif void ScribeWnd::SetDefaultHandler() { #if WINNATIVE if (LAppInst->GetOption("noreg")) return; LVariant RegisterClient; if (!GetOptions()->GetValue(OPT_RegisterWindowsClient, RegisterClient)) RegisterClient = true; if (!RegisterClient.CastInt32()) return; // Create IE mail client entries for local machine and current user DefaultClient Def; bool OldAssert = LRegKey::AssertOnError; LRegKey::AssertOnError = false; bool RegistryOk = ( !Def.IsWindowsXp() || Def.InstallMailto(true) ) && Def.InstallAsClient("HKLM", true) && Def.Win7Install(true); LRegKey::AssertOnError = OldAssert; if (!RegistryOk) { // Need write permissions to fix up the registry? NeedsCapability("RegistryWritePermissions"); return; } // Check if the user wants us to be the default client LVariant n = true; GetOptions()->GetValue(OPT_CheckDefaultEmail, n); if (n.CastInt32()) { // HKEY_CURRENT_USER\Software\Microsoft\Windows\Shell\Associations\UrlAssociations\mailto\UserChoice LRegKey::AssertOnError = false; bool Error = false; bool IsDef = Def.IsDefault(); if (!IsDef) { // Ask the user... DefaultClientDlg Dlg(this); if (Dlg.DoModal()) { Error = !Def.SetDefault(); GetOptions()->SetValue(OPT_CheckDefaultEmail, n = (int) (!Dlg.DontWarn)); } } LRegKey::AssertOnError = OldAssert; if (Error) { NeedsCapability("RegistryWritePermissions"); } } #endif } void ScribeWnd::OnSelect(List *l, bool ChangeEvent) { Mail *m = (l && l->Length() == 1) ? (*l)[0]->IsMail() : 0; if (Commands) { bool NotCreated = m && !TestFlag(m->GetFlags(), MAIL_CREATED); Commands->SetCtrlEnabled(IDM_DELETE, l && l->Length() > 0); Commands->SetCtrlEnabled(IDM_DELETE_AS_SPAM, l && l->Length() > 0); Commands->SetCtrlEnabled(IDM_PRINT, l && l->Length() == 1); Commands->SetCtrlEnabled(IDM_REPLY, NotCreated); Commands->SetCtrlEnabled(IDM_REPLY_ALL, NotCreated); Commands->SetCtrlEnabled(IDM_FORWARD, m != 0); Commands->SetCtrlEnabled(IDM_BOUNCE, m != 0); } if (PreviewPanel && GetEffectiveLayoutMode() != 3) { if (!PreviewPanel->IsAttached()) { SetItemPreview(PreviewPanel); } Thing *t = (l && l->Length() == 1) ? (*l)[0] : 0; PreviewPanel->OnThing(t, ChangeEvent); /* if (d->Debug) d->Debug->OnThing(t); */ } } class SpellErrorInst { public: int Id; LString Word; LString::Array Suggestions; SpellErrorInst(int id) { Id = id; // Decor = LCss::TextDecorSquiggle; // DecorColour.Rgb(255, 0, 0); } ~SpellErrorInst() { } bool OnMenu(LSubMenu *m) { if (Suggestions.Length()) { for (unsigned i=0; iAppendItem(Suggestions[i], 100 + i, true); } m->AppendSeparator(); } char Buf[256]; sprintf_s(Buf, sizeof(Buf), LLoadString(IDS_ADD_TO_DICTIONARY, "Add '%s' to dictionary"), Word.Get()); m->AppendItem(Buf, 1, true); return true; } void OnMenuClick(int i) { if (i == 1) { // Add to dictionary... /* if (PostThreadEvent(SpellHnd, M_ADD_WORD, (LMessage::Param) new LString(Word))) { // FIXME LAssert(!"Impl me."); // View->PostEvent(M_DELETE_STYLE, (LMessage::Param) dynamic_cast(this)); } */ } else if (i >= 100 && i < 100 + (int)Suggestions.Length()) { // Change spelling.. char *Replace = Suggestions[i - 100]; if (Replace) { char16 *w = Utf8ToWide(Replace); if (w) { /* int NewLen = StrlenW(w); if (NewLen > Len) { // Bigger... memcpy(View->NameW() + Start, w, Len * sizeof(char16)); View->Insert(Start + Len, w + Len, NewLen - Len); } else if (NewLen < Len) { // Smaller... memcpy(View->NameW() + Start, w, NewLen * sizeof(char16)); View->Delete(Start + NewLen, Len - NewLen); } else { // Just copy... memcpy(View->NameW() + Start, w, Len * sizeof(char16)); RefreshLayout(Start, Len); } */ DeleteArray(w); } } } } }; class MailTextView : public LTextView3 { ScribeWnd *App; LSpellCheck *Thread; LColour c[8]; LHashTbl, SpellErrorInst*> ErrMap; SpellErrorInst *NewErrorInst() { int Id; while (ErrMap.Find(Id = LRand(10000))) ; SpellErrorInst *Inst = new SpellErrorInst(Id); if (!Inst) return NULL; ErrMap.Add(Id, Inst); return Inst; } public: MailTextView(ScribeWnd *app, int Id, int x, int y, int cx, int cy, LFontType *FontType) : LTextView3(Id, x, y, cx, cy, FontType) { App = app; Thread = 0; int i=0; c[i++].Rgb(0x80, 0, 0); c[i++].Rgb(0, 0x80, 0); c[i++].Rgb(0, 0, 0x80); c[i++].Rgb(0x80, 0x80, 0); c[i++].Rgb(0x80, 0, 0x80); c[i++].Rgb(0, 0x80, 0x80); c[i++].Rgb(0x80, 0x80, 0x80); c[i++].Rgb(0xc0, 0xc0, 0xc0); for (i=0; i 0 && !StrchrW(SpellDelim, Text[Start-1])) Start--; if (Len > 0) { // Text being added Len += Origin - Start; while ((ssize_t)Start + Len < Size && !StrchrW(SpellDelim, Text[Start + Len])) Len++; } else if (Len < 0) { // Text being deleted Len = Origin - Start; while ((ssize_t)Start + Len < Size && !StrchrW(SpellDelim, Text[Start + Len])) Len++; } if (!Thread) Thread = App->GetSpellThread(); if (Thread && Len > 0) { LString Str(Text+Start, Len); LArray Params; Thread->Check(AddDispatch(), Str, Start, Len, &Params); } // Adjust all the positions of the styles after this. for (auto s = Style.begin(); s != Style.end(); ) { if (s->Start >= Origin && s->Owner == 1) { if (Length < 0 && s->Start < Origin - Length) { // In the deleted text... Style.Delete(s); continue; } // After the deleted text s->Start += Length; LAssert(s->Start >= 0); } s++; } } } void PourText(size_t Start, ssize_t Len) { LTextView3::PourText(Start, Len); for (auto l: Line) { int n=0; char16 *t = Text + l->Start; char16 *e = t + l->Len; while ((*t == ' ' || *t == '>') && t < e) if (*t++ == '>') n++; if (n > 0) l->c = c[(n-1)%CountOf(c)]; } } LMessage::Result OnEvent(LMessage *m) { switch (m->Msg()) { case M_CHECK_TEXT: { LAutoPtr Ct((LSpellCheck::CheckText*)m->A()); if (!Ct || !Thread) break; // Clear existing spelling error styles ssize_t Start = Ct->Start; ssize_t End = Start + Ct->Len; for (auto i = Style.begin(); i != Style.end(); ) { if (i->End() < (size_t)Start || i->Start >= End) { // Outside the area we are re-styling. i++; } else { if (i->Owner == STYLE_SPELLING) { // Existing error style inside the area Style.Delete(i); } else { // Existing non-error style... i++; } } } // Insert the new styles for (auto Ct: Ct->Errors) { SpellErrorInst *ErrInst = NewErrorInst(); LAutoPtr Style(new LTextView3::LStyle(STYLE_SPELLING)); if (Style && ErrInst) { Style->View = this; Style->Start = Ct.Start; Style->Len = Ct.Len; Style->Font = GetFont(); Style->Data = ErrInst->Id; Style->DecorColour = LColour::Red; Style->Decor = LCss::TextDecorSquiggle; ErrInst->Word = LString(Text + Style->Start, Style->End()); ErrInst->Suggestions = Ct.Suggestions; InsertStyle(Style); } } // Update the screen... Invalidate(); break; } case M_DELETE_STYLE: { /* LTextView3::LStyle *s = (LTextView3::LStyle*)m->A(); if (s && Style.HasItem(s)) { Style.Delete(s); Invalidate(); } else LAssert(0); */ break; } } return LTextView3::OnEvent(m); } bool OnStyleClick(LStyle *style, LMouse *m) { switch (style->Owner) { case STYLE_URL: { if (m->Left() && m->Down() && m->Double()) { LString s(Text + style->Start, style->Len); LUri u(s); if ( (u.sProtocol && !_stricmp(u.sProtocol, "mailto")) || LIsValidEmail(s) ) { Mailto m(App, s); Mail *email = App->CreateMail(); if (email) { m.Apply(email); email->DoUI(); return true; } } else { // Web link? LExecute(s); } } break; } default: return false; } return true; } }; LDocView *ScribeWnd::CreateTextControl(int Id, const char *MimeType, bool Editor, Mail *m) { LDocView *Ctrl = 0; // Get the default font LFontType FontType; bool UseFont = FontType.Serialize(GetOptions(), OPT_EditorFont, false); if (Editor) { if (MimeType && !_stricmp(MimeType, sTextHtml)) { // Use the built in html editor LRichTextEdit *Rte; if ((Ctrl = Rte = new LRichTextEdit(Id))) { if (UseFont) Ctrl->SetFont(FontType.Create(), true); // Give the control the speller settings: LVariant Check, Lang, Dict; if (GetOptions()->GetValue(OPT_SpellCheck, Check) && Check.CastInt32() != 0) { if (GetOptions()->GetValue(OPT_SpellCheckLanguage, Lang)) Rte->SetValue(LDomPropToString(SpellCheckLanguage), Lang); if (GetOptions()->GetValue(OPT_SpellCheckDictionary, Dict)) Rte->SetValue(LDomPropToString(SpellCheckDictionary), Dict); // Set the spell thread: LSpellCheck *t = GetSpellThread(); if (t) Rte->SetSpellCheck(t); } } } else { // Use the built in plain text editor Ctrl = new MailTextView(this, Id, 0, 0, 200, 200, (UseFont) ? &FontType : 0); } } else { // Create a view only control for the mime type: LDocView *HtmlCtrl = NULL; if (!MimeType || _stricmp(MimeType, sTextPlain) == 0) Ctrl = new MailTextView(this, Id, 0, 0, 200, 200, (UseFont) ? &FontType : 0); #if 0 // defined(WINDOWS) && !defined(__GTK_H__) else if (_stricmp(MimeType, sApplicationInternetExplorer) == 0) HtmlCtrl = Ctrl = CreateIeControl(Id); #endif else HtmlCtrl = Ctrl = new Html1::LHtml(Id, 0, 0, 200, 200); if (HtmlCtrl && UseFont) { LVariant LoadImg; if (GetOptions()->GetValue(OPT_HtmlLoadImages, LoadImg)) HtmlCtrl->SetLoadImages(LoadImg.CastInt32() != 0); HtmlCtrl->SetFont(FontType.Create(), true); } } if (Ctrl) { Ctrl->SetUrlDetect(true); Ctrl->SetAutoIndent(true); LVariant WrapOption; if (GetOptions()->GetValue(OPT_WordWrap, WrapOption)) { if (WrapOption.CastInt32()) { LVariant WrapCols = 80; GetOptions()->GetValue(OPT_WrapAtColumn, WrapCols); Ctrl->SetWrapAtCol(WrapCols.CastInt32()); } else { Ctrl->SetWrapAtCol(0); } } } return Ctrl; } void ScribeWnd::GrowlInfo(LString title, LString text) { LGrowl *g = d->GetGrowl(); if (!g) return; LAutoPtr n(new LGrowl::LNotify); n->Name = "info"; n->Title = title; n->Text = text; g->Notify(n); } void ScribeWnd::GrowlOnMail(Mail *m) { LVariant v; LAutoPtr n(new LGrowl::LNotify); n->Name = "new-mail"; n->Title = m->GetSubject(); int Len = 64; char sLen[16]; sprintf_s(sLen, sizeof(sLen), "%i", Len); if (m->GetVariant("BodyAsText", v, sLen)) { char *s = v.Str(); if (s) { int Words = 0; bool Lut[256]; memset(Lut, 0, sizeof(Lut)); Lut[(int)' '] = Lut[(int)'\t'] = Lut[(int)'\r'] = Lut[(int)'\n'] = true; char *c; for (c = s; *c && Words < 30; ) { while (*c && Lut[(int)*c]) c++; while (*c && !Lut[(int)*c]) c++; Words++; } n->Text.Set(s, c - s); } } LGrowl *g = d->GetGrowl(); if (g) { g->Notify(n); m->NewEmail = Mail::NewEmailTray; } } void ScribeWnd::OnNewMailSound() { static uint64 PrevTs = 0; auto Now = LCurrentTime(); if (Now - PrevTs > 30000) { PrevTs = Now; LVariant v; if (GetOptions()->GetValue(OPT_NewMailSoundFile, v) && LFileExists(v.Str())) { LPlaySound(v.Str(), SND_ASYNC); } } } void ScribeWnd::OnFolderSelect(ScribeFolder *f) { if (SearchView) SearchView->OnFolder(); } void ScribeWnd::OnNewMail(List *MailObjs, bool Add) { if (!MailObjs) return; LVariant v; bool ShowDetail = MailObjs->Length() < 5; List NeedsFiltering; LArray NeedsBayes; LArray NeedsGrowl; LArray Resort; for (auto m: *MailObjs) { if (Add) { #if DEBUG_NEW_MAIL LgiTrace("%s:%i - NewMail.OnNewMail t=%p, uid=%s, mode=%s\n", _FL, (Thing*)m, m->GetServerUid().ToString().Get(), toString(m->NewEmail)); #endif switch (m->NewEmail) { case Mail::NewEmailNone: { auto Loaded = m->GetLoaded(); #if DEBUG_NEW_MAIL LgiTrace("%s:%i - NewMail.OnNewMail.GetLoaded=%i uid=%s\n", _FL, (int)Loaded, m->GetServerUid().ToString().Get()); #endif if (Loaded != Store3Loaded) { LOG_STORE("\tOnNewMail calling SetLoaded.\n"); m->SetLoaded(); m->NewEmail = Mail::NewEmailLoading; } else { m->NewEmail = Mail::NewEmailFilter; LOG_STORE("\tOnNewMail none->NeedsFiltering.\n"); NeedsFiltering.Insert(m); } break; } case Mail::NewEmailLoading: { auto Loaded = m->GetLoaded(); if (Loaded == Store3Loaded) { m->NewEmail = Mail::NewEmailFilter; NeedsFiltering.Insert(m); if (m->GetFolder() && !Resort.HasItem(m->GetFolder())) { Resort.Add(m->GetFolder()); } } break; } case Mail::NewEmailFilter: { NeedsFiltering.Insert(m); break; } case Mail::NewEmailBayes: { NeedsBayes.Add(m); break; } case Mail::NewEmailGrowl: { if (d->Growl) { NeedsGrowl.Add(m); break; } else { m->NewEmail = Mail::NewEmailTray; // no Growl loaded so fall through to new tray mail } } case Mail::NewEmailTray: { LAssert(m->GetObject()); Mail::NewMailLst.Insert(m); OnNewMailSound(); break; } default: { LAssert(!"Hmmm what happen?"); break; } } } else { #if DEBUG_NEW_MAIL LgiTrace("%s:%i - NewMail.OnNewMail.RemoveNewMail t=%p, uid=%s\n", _FL, (Thing*)m, m->GetServerUid().ToString().Get()); #endif Mail::NewMailLst.Delete(m); if (m->NewEmail == Mail::NewEmailFilter) m->NewEmail = Mail::NewEmailNone; } } if (Add) { // Do filtering if (NeedsFiltering.Length()) { List Filters; if (!GetOptions()->GetValue(OPT_DisableUserFilters, v) || !v.CastInt32()) { GetFilters(Filters, true, false, false); } if (Filters.Length() > 0) { // Run the filters #if DEBUG_NEW_MAIL LgiTrace("%s:%i - NewMail.OnNewMail.Filtering %i mail through %i filters\n", _FL, (int)NeedsFiltering.Length(), (int)Filters.Length()); #endif Filter::ApplyFilters(NULL, Filters, NeedsFiltering); // All the email not filtered now needs to be sent to the bayes filter. for (auto m: NeedsFiltering) { if (m->NewEmail == Mail::NewEmailBayes) { #if DEBUG_NEW_MAIL LgiTrace("%s:%i - NewMail.OnNewMail.NeedsBayes t=%p, msgid=%s\n", _FL, (Thing*)m, m->GetMessageId()); #endif NeedsBayes.Add(m); } else if (m->NewEmail == Mail::NewEmailGrowl) { #if DEBUG_NEW_MAIL LgiTrace("%s:%i - NewMail.OnNewMail.NeedsGrowl t=%p, msgid=%s\n", _FL, (Thing*)m, m->GetMessageId()); #endif NeedsGrowl.Add(m); } } } } // Do bayes if (NeedsBayes.Length()) { ScribeBayesianFilterMode FilterMode = BayesOff; if (GetOptions()->GetValue(OPT_BayesFilterMode, v)) FilterMode = (ScribeBayesianFilterMode)v.CastInt32(); for (unsigned i=0; iMailMessageIdMap(); // Start the Bayesian rating process off Store3Status Status = IsSpam(Rating, m); if (Status == Store3Success) { // Bayes done... this stops OnBayesResult from passing it back to OnNewMail m->NewEmail = Mail::NewEmailGrowl; // Process bayes result if (!OnBayesResult(m, Rating)) { // Not spam... so on to growl #if DEBUG_NEW_MAIL LgiTrace("%s:%i - NewMail.Bayes.NeedsGrowl t=%p, msgid=%s\n", _FL, (Thing*)m, m->GetMessageId()); #endif NeedsGrowl.Add(m); } else { // Is spam... do nothing... #if DEBUG_NEW_MAIL LgiTrace("%s:%i - NEW_MAIL: Bayes->IsSpam t=%p, msgid=%s\n", _FL, (Thing*)m, m->GetMessageId()); #endif m->NewEmail = Mail::NewEmailNone; m = 0; } } else { // Didn't get classified immediately, so it'll be further // processed when OnBayesResult gets called later. } } else { // Bayes filter not active... move it to growl m->NewEmail = Mail::NewEmailGrowl; NeedsGrowl.Add(m); } } } if (NeedsGrowl.Length()) { if (d->Growl) { if (!ShowDetail) { LAutoPtr n(new LGrowl::LNotify); n->Name = "new-mail"; n->Title = "New Mail"; n->Text.Printf("%i new messages", (int)MailObjs->Length()); d->Growl->Notify(n); } else { for (unsigned i=0; iGetLoaded(); LAssert(state == Store3Loaded); // If loaded then notify GrowlOnMail(m); } } } for (unsigned i=0; iNewEmail = Mail::NewEmailTray; #if DEBUG_NEW_MAIL LgiTrace("%s:%i - NewMail.OnNewMail.Growl->Tray t=%p, msgid=%s\n", _FL, (Thing*)m, m->GetMessageId()); #endif LAssert(m->GetObject()); Mail::NewMailLst.Insert(m); OnNewMailSound(); } } if (GetOptions()->GetValue(OPT_NewMailNotify, v) && v.CastInt32()) { PostEvent(M_SCRIBE_NEW_MAIL); } for (unsigned i=0; iGetPath(); LgiTrace("%s:%i - NewMail.OnNewMail.Resort=%s\n", _FL, Path.Get()); #endif Resort[i]->ReSort(); } } } LColour ScribeWnd::GetColour(int i) { static LColour MailPreview; static LColour UnreadCount; #define ReadColDef(Var, Tag, Default) \ case Tag: \ { \ if (!Var.IsValid()) \ { \ Var = Default; \ LColour::GetConfigColour("Colour."#Tag, Var); \ } \ return Var; \ break; \ } switch (i) { ReadColDef(MailPreview, L_MAIL_PREVIEW, LColour(0, 0, 255)); ReadColDef(UnreadCount, L_UNREAD_COUNT, LColour(0, 0, 255)); default: { return LColour((LSystemColour)i); break; } } return LColour(); } bool WriteXmlTag(LStream &p, LXmlTag *t) { const char *Tag = t->GetTag(); bool ValidTag = ValidStr(Tag) && !IsDigit(Tag[0]); if (ValidTag) p.Print("<%s", Tag); else { LAssert(0); return false; } LXmlTree Tree; static const char *EncodeEntitiesAttr = "\'<>\"\n"; for (unsigned i=0; iAttr.Length(); i++) { auto &a = t->Attr[i]; // Write the attribute name p.Print(" %s=\"", a.GetName()); // Encode the value if (!Tree.EncodeEntities(&p, a.GetValue(), -1, EncodeEntitiesAttr)) { LAssert(0); return false; } // Write the delimiter p.Write((void*)"\"", 1); if (iAttr.Length()-1 /*&& TestFlag(d->Flags, GXT_PRETTY_WHITESPACE)*/) { p.Write((void*)"\n", 1); } } p.Write(">", 1); return true; } LString ScribeWnd::ProcessReplyForwardTemplate(Mail *m, Mail *r, char *Xml, int &Cursor, const char *MimeType) { LStringPipe p(256); if (m && r && Xml) { bool IsHtml = MimeType && !_stricmp(MimeType, sTextHtml); LMemStream mem(Xml, strlen(Xml)); LXmlTag x; LXmlTree t(GXT_KEEP_WHITESPACE | GXT_NO_DOM); if (t.Read(&x, &mem, 0)) { ScribeDom Dom(this); Dom.Email = m; if (IsHtml) { const char *EncodeEntitiesContent = "\'<>\""; for (auto Tag: x.Children) { if (!WriteXmlTag(p, Tag)) { break; } for (const char *c = Tag->GetContent(); c; ) { const char *s = strstr(c, "") : NULL; if (s && e) { if (s > c) { t.EncodeEntities(&p, (char*)c, s - c, EncodeEntitiesContent); } s += 2; LString Var = LString(s, e - s).Strip(); LVariant v; if (Var) { LString::Array parts = Var.SplitDelimit(" "); if (parts.Length() > 0) { if (Dom.GetValue(parts[0], v)) { for (unsigned mod = 1; mod < parts.Length(); mod++) { LString::Array m = parts[mod].SplitDelimit("=", 1); if (m.Length() == 2) { if (m[0].Equals("quote")) { LVariant Quote; if (Dom.GetValue(m[1], Quote)) { LVariant WrapColumn; if (!GetOptions()->GetValue(OPT_WrapAtColumn, WrapColumn) || WrapColumn.CastInt32() <= 0) WrapColumn = 76; WrapAndQuote(p, Quote.Str(), WrapColumn.CastInt32(), v.Str(), NULL, MimeType); v.Empty(); } } } } switch (v.Type) { case GV_STRING: { p.Push(v.Str()); break; } case GV_DATETIME: { p.Push(v.Value.Date->Get()); break; } case GV_NULL: break; default: { LAssert(!"Unsupported type."); break; } } } } } c = e + 2; } else { p.Print("%s", c); break; } } } } else { LArray Tags; Tags.Add(&x); for (auto Tag: x.Children) { Tags.Add(Tag); } for (unsigned i=0; iGetTag() && Dom.GetValue(Tag->GetTag(), v)) { char *s = v.Str(); if (s) { const char *Quote; if ((Quote = Tag->GetAttr("quote"))) { LVariant q, IsQuote; GetOptions()->GetValue(OPT_QuoteReply, IsQuote); if (r->GetValue(Quote, q)) { Quote = q.Str(); } else { Quote = "> "; } if (Quote && IsQuote.CastInt32()) { LVariant WrapColumn; if (!GetOptions()->GetValue(OPT_WrapAtColumn, WrapColumn) || WrapColumn.CastInt32() <= 0) WrapColumn = 76; WrapAndQuote(p, Quote, WrapColumn.CastInt32(), s); } else { p.Push(s); } } else { p.Push(s); } } else if (v.Type == GV_DATETIME && v.Value.Date) { char b[64]; v.Value.Date->Get(b, sizeof(b)); p.Push(b); } } else if (Tag->IsTag("cursor")) { int Size = (int)p.GetSize(); char *Buf = new char[Size+1]; if (Buf) { p.Peek((uchar*)Buf, Size); Buf[Size] = 0; RemoveReturns(Buf); Cursor = LCharLen(Buf, "utf-8"); DeleteArray(Buf); } } if (Tag->GetContent()) { p.Push(Tag->GetContent()); } } } } } return p.NewGStr(); } LAutoString ScribeWnd::ProcessSig(Mail *m, char *Xml, const char *MimeType) { LStringPipe p; if (!m || !Xml) return LAutoString(); if (MimeType && !_stricmp(MimeType, sTextHtml)) p.Write(Xml, strlen(Xml)); else { LMemStream mem(Xml, strlen(Xml)); LXmlTag x; LXmlTree t(GXT_KEEP_WHITESPACE|GXT_NO_DOM); if (t.Read(&x, &mem, 0)) { for (auto Tag: x.Children) { if (Tag->IsTag("random-line")) { char *FileName = 0; if ((FileName = Tag->GetAttr("Filename"))) { char *File = LReadTextFile(FileName); if (File) { LToken Lines(File, "\r\n"); DeleteArray(File); char *RandomLine = Lines[LRand((unsigned)Lines.Length())]; if (RandomLine) { p.Push(RandomLine); } } } } else if (Tag->IsTag("random-paragraph")) { char *FileName = 0; if ((FileName = Tag->GetAttr("Filename"))) { char *File = LReadTextFile(FileName); if (File) { List Para; for (char *f=File; f && *f; ) { // skip whitespace while (strchr(" \t\r\n", *f)) f++; if (*f) { char *Start = f; char *n; while ((n = strchr(f, '\n'))) { f = n + 1; if (f[1] == '\n' || (f[1] == '\r' && f[2] == '\n')) { break; } } if (f == Start) f += strlen(f); Para.Insert(NewStr(Start, f-Start)); } } DeleteArray(File); char *RandomPara = Para.ItemAt(LRand((int)Para.Length())); if (RandomPara) { p.Push(RandomPara); } Para.DeleteArrays(); } } } else if (Tag->IsTag("include-file")) { char *FileName = 0; if ((FileName = Tag->GetAttr("filename"))) { char *File = LReadTextFile(FileName); if (File) { p.Push(File); DeleteArray(File); } } } else if (Tag->IsTag("quote-file")) { char *FileName = 0; char *QuoteStr = 0; if ((FileName = Tag->GetAttr("filename")) && (QuoteStr = Tag->GetAttr("Quote"))) { } } else { p.Push(Tag->GetContent()); } } } } return LAutoString(p.NewStr()); } bool ScribeWnd::GetAccessLevel(LViewI *Parent, ScribePerm Required, const char *ResourceName) { if (Required <= CurrentAuthLevel) { return true; } if (!Parent) { Parent = this; } switch (Required) { case PermRequireNone: { return true; break; } case PermRequireUser: { bool Status = false; GPassword p; if (p.Serialize(GetOptions(), OPT_UserPermPassword, false)) { char Msg[256]; sprintf_s(Msg, sizeof(Msg), LLoadString(IDS_ASK_USER_PASS), ResourceName); LInput d(Parent, "", Msg, AppName, true); if (d.DoModal() && d.GetStr()) { char Pass[256]; p.Get(Pass); Status = strcmp(Pass, d.GetStr()) == 0; if (Status) { CurrentAuthLevel = PermRequireUser; auto i = Menu->FindItem(IDM_LOGOUT); if (i) i->Enabled(true); } } } else { Status = true; } return Status; } case PermRequireAdmin: { LString Key; Key.Printf("Scribe.%s", OPT_AdminPassword); auto Hash = LAppInst->GetConfig(Key); if (ValidStr(Hash)) { uchar Bin[256]; ssize_t BinLen = 0; if ((BinLen = ConvertBase64ToBinary(Bin, sizeof(Bin), Hash, strlen(Hash))) == 16) { LInput d(Parent, "", LLoadString(IDS_ASK_ADMIN_PASS), AppName, true); if (d.DoModal() && d.GetStr()) { unsigned char Digest[16]; char Str[256]; sprintf_s(Str, sizeof(Str), "%s admin", d.GetStr().Get()); MDStringToDigest(Digest, Str); if (memcmp(Bin, Digest, 16) == 0) { CurrentAuthLevel = PermRequireAdmin; auto i = Menu->FindItem(IDM_LOGOUT); if (i) i->Enabled(true); return true; } } } else { LgiMsg(Parent, "Admin password not correctly encoded.", AppName); } return false; } break; } } return false; } bool ScribeWnd::GetAccountSettingsAccess(LViewI *Parent, ScribeAccessType AccessType) { LVariant Level = (int)PermRequireNone; // Check if user level access is required char *Opt = (char*)(AccessType == ScribeReadAccess ? OPT_AccPermRead : OPT_AccPermWrite); GetOptions()->GetValue(Opt, Level); /* // Check if admin access is required char *Admin = GetScribeAccountPerm((char*) (AccessType == ScribeReadAccess ? "Read" : "Write")); if (Admin && _stricmp(Admin, "Admin") == 0) { Level = PermRequireAdmin; } */ return GetAccessLevel(Parent ? Parent : this, (ScribePerm)Level.CastInt32(), "Account Settings"); } LMutex *ScribeWnd::GetLock() { return _Lock; } void ScribeWnd::OnBeforeConnect(ScribeAccount *Account, bool Receive) { if (Receive) { Account->Receive.Enabled(false); } else { Account->Send.Enabled(true); } if (StatusPanel) { StatusPanel->Invalidate(); } } void ScribeWnd::OnAfterConnect(ScribeAccount *Account, bool Receive) { if (Account) { SaveOptions(); if (ScribeState == ScribeExiting) { LCloseApp(); } } if (Receive) { Account->Receive.Enabled(true); } else { Account->Send.Enabled(true); } if (StatusPanel) { StatusPanel->Invalidate(); } if (d->SendAfterReceive) { bool Online = false; for (auto a: Accounts) { bool p = a->Receive.IsPersistant(); bool o = a->Receive.IsOnline(); if (!p && o) { Online = true; break; } } if (!Online) { Send(-1, true); d->SendAfterReceive = false; } } } void ScribeWnd::Send(int Which, bool Quiet) { if (ScribeState == ScribeExiting) return; if (Which < 0) { LVariant v; if (GetOptions()->GetValue(OPT_DefaultSendAccount, v)) Which = v.CastInt32(); } LArray Outboxes; unsigned i; for (i=0; iLoadThings(); Outboxes.Add(OutBox); } } if (Outboxes.Length() < 1) { LgiMsg(this, LLoadString(IDS_NO_OUTGOING_FOLDER), AppName, MB_OK); return; } int MailToSend = 0; List Acc; SendAccountlet *Default = 0; { // Create list of accounts for (auto a: Accounts) { Acc.Insert(&a->Send); a->Send.Outbox.DeleteObjects(); if (Which < 0 || a->GetIndex() == Which) Default = &a->Send; } // If the default it not in the list try the first one.. if (!Default) Default = Acc[0]; } for (i=0; iItems) { Mail *m = t->IsMail(); if (!m) continue; uint32_t Flags = m->GetFlags(); if (!TestFlag(Flags, MAIL_SENT) && TestFlag(Flags, MAIL_READY_TO_SEND)) { GDataIt To = m->GetObject()->GetList(FIELD_TO); if (To && To->Length()) { LAutoPtr Out(new ScribeEnvelope); if (Out) { if (m->OnBeforeSend(Out)) { SendAccountlet *Send = 0; for (auto a: Acc) { LVariant Ie = a->GetAccount()->Identity.Email(); if (ValidStr(Ie.Str()) && a->OnlySendThroughThisAccount()) { if (Ie.Str() && m->GetFromStr(FIELD_EMAIL) && _stricmp(Ie.Str(), m->GetFromStr(FIELD_EMAIL)) == 0) { Send = a; break; } } } if (!Send) { Send = Default; } if (Send) { LAssert(Out->To.Length() > 0); Out->SourceFolder = OutBox->GetPath(); Send->Outbox.Add(Out.Release()); MailToSend++; } } } } else { LgiMsg( this, LLoadString(IDS_ERROR_NO_RECIPIENTS), AppName, MB_OK, m->GetSubject() ? m->GetSubject() : (char*)"(none)"); } } } } if (MailToSend) { for (auto a: Acc) { if (a->Outbox.Length() > 0 && !a->IsOnline()) { if (a->IsConfigured()) { a->Connect(0, Quiet); } else { LAlert d(this, AppName, LLoadString(IDS_ERROR_NO_CONFIG_SEND), LLoadString(IDS_CONFIGURE), LLoadString(IDS_CANCEL)); if (d.DoModal() == 1) { a->GetAccount()->InitUI(this, 1); } } } } } else { LgiMsg(this, LLoadString(IDS_NO_MAIL_TO_SEND), AppName, MB_OK, Outboxes[0]->GetText()); } } void ScribeWnd::Receive(int Which) { #define LOG_RECEIVE 0 if (ScribeState == ScribeExiting) { LgiTrace("%s:%i - Won't receive, is trying to exit.\n", _FL); return; } for (ScribeAccount *i: Accounts) { if (i->GetIndex() != Which) continue; if (i->Receive.IsOnline()) { #if LOG_RECEIVE LgiTrace("%s:%i - %i already online.\n", _FL, Which); #endif } else if (i->Receive.Disabled() > 0) { #if LOG_RECEIVE LgiTrace("%s:%i - %i is disabled.\n", _FL, Which); #endif } else if (!i->Receive.IsConfigured()) { #if LOG_RECEIVE LgiTrace("%s:%i - %i is not configured.\n", _FL, Which); #endif LAlert a(this, AppName, LLoadString(IDS_ERROR_NO_CONFIG_RECEIVE), LLoadString(IDS_CONFIGURE), LLoadString(IDS_CANCEL)); if (a.DoModal() == 1) { i->InitUI(this, 2); } } else { i->Receive.Connect(0, false); } break; } } bool ScribeWnd::GetHelpFilesPath(char *Path, int PathSize) { const char *Index = "index.html"; char Install[MAX_PATH_LEN]; strcpy_s(Install, sizeof(Install), ScribeResourcePath()); for (int i=0; i<5; i++) { char p[MAX_PATH_LEN]; LMakePath(p, sizeof(p), Install, "Help"); LMakePath(p, sizeof(p), p, Index); LgiTrace("Trying '%s'\n", p); if (LFileExists(p)) { LTrimDir(p); strcpy_s(Path, PathSize, p); return true; } #ifdef MAC LMakePath(p, sizeof(p), Install, "Resources/Help"); LMakePath(p, sizeof(p), p, Index); // LgiTrace("Trying '%s'\n", p); if (LFileExists(p)) { LTrimDir(p); strcpy_s(Path, PathSize, p); return true; } #endif LTrimDir(Install); // Try all the parent folders... } LArray Ext; LArray Help; Ext.Add("index.html"); LRecursiveFileSearch(Install, &Ext, &Help); for (unsigned i=0; iSetEvents(d); return Browse->SetUri(Path); } #else #ifdef MAC if (LExecute(Path)) return true; #else if (Hash) *Hash = '#'; // Get browser... char Browser[256]; if (!LGetAppForMimeType("application/browser", Browser, sizeof(Browser))) { LgiTrace("%s:%i - LGetAppForMimeType('text/html') failed.\n", _FL); goto HelpError; } // Execute browser to view help... char Uri[256]; sprintf_s(Uri, sizeof(Uri), "\"file://%s\"", Path); #ifdef WIN32 char *c; while (c = strchr(Uri, '\\')) *c = '/'; #endif LgiTrace("LaunchHelp('%s','%s').\n", Browser, Uri); if (!LExecute(Browser, Uri)) { LgiTrace("%s:%i - LExecute('%s','%s') failed.\n", _FL, Browser, Uri); goto HelpError; } return true; #endif #endif HelpError: LgiMsg(this, LLoadString(IDS_ERROR_NO_HELP), AppName, MB_OK); } return false; } void ScribeWnd::Preview(int Which) { LArray a; a.Add(Accounts[Which]); OpenPopView(this, a); } bool MergeSegments(LDataPropI *DstProp, LDataPropI *SrcProp, LDom *Dom) { LDataI *Src = dynamic_cast(SrcProp); LDataI *Dst = dynamic_cast(DstProp); if (!Dst || !Src) return false; Store3MimeType Mt(Src->GetStr(FIELD_MIME_TYPE)); if (Mt.IsText()) { // Set the headers... Dst->SetStr(FIELD_INTERNET_HEADER, Src->GetStr(FIELD_INTERNET_HEADER)); // Do mail merge of data part... LAutoStreamI Data = Src->GetStream(_FL); if (Data) { // Read data out into a string string... LAutoString Str(new char[(int)Data->GetSize()+1]); Data->Read(Str, (int)Data->GetSize()); Str[Data->GetSize()] = 0; // Do field insert and save result to segment char *Merged = ScribeInsertFields(Str, Dom); LAutoStreamI Mem(new LMemStream(Merged, strlen(Merged))); Dst->SetStream(Mem); } } else { // Straight copy... Dst->CopyProps(*Src); } // Merge children segments as well GDataIt Sc = Src->GetList(FIELD_MIME_SEG); GDataIt Dc = Dst->GetList(FIELD_MIME_SEG); if (Dc && Sc) { for (unsigned i=0; iLength(); i++) { // Create new dest child, and merge the source child across LDataPropI *NewDestChild = Dc->Create(Dst->GetStore()); if (!MergeSegments(NewDestChild, (*Sc)[i], Dom)) return false; LDataI *NewDest = dynamic_cast(NewDestChild); if (NewDest) NewDest->Save(Dst); } } return true; } // Either 'FileName' or 'Source' will be valid void ScribeWnd::MailMerge(LArray &Contacts, const char *FileName, Mail *Source) { ScribeFolder *Outbox = GetFolder(FOLDER_OUTBOX); if (Outbox && Contacts.Length() && (FileName || Source)) { LAutoPtr ImportEmail; if (FileName) { LAutoPtr File(new LFile); if (File->Open(FileName, O_READ)) { Thing *t = CreateItem(MAGIC_MAIL, Outbox, false); ImportEmail.Reset(Source = t->IsMail()); if (!Source->Import(Source->AutoCast(File), sMimeMessage)) { Source = 0; } } } if (Source) { List Msgs; ScribeDom Dom(this); // Do the merging of the document with the database for (unsigned i=0; iGetContact() : 0; Contact *temp = new Contact(this); if (!c) { temp->SetFirst(la->sName); temp->SetEmail(la->sAddr); c = temp; } Dom.Con = c; Thing *t = CreateItem(MAGIC_MAIL, Outbox, false); if (t) { Dom.Email = t->IsMail(); LAutoString s; if (s.Reset(ScribeInsertFields(Source->GetSubject(), &Dom))) Dom.Email->SetSubject(s); LDataPropI *Recip = Dom.Email->GetTo()->Create(Dom.Email->GetObject()->GetStore()); if (Recip) { Recip->CopyProps(*Dom.Con->GetObject()); Recip->SetInt(FIELD_CC, 0); Dom.Email->GetTo()->Insert(Recip); } MergeSegments( Dom.Email->GetObject()->GetObj(FIELD_MIME_SEG), Source->GetObject()->GetObj(FIELD_MIME_SEG), &Dom); Msgs.Insert(Dom.Email); } temp->DecRef(); } // Ask user what to do if (Msgs[0]) { char Msg[256]; sprintf_s(Msg, sizeof(Msg), LLoadString(IDS_MAIL_MERGE_Q), Msgs.Length()); LAlert Ask(this, AppName, Msg, LLoadString(IDS_SAVE_TO_OUTBOX), LLoadString(IDS_CANCEL)); switch (Ask.DoModal()) { case 1: // Save To Outbox { for (size_t i=0; iSave(Outbox); } break; } case 2: // Cancel { for (size_t i=0; iOnDelete(); } break; } } } else { LgiMsg(this, LLoadString(IDS_MAIL_MERGE_EMPTY), AppName); } } } } ScribeFolder *CastFolder(LDataI *f) { if (!f) { LAssert(!"Null pointer"); return 0; } if (f->Type() == MAGIC_FOLDER) { return (ScribeFolder*)f->UserData; } return 0; } Thing *CastThing(LDataI *t) { if (!t) { LAssert(!"Null pointer"); return 0; } if (t->Type() == MAGIC_FOLDER) { LAssert(!"Shouldn't be a folder"); return 0; } return (Thing*)t->UserData; } LString _GetUids(LArray &items) { LString::Array a; for (auto i: items) a.New().Printf(LPrintfInt64, i->GetInt(FIELD_SERVER_UID)); return LString(",").Join(a); } /// Received new items from a storage backend. void ScribeWnd::OnNew( /// The parent folder of the new item LDataFolderI *Parent, /// All the new items LArray &NewItems, /// The position in the parent folder or -1 int Pos, /// Non-zero if the object is a new email. bool IsNew) { ScribeFolder *Fld = CastFolder(Parent); int UnreadDiff = 0; if (Stricmp(Parent->GetStr(FIELD_FOLDER_NAME), "Contacts") && Stricmp(Parent->GetStr(FIELD_FOLDER_NAME), "Calendar")) { LOG_STORE("OnNew(%s, %s, %i, %i)\n", Parent->GetStr(FIELD_FOLDER_NAME), _GetUids(NewItems).Get(), Pos, IsNew); } if (!Fld) { // When this happens the parent hasn't been loaded by the UI thread // yet, so if say the IMAP back end notices a new sub-folder then we // can safely ignore it until such time as the UI loads the parent // folder. At which point the child we got told about here will be // loaded anyway. #if DEBUG_NEW_MAIL LDataFolderI *p = dynamic_cast(Parent); LgiTrace("%s:%i - NewMail.OnNew no UI object for '%s'\n", _FL, p ? p->GetStr(FIELD_FOLDER_NAME) : NULL); #endif return; } if (!Parent || !NewItems.Length()) { LAssert(!"Param error"); return; } for (auto cb: d->Store3EventCallbacks) cb->OnNew(Parent, NewItems, Pos, IsNew); List NewMail; for (auto Item: NewItems) { if (Item->Type() == MAGIC_FOLDER) { // Insert new folder into the right point on the tree... LDataFolderI *SubObject = dynamic_cast(Item); if (!SubObject) { LAssert(!"Not a valid folder."); continue; } ScribeFolder *Sub = CastFolder(SubObject); // LgiTrace("OnNew '%s', Sub=%p Children=%i\n", SubObject->GetStr(FIELD_FOLDER_NAME), Sub, SubObject->SubFolders().Length()); if (!Sub) { // New folder... if ((Sub = new ScribeFolder)) { Sub->App = this; Sub->SetObject(SubObject, false, _FL); Fld->Insert(Sub); } } else { // Existing folder... Fld->Insert(Sub, Pos); } } else { Thing *t = CastThing(Item); if (!t) { // Completely new thing... t = CreateThingOfType((Store3ItemTypes) Item->Type(), Item); } if (t) { if (t->DeleteOnAdd.Obj) { // Complete a delayed move auto OldFolder = t->App->GetFolder(t->DeleteOnAdd.Path); if (!OldFolder) { LgiTrace("%s:%i - Couldn't resolve old folder '%s'\n", _FL, t->DeleteOnAdd.Path.Get()); } else { auto OldItem = t->DeleteOnAdd.Obj; if (!OldFolder->Items.HasItem(OldItem)) LgiTrace("%s:%i - Couldn't find old obj.\n", _FL); else OldItem->OnDelete(); } } t->SetParentFolder(Fld); Fld->Update(); if (Fld->Select()) { // Existing thing... t->SetFieldArray(Fld->GetFieldArray()); ThingFilter *Filter = GetThingFilter(); if (!Filter || Filter->TestThing(t)) { MailList->Insert(t, -1, false); } } Mail *m = t->IsMail(); if (m) { // LgiTrace("OnNew %p\n", m->GetObject()); UnreadDiff += TestFlag(m->GetFlags(), MAIL_READ) ? 0 : 1; #if DEBUG_NEW_MAIL LgiTrace("%s:%i - NewMail.OnNew t=%p uid=%s IsNew=%i\n", _FL, t, m->GetServerUid().ToString().Get(), IsNew); #endif if (IsNew) { LAssert(!NewMail.HasItem(m)); NewMail.Insert(m); } } t->Update(); } } } if (UnreadDiff) Fld->OnUpdateUnRead(UnreadDiff, false); if (MailList && Fld->Select()) MailList->Sort(ListItemCompare, (NativeInt)Fld); if (NewMail.Length()) OnNewMail(&NewMail); } void ScribeWnd::OnPropChange(LDataStoreI *store, int Prop, LVariantType Type) { switch (Prop) { case FIELD_IS_ONLINE: { // This is in case we receive a message after the app has shutdown and // deleted 'this'. if (ScribeState > ScribeRunning) break; if (StatusPanel) StatusPanel->Invalidate(); for (auto a : Accounts) { if (a->Receive.GetDataStore() == store) { int64 Online = store->GetInt(FIELD_IS_ONLINE); auto Old = ScribeState; // This prevents the folders unloading during this call. // Which causes crashes. ScribeState = ScribeLoadingFolders; a->Receive.OnOnlineChange(Online != 0); ScribeState = Old; break; } } break; } } } void ScribeWnd::SetContext(const char *file, int line) { d->CtxFile = file; d->CtxLine = line; } bool ScribeWnd::OnChange(LArray &items, int FieldHint) { bool UpdateSelection = false; List NewMail; ScribeFolder *Parent = 0; // LOG_STORE("OnChange(%i, %i)\n", (int)items.Length(), FieldHint); LAssert(d->CtxFile != NULL); for (unsigned c=0; cStore3EventCallbacks.Length(); c++) { d->Store3EventCallbacks[c]->OnChange(items, FieldHint); } for (unsigned i=0; iType() == MAGIC_FOLDER) { auto ItemFolder = dynamic_cast(Item); ScribeFolder *fld = CastFolder(ItemFolder); if (fld) { if (FieldHint == FIELD_STATUS) { // This is the delayed folder load case: fld->IsLoaded(true); if (fld->Select()) fld->Populate(GetMailList()); } else { fld->Update(); } } else { // LAssert(!"Can't cast to folder?"); } } else if ((t = CastThing(Item))) { ThingUi *Ui = t->GetUI(); if (Ui) Ui->OnChange(); Mail *m = t->IsMail(); if (m) { auto StoreFlags = Item->GetInt(FIELD_FLAGS); #if 0 LgiTrace("App.OnChange(%i) handler %p: %s -> %s\n", FieldHint, m, EmailFlagsToStr(m->FlagsCache).Get(), EmailFlagsToStr(StoreFlags).Get()); #endif if (TestFlag(m->FlagsCache, MAIL_NEW) && !TestFlag(StoreFlags, MAIL_NEW)) { Mail::NewMailLst.Delete(m); Parent = m->GetFolder(); } if (m->FlagsCache != StoreFlags) { // LgiTrace("%s:%i - OnChange mail flags changed.\n", _FL); m->SetFlagsCache(StoreFlags, false, false); Parent = m->GetFolder(); } if (m->NewEmail == Mail::NewEmailLoading) { auto Loaded = m->GetLoaded(); if (Loaded < Store3Loaded) { #if DEBUG_NEW_MAIL LgiTrace("%s:%i - NewMail.OnChange.GetBody t=%p, uid=%s, mode=%s, loaded=%s (%s:%i)\n", _FL, (Thing*)m, m->GetServerUid().ToString().Get(), toString(m->NewEmail), toString(Loaded), d->CtxFile, d->CtxLine); #endif m->GetBody(); } else { #if DEBUG_NEW_MAIL LgiTrace("%s:%i - NewMail.OnChange.NewMail t=%p, uid=%s, mode=%s, loaded=%s (%s:%i)\n", _FL, (Thing*)m, m->GetServerUid().ToString().Get(), toString(m->NewEmail), toString(Loaded), d->CtxFile, d->CtxLine); #endif NewMail.Insert(m); } } } else if (t->IsCalendar()) { for (auto cv: CalendarView::CalendarViews) cv->OnContentsChanged(); } if (FieldHint != FIELD_FLAGS) { // Call the on load handler... t->IsLoaded(true); } if (t->GetList()) { t->Update(); if (FieldHint != FIELD_FLAGS) UpdateSelection |= t->Select(); } } } if (MailList && UpdateSelection) { List Sel; if (MailList->GetSelection(Sel)) { OnSelect(&Sel, true); } } if (Parent) Parent->OnUpdateUnRead(0, true); if (NewMail.Length()) OnNewMail(&NewMail); d->CtxFile = NULL; d->CtxLine = 0; return true; } ContactGroup *ScribeWnd::FindGroup(char *SearchName) { ScribeFolder *g = GetFolder(FOLDER_GROUPS); if (!g || !SearchName) return 0; g->LoadThings(); for (Thing *t: g->Items) { ContactGroup *Grp = t->IsGroup(); if (!Grp) continue; auto Name = Grp->GetObject()->GetStr(FIELD_GROUP_NAME); if (Name) { if (!_stricmp(Name, SearchName)) { return Grp; } } } return 0; } bool ScribeWnd::Match(LDataStoreI *Store, LDataPropI *Address, int ObjType, LArray &Matches) { if (!Store || !Address) return 0; // char *Addr = Address->GetStr(ObjType == MAGIC_CONTACT ? FIELD_EMAIL : FIELD_NAME); auto Addr = Address->GetStr(FIELD_EMAIL); if (!Addr) return 0; List *l = GetEveryone(); if (!l) return 0; Matches.Length(0); if (ObjType == MAGIC_CONTACT) { for (auto c: *l) { if (strchr(Addr, '@')) { auto Emails = c->GetEmails(); for (auto e: Emails) { if (e.Equals(Addr)) { Matches.Add(c); break; } } } } } else if (ObjType == MAGIC_GROUP) { ScribeFolder *g = GetFolder(FOLDER_GROUPS); if (!g) return false; g->LoadThings(); for (auto t: g->Items) { ContactGroup *Grp = t->IsGroup(); if (!Grp) continue; List GrpAddr; if (!Grp->GetAddresses(GrpAddr)) continue; for (auto a: GrpAddr) { if (_stricmp(a, Addr) == 0) { Matches.Add(t); break; } } GrpAddr.DeleteArrays(); } } return Matches.Length() > 0; } bool ScribeWnd::OnMove(LDataFolderI *new_parent, LDataFolderI *old_parent, LArray &items) { if (!new_parent || items.Length() == 0) return false; bool Status = false; for (auto cb: d->Store3EventCallbacks) cb->OnMove(new_parent, old_parent, items); ScribeFolder *New = CastFolder(new_parent); ScribeFolder *Old = old_parent ? CastFolder(old_parent) : NULL; if (New) { ssize_t SelIdx = -1; for (unsigned n=0; nType()) { case MAGIC_FOLDER: { ScribeFolder *i = CastFolder(items[n]); if (i) { i->Detach(); New->Insert(i); Status = true; } break; } default: { Thing *t = CastThing(items[n]); if (t) { int UnreadMail = t->IsMail() ? !TestFlag(t->IsMail()->GetFlags(), MAIL_READ) : false; if (Old && UnreadMail) Old->OnUpdateUnRead(-1, false); if (t->GetList()) { if (t->Select() && SelIdx < 0) SelIdx = t->GetList()->IndexOf(t); t->GetList()->Remove(t); } // This closes the user interface if the object is being moved to the trash... if (New->GetSystemFolderType() == Store3SystemTrash) t->SetUI(); t->SetParentFolder(New); if (New->Select()) { MailList->Insert(t); MailList->ReSort(); } if (UnreadMail) New->OnUpdateUnRead(1, false); if (New->GetItemType() == MAGIC_ANY && t->IsMail()) { Mail::NewMailLst.Delete(t->IsMail()); } Status = true; } break; } } } if (MailList && SelIdx >= 0 && MailList->Length() > 0) { if (SelIdx >= (ssize_t)MailList->Length()) SelIdx = MailList->Length()-1; MailList->Value(SelIdx); } } return Status; } bool ScribeWnd::OnDelete(LDataFolderI *Parent, LArray &Items) { int UnreadAdjust = 0; int SelectIdx = -1; LOG_STORE("OnDelete(%s, %i)\n", Parent->GetStr(FIELD_FOLDER_NAME), (int)Items.Length()); if (!Items.Length()) return false; for (unsigned c=0; cStore3EventCallbacks.Length(); c++) { d->Store3EventCallbacks[c]->OnDelete(Parent, Items); } ScribeFolder *Fld = 0; for (unsigned i=0; iType() == MAGIC_FOLDER) { // Insert new folder into the right point on the tree... ScribeFolder *Sub = CastFolder(Item); if (!Sub) return true; LgiTrace("OnDelete folder %p (%i)\n", (ThingType*)Sub, ThingType::DirtyThings.HasItem(Sub)); // Remove the deleted object from the dirty queue... ThingType::DirtyThings.Delete(Sub); // And make sure it can't get dirty again... Sub->SetWillDirty(false); // Remove all the children LDataIterator &SubItems = Sub->GetFldObj()->Children(); LArray Children; for (LDataI *c = SubItems.First(); c; c = SubItems.Next()) { Children.Add(c); } OnDelete(Sub->GetFldObj(), Children); // Remove the UI element Sub->Remove(); // Free the memory for the object DeleteObj(Sub); } else { Fld = Parent ? CastFolder(Parent) : 0; if (!Fld) return false; Thing *t = CastThing(Item); if (!t) { // This happens when the item moves from one store to another // of a different type and a copy of the old object is made. The // 'Thing' is detached from 'Item' and attached to the new LDataI // object. // However if the object is an unread email... we should still decrement the // folder's unread count. if (Item->Type() == MAGIC_MAIL && !(Item->GetInt(FIELD_FLAGS) & MAIL_READ)) { Fld->OnUpdateUnRead(-1, false); } } else { LAssert(!Fld || Fld == t->GetFolder()); // LgiTrace("OnDelete thing %p (%i)\n", (ThingType*)t, ThingType::DirtyThings.HasItem(t)); // Remove the deleted object from the dirty queue... ThingType::DirtyThings.Delete(t); // And make sure it can't get dirty again... t->SetWillDirty(false); // Was the thing currently being previewed? if (PreviewPanel && PreviewPanel->GetCurrent() == t) PreviewPanel->OnThing(0, false); // Was the object selected in the thing list? if (Fld && Fld->Select()) { // If so, select an adjacent item. if (SelectIdx < 0 && t->Select()) SelectIdx = MailList->IndexOf(t); MailList->Remove(t); } // Was the object an unread email? Mail *m = t->IsMail(); if (m) { if (!TestFlag(m->GetFlags(), MAIL_READ) && Fld) UnreadAdjust--; Mail::NewMailLst.Delete(m); } if (Fld) { LAssert(Fld->Items.HasItem(t)); Fld->Items.Delete(t); Fld->Update(); } t->SetUI(); t->SetParentFolder(NULL); t->SetObject(NULL, false, _FL); if (!t->DecRef()) { t->SetDirty(false); t->SetWillDirty(false); } } } } if (Fld) Fld->OnUpdateUnRead(UnreadAdjust, false); if (SelectIdx >= 0) { LListItem *i = MailList->ItemAt(SelectIdx); if (!i && MailList->Length() > 0) i = MailList->ItemAt(MailList->Length()-1); if (i) i->Select(true); } return true; } bool ScribeWnd::AddStore3EventHandler(LDataEventsI *callback) { if (!d->Store3EventCallbacks.HasItem(callback)) { d->Store3EventCallbacks.Add(callback); } return true; } bool ScribeWnd::RemoveStore3EventHandler(LDataEventsI *callback) { d->Store3EventCallbacks.Delete(callback); return true; } bool ScribeWnd::OnMailTransferEvent(MailTransferEvent *e) { if (!Lock(_FL)) return false; d->Transfers.Add(e); Unlock(); return true; } bool ScribeWnd::OnTransfer() { LVariant v; LArray Local; // Lock the transfer list if (Lock(_FL)) { // Take out a bunch of emails... for (int i=0; i<5 && d->Transfers.Length() > 0; i++) { MailTransferEvent *Transfer = d->Transfers[0]; d->Transfers.DeleteAt(0, true); // Save them to a local array Local.Add(Transfer); } Unlock(); if (Local.Length() == 0) // Didn't get any return false; } LArray Trans; for (unsigned s=0; sStartTransaction(); } for (auto Transfer: Local) { ReceiveStatus NewStatus = MailReceivedError; if (!Transfer) { LAssert(0); continue; } // We have to set Transfer->Status to something other than "waiting" // in all branches of this loop. Othewise the account thread will hang. Accountlet *Acc = Transfer->Account; #if DEBUG_NEW_MAIL LgiTrace( "%s:%i - NewMail.OnTransfer t=%p receive=%i act=%i\n", _FL, Transfer, Acc->IsReceive(), Transfer->Action); #endif if (Acc->IsReceive()) { switch (Transfer->Action) { default: break; case MailDownload: case MailDownloadAndDelete: { // Import newly received mail from file ReceiveAccountlet *Receive = dynamic_cast(Acc); if (Receive) { LVariant Path = Receive->DestinationFolder(); ScribeFolder *Inbox = Path.Str() ? GetFolder(Path.Str()) : NULL; if (!Inbox) Inbox = GetFolder(FOLDER_INBOX); if (Inbox) { Mail *m = new Mail(this, Inbox->GetObject()->GetStore()->Create(MAGIC_MAIL)); if (m) { // Just in case m->SetParentFolder(Inbox); // Set the new flag... m->SetFlags(m->GetFlags() | MAIL_NEW, true, false); // Set the account to, which is used to map the email back // to the incoming account in the case that the headers // don't have the correct "To" information. m->SetAccountId(Receive->Id()); // Decode the email m->OnAfterReceive(Transfer->Rfc822Msg); LVariant v; m->SetServerUid(v = Transfer->Uid); // Save to permanent storage if (m->Save(Inbox)) { m->SetDirty(false); NewStatus = MailReceivedOk; // Only after we've safely stored the email can we // actually mark it as downloaded. if (Transfer->Uid) { Receive->AddMsg(Transfer->Uid); } } else LgiTrace("%s:%i - Error: Couldn't save mail to folders.\n", _FL); } else LgiTrace("%s:%i - Error: Memory alloc failed.\n", _FL); } else LgiTrace("%s:%i - Error: No Inbox.\n", _FL); } else LgiTrace("%s:%i - Error: Bad ptr.\n", _FL); break; } case MailHeaders: { LList *Lst; if (Transfer->Msg && (Lst = Transfer->GetList())) { //LgiTrace("Using Lst=%p\n", Lst); Lst->Insert(Transfer->Msg); NewStatus = MailReceivedOk; } break; } } } else if (Transfer->Send) { ScribeFolder *Outbox = GetFolder(Transfer->Send->SourceFolder); if (!Outbox) Outbox = GetFolder(FOLDER_OUTBOX); if (Outbox) { Mail *m = Outbox->GetMessageById(Transfer->Send->MsgId); if (!m) { LAssert(!"Where is the email?"); LgiTrace("%s:%i - Can't find outbox for msg id '%s'\n", _FL, Transfer->Send->MsgId.Get()); } else { if (Transfer->OutgoingHeaders) { m->SetInternetHeader(Transfer->OutgoingHeaders); DeleteArray(Transfer->OutgoingHeaders); } m->OnAfterSend(); m->Save(); // Do filtering LVariant DisableFilters; GetOptions()->GetValue(OPT_DisableUserFilters, DisableFilters); if (!DisableFilters.CastInt32()) { // Run the filters List Filters; GetFilters(Filters, false, true, false); if (Filters[0]) { List In; In.Insert(m); Filter::ApplyFilters(0, Filters, In); } } // Add to bayesian spam whitelist... LVariant v; ScribeBayesianFilterMode FilterMode = BayesOff; GetOptions()->GetValue(OPT_BayesFilterMode, v); FilterMode = (ScribeBayesianFilterMode) v.CastInt32(); if (FilterMode != BayesOff && m->GetObject()) { GDataIt To = m->GetObject()->GetList(FIELD_TO); if (To) { for (LDataPropI *a = To->First(); a; a = To->Next()) { if (a->GetStr(FIELD_EMAIL)) WhiteListIncrement(a->GetStr(FIELD_EMAIL)); } } } NewStatus = MailReceivedOk; } } } #if DEBUG_NEW_MAIL LgiTrace( "%s:%i - NewMail.OnTransfer t=%p NewStatus=%i\n", _FL, Transfer, NewStatus); #endif if (NewStatus != MailReceivedOk) { // So tell the thread not to delete it from the server LgiTrace("%s:%i - Mail[%i] error: %s\n", _FL, Transfer->Index, ReceiveStatusName(Transfer->Status)); Transfer->Action = MailNoop; } Transfer->Status = NewStatus; } return Local.Length() > 0; } bool ScribeWnd::OnIdle() { bool Status = false; for (auto a : Accounts) Status |= a->Receive.OnIdle(); Status |= OnTransfer(); LMessage m(M_SCRIBE_IDLE); BayesianFilter::OnEvent(&m); SaveDirtyObjects(); #ifdef _DEBUG static uint64_t LastTs = 0; auto Now = LCurrentTime(); if (Now - LastTs >= 1000) { LastTs = Now; if (Thing::DirtyThings.Length() > 0) LgiTrace("%s:%i - Thing::DirtyThings=" LPrintfInt64 "\n", _FL, Thing::DirtyThings.Length()); } #endif return Status; } void ScribeWnd::OnScriptCompileError(const char *Source, Filter *f) { const char *CompileMsg = LLoadString(IDS_ERROR_SCRIPT_COMPILE); LArray Actions; Actions.Add(LLoadString(IDS_SHOW_CONSOLE)); Actions.Add(LLoadString(IDS_OPEN_SOURCE)); Actions.Add(LLoadString(IDS_OK)); if (!d->Bar) { // FYI Capabilities are handled in ScribeWnd::StartAction. d->ErrSource = Source; d->ErrFilter = f; d->Bar = new MissingCapsBar(this, &d->MissingCaps, CompileMsg, this, Actions); AddView(d->Bar, 2); AttachChildren(); OnPosChange(); } } diff --git a/Code/Store3Imap/ScribeImap.h b/Code/Store3Imap/ScribeImap.h --- a/Code/Store3Imap/ScribeImap.h +++ b/Code/Store3Imap/ScribeImap.h @@ -1,583 +1,583 @@ /** * \file * \author Matthew Allen * * http://tools.ietf.org/html/rfc3501 * http://tools.ietf.org/html/rfc4315 * * cmake -G "Visual Studio 14 2015 Win64" -Dprotobuf_BUILD_TESTS=OFF -DCMAKE_INSTALL_PREFIX=../../../../install ../.. */ #ifndef __SCRIBE_IMAP_H__ #define __SCRIBE_IMAP_H__ #include "lgi/common/Mime.h" #include "lgi/common/Mail.h" #include "Store3Common.h" #include "ScribeUtils.h" #define CastFld(obj) (dynamic_cast(obj)) #define CastMail(obj) (dynamic_cast(obj)) #define IMAP_PROTOBUF 0 #if IMAP_PROTOBUF #include "include/Scribe.pb.h" #else // Folder meta sub-element names #define TAG_FOLDER "Folder" #define TAG_MAIL "Mail" #define TAG_EMAILS "Emails" #define TAG_FIELDS "Fields" // Email attributes #define ATTR_UID "Uid" #define ATTR_SIZE "Size" #define ATTR_STRUCT "Structure" #define ATTR_FLAGS "Flags" // Flags sent to the server... #define ATTR_LOCAL "Local" // Flags not supported by IMAP... (not sent to the server) #define ATTR_DATE "Date" #define ATTR_COLOUR "Colour" #define ATTR_MSGID "MsgId" #define ATTR_SUBJECT "Subject" #define ATTR_FROM "From" #define ATTR_REPLYTO "ReplyTo" #define ATTR_LABEL "Label" #endif #define TIMEOUT_STORE_ONPULSE 3000 #define TIMEOUT_LISTING_CHUNK 1000 #define TIMEOUT_FOLDER_LOAD 100 // ms #define IMAP_BLOCK_SIZE 100 // chuck large processes into this many items enum ImapMsgType { IMAP_NULL, // Connection IMAP_ONLINE, IMAP_OFFLINE, IMAP_ERROR, // Lifespan events IMAP_ON_NEW, IMAP_ON_DEL, IMAP_ON_MOVE, // Folder IMAP_SELECT_FOLDER, IMAP_CREATE_FOLDER, IMAP_DELETE_FOLDER, IMAP_EXPUNGE_FOLDER, IMAP_RENAME_FOLDER, IMAP_LOAD_FOLDER, // Mail IMAP_FOLDER_SELECTED, IMAP_FOLDER_LISTING, IMAP_DOWNLOAD, IMAP_APPEND, IMAP_MOVE_EMAIL, // General IMAP_DELETE, IMAP_UNDELETE, IMAP_SET_FLAGS, // Check IMAP_MSG_MAX }; extern const char *ImapMsgTypeNames[IMAP_MSG_MAX]; extern bool ValidateImapDate(LDateTime &dt); /// MAPI folder info struct struct ImapFolderInfo { // The local path name LString Local; // The remote path name LString Remote; // [Optional] Highest known UID uint32_t LastUid; // Separator for IMAP path char Sep; ImapFolderInfo() { LastUid = -1; Sep = 0; } ImapFolderInfo &operator =(ImapFolderInfo &i) { Local = i.Local; Remote = i.Remote; return *this; } }; /// MAPI mail info struct, all of these fields are optional struct ImapMailInfo { // The email meta flags ImapMailFlags Flags; // The sequence number of the email uint32_t Seq; // The server UID of the email uint32_t Uid; // The initial headers LString Headers; // The local path to that .eml file LString Local; // The size of the email in bytes int64 Size; // The structure of the email LString Structure; // The internal date of the email LString Date; }; struct ImapMsg { ImapMsgType Type; // The path of the parent folder if required LString Parent; // A new remote path name (for moving/renaming folder) LString NewRemote; // Folder related... LArray Fld; // Mail related LArray Mail; uint8_t Last : 1; uint8_t New : 1; // Debug const char *File; int Line; int IntParam; LError Error; ImapMsg(ImapMsgType t, const char *file, int line) { Type = t; Last = 0; New = 0; File = file; Line = line; IntParam = 0; } void SetType(ImapMsgType t, const char *file, int line) { Type = t; File = file; Line = line; } }; extern char *TrimWhite(char *s); class ImapFolder; class ImapThread; class ImapMail; class ImapStore : public LDataStoreI { friend class ImapThread; friend class ImapFolder; friend class FolderLoaderThread; LVariant Host, User, Pass; int Port; int ConnectFlags; LDataEventsI *Callback; ImapFolder *Root; char *Cache; int AccountId; ImapThread *Thread; LStream *Log; ScribeAccountletStatusIcon Online; LArray Listing; int64 LastPulse; int64 SelectTime; int64 ListingTime; LArray Msgs; LAutoString ErrorMsg; LAutoPtr FolderLoader; MailProtocolProgress *ItemProgress, *DataProgress; LAutoPtr SettingStore; ImapFolder *GetParent(char *Local, char *Remote); ImapFolder *GetTrash(); ImapFolder *GetSystemFolder(int Type); public: ImapStore( char *host, int port, char *user, char *pass, int flags, LDataEventsI *callback, LCapabilityClient *caps, MailProtocolProgress *prog[2], LStream *log, int accoundid, LAutoPtr store); ~ImapStore(); void PostStore(ImapMsg *m) { Callback->Post(this, m); } bool PostThread(ImapMsg *m, bool UiPriority); void OnNew(const char *File, int Line, LDataFolderI *parent, LArray &new_items, int pos, bool is_new); bool OnDelete(const char *File, int Line, LDataFolderI *parent, LArray &del); bool OnChange(const char *File, int Line, LArray &items, int FieldHint); bool OnIdle(); LStreamI *GetLogger() { return Log; } LDataEventsI *GetEvents() { return Callback; } char *GetCache(); LStream *GetLog() { return Log; } uint64 Size(); LDataI *Create(int Type); LDataFolderI *GetRoot(bool create); Store3Status Move(LDataFolderI *NewFolder, LArray &Items); Store3Status Delete(LArray &Items, bool ToTrash); Store3Status Change(LArray &Items, int PropId, LVariant &Value, LOperator Operator); - bool Compact(LViewI *Parent, LDataPropI *Props); + void Compact(LViewI *Parent, LDataPropI *Props, std::function OnStatus); void OnEvent(void *Param); const char *GetStr(int id); int64 GetInt(int id); Store3Status SetInt(int id, int64 val); LDataPropI *GetObj(int id); }; class ImapThread : public LThread, public LMutex { struct ImapThreadPrivate *d; void Log(LSocketI::SocketMsgType Type, const char *Fmt, ...); public: ImapThread(ImapStore *s, LCapabilityClient *caps, LStream *log, ProtocolSettingStore *Store); ~ImapThread(); void PostThread(ImapMsg *m, bool UiPriority); void PostStore(ImapMsg *m); int Main(); ImapMsg *GetListing(); void FlushListing(bool Force = false); }; class ImapAttachment : public Store3Attachment { LAutoString Name, MimeType, ContentId, Charset; LMime *Seg; void _ClearChildSegs(); /// This is set when the segment is not to be stored on disk. /// When signed and/or encrypted messages are stored, the original /// rfc822 image is maintained by not MIME decoding into separate /// segments but leaving it MIME encoded in one seg (headers and body). /// At runtime the segment is loaded and parsed into a temporary tree /// of LMail3Attachment objects. This flag is set for those temporary /// nodes. bool InMemoryOnly; public: ImapAttachment(ImapStore *store, ImapMail *mail = 0, LMime *seg = 0); ImapAttachment(ImapStore *store, ImapMail *mail, LDataPropI *att); ~ImapAttachment(); void SetInMemoryOnly(bool b); Store3CopyDecl; bool IsOrphan() override { return Mail == 0; } LMime *GetSeg() { return Seg; } const char *GetStr(int id) override; Store3Status SetStr(int id, const char *str) override; int64 GetInt(int id) override; Store3Status SetInt(int id, int64 i) override; uint32_t Type() override; bool IsOnDisk() override; uint64 Size() override; Store3Status Save(LDataI *Folder = 0) override; Store3Status Delete(bool ToTrash = false) override; LAutoStreamI GetStream(const char *file, int line) override; bool SetStream(LAutoStreamI stream) override; void OnSave() override; }; class ImapFolderData; class ImapMail : public LDataI { public: enum ImapMailState { ImapMailIdle, ImapMailGettingBody, ImapMailMoving, ImapMailDeleting }; protected: ImapMailState State; LAutoString TextCache, HtmlCache; LAutoString HeaderCache; LString UidCache; Store3Addr *ProcessAddress(Store3Addr &Addr, const char *FieldId, const char *RfcField); public: const char *AllocFile = NULL; int AllocLine = 0; #if IMAP_PROTOBUF typedef scribe::Email *IMeta; #else typedef LXmlTag *IMeta; #endif ImapStore *Store = NULL; ImapFolderData *Data = NULL; ImapFolder *Parent = NULL; Store3State Loaded = Store3Unloaded; LString Path; uint32_t Uid; // Server UID ImapMailFlags RemoteFlags; int LocalFlags; int Priority; int64 DataSize; uint8_t SegDirty : 1; LString MsgId; LString Subject; LString Label; LString Structure; Store3Addr From; Store3Addr Reply; LDateTime DateReceived; LDateTime DateSent; LColour Colour; DIterator To; LAutoStreamI Stream; ImapAttachment *Seg; ImapMail(ImapStore *store, const char *file = NULL, int line = 0, uint32_t uid = 0); ~ImapMail(); void SetState(ImapMailState s); void SetRemoteFlags(ImapMailFlags f); bool IsOrphan() override { return Parent == 0; } uint32_t Type() override { return MAGIC_MAIL; } bool IsOnDisk() override { return Path.Get() != NULL; } uint64 Size() override { return LFileSize(Path); } LDataStoreI *GetStore() override { return Store; } void Serialize(IMeta m, bool Write); IMeta GetMeta(bool AllowCreate = true); Store3CopyDecl; void Load(); void ReadMime(ImapMail::IMeta MetaTag); Store3Status SetRfc822(LStreamI *m) override; bool FindSegs(const char *Type, LArray &Segs); const char *GetStr(int id) override; Store3Status SetStr(int id, const char *str) override; int64 GetInt(int id) override; Store3Status SetInt(int id, int64 i) override; const LDateTime *GetDate(int id) override; Store3Status SetDate(int id, const LDateTime *i) override; LDataPropI *GetObj(int id) override; Store3Status SetObj(int id, LDataPropI *i) override; GDataIt GetList(int id) override; Store3Status Save(LDataI *Folder = 0) override; LAutoStreamI GetStream(const char *file, int line) override; bool SetStream(LAutoStreamI stream) override; /// This is called to start the deletion process Store3Status Delete(bool ToTrash) override; /// When the thread has deleted the email, this is called to finialize the deletion. bool OnDelete(); void OnDownload(LAutoString &Headers); }; #if IMAP_PROTOBUF #define FOLDER_META_NAME "Folder.proto" #else #define FOLDER_META_NAME "Folder.xml" #define PROP_SORT "Sort" #define PROP_EXPANDED "Expanded" #define PROP_UNREAD "Unread" #define PROP_READ "ReadPerm" #define PROP_WRITE "WritePerm" #define PROP_THREAD "Thread" #endif class ImapFolderFld : public LXmlTag, public LDataPropI { ImapFolder *Parent; public: ImapFolderFld(LDataStoreI *s, ImapFolder *p = 0, int id = 0, int width = 100); const char *GetStr(int id); int64 GetInt(int id); Store3Status SetInt(int id, int64 i); }; class ImapFolderData { friend class ImapStore; protected: // Mail containers, "UidMap" and "Mail" should always mirror each other 1:1 for email // that is not deleted. Deleted email will exist on in "UidMap" till they get expunged. // // The only exception to this is when a new email is being appended to the mailbox, // In that case it'll exist (with a temporary filename and no UID) in the Mail store // for a little while until the thread responds with an IMAP_APPEND msg. LHashTbl,ImapMail::IMeta> UidMap; DIterator Mail; void Swap(ImapFolderData &s) { UidMap.Swap(s.UidMap); Mail.Swap(s.Mail); } public: ImapMail::IMeta GetMeta(uint32_t Uid, bool Create = false); ImapMail *GetMail(uint32_t Uid); bool DeleteMeta(uint32_t Uid); bool AddMail(ImapMail *m, LArray *OnNew); bool DelMail(ImapMail *m); bool HasMail(ImapMail *m) { return Mail.IndexOf(m) >= 0; } virtual void LoadMail() = 0; virtual void SetDirty(bool b = true) = 0; }; class ImapFolder : public LDataFolderI, public ImapFolderData { friend struct ImapFolderLoadThread; int64 LastIdleSelect; bool IsOnline; int SortCache; LString RootName; LAutoPtr WriteThread; ImapFolder *_Parent; // Threaded loading... LAutoPtr LoadThread; #if IMAP_PROTOBUF bool ThreadView; ScribePerm PermRead; ScribePerm PermWrite; int SortField; bool Expanded; int UnreadCount; #else LXmlTag Meta; #endif // Loading of the files and folder int ScanState; LArray Folders; LArray Files; bool Dirty; // Events... LArray> OnLoad; public: ImapStore *Store; Store3SystemFolder System; /// The item type... int ItemType; /// The IMAP path LString Remote; /// The on disk cache folder LString Local; /// Temp storage for leaf name, used before written to disk LString LeafName; /// The sub-folders of this folder DIterator Sub; /// The configured fields in the folder DIterator Field; ImapFolder(ImapStore *store, const char *path = 0); ~ImapFolder(); Store3CopyDecl; bool IsOrphan() override { return _Parent == NULL; } bool IsRoot() { return ItemType == MAGIC_NONE; } uint32_t Type() override { return MAGIC_FOLDER; } bool IsOnDisk() override { return Local && Remote; } LDataStoreI *GetStore() override { return Store; } ImapFolder *Find(const char *Local, const char *Remote); int GetLastUid(); void Swap(ImapFolder &f); ImapFolder *GetParent() { return _Parent; } void SetParent(ImapFolder *p); void LoadMail() override; bool IsLoading() { return LoadThread.Get() != NULL; } void OnLoadMail(bool Threaded); Store3Status WhenLoaded(std::function cb); void LoadSub(bool All = false); void ScanFolder(); Store3Status Move(LArray &Items); LString MakeImapPath(char *From); uint64 Size() override; Store3Status Save(LDataI *Into = 0) override; Store3Status Delete(bool ToTrash = true) override; LAutoStreamI GetStream(const char *file, int line) override; bool SetStream(LAutoStreamI stream) override; const char *GetStr(int id) override; Store3Status SetStr(int id, const char *str) override; int64 GetInt(int id) override; Store3Status SetInt(int id, int64 i) override; const LDateTime *GetDate(int id) override; Store3Status SetDate(int id, const LDateTime *i) override; LDataPropI *GetObj(int id) override; GDataIt GetList(int id) override; LDataIterator &SubFolders() override; LDataIterator &Children() override; LDataIterator &Fields() override; Store3Status FreeChildren() override; Store3Status DeleteAllChildren() override; void SetDirty(bool b = true) override; LString MailPath(uint32_t Uid, bool CheckExists = true); // Mail container stuff ImapMail *FindByFile(char *File); bool Serialize(bool Write); // Events void OnPulse(); void OnDeleteComplete(); void OnListing(ImapMsg *m); void OnSelect(bool b) override; void OnCommand(const char *Name) override; void OnExpunge(); bool OnRename(const char *NewRemote); }; #endif diff --git a/Code/Store3Imap/ScribeImap_Store.cpp b/Code/Store3Imap/ScribeImap_Store.cpp --- a/Code/Store3Imap/ScribeImap_Store.cpp +++ b/Code/Store3Imap/ScribeImap_Store.cpp @@ -1,1310 +1,1311 @@ #include "lgi/common/Lgi.h" #include "lgi/common/NetTools.h" #include "ScribeImap.h" #include "ScribeUtils.h" #if IMAP_PROTOBUF #ifdef _DEBUG #pragma comment(lib, "libprotobufd.lib") #else #pragma comment(lib, "libprotobufd.lib") #endif #endif const char *ImapMsgTypeNames[IMAP_MSG_MAX] = { "IMAP_NULL", // Connection "IMAP_ONLINE", "IMAP_OFFLINE", // Lifespan events "IMAP_ON_NEW", "IMAP_ON_DEL", "IMAP_ON_MOVE", // Folder "IMAP_SELECT_FOLDER", "IMAP_CREATE_FOLDER", "IMAP_DELETE_FOLDER", "IMAP_EXPUNGE_FOLDER", "IMAP_RENAME_FOLDER", // Mail "IMAP_FOLDER_SELECTED", "IMAP_FOLDER_LISTING", "IMAP_DOWNLOAD", "IMAP_APPEND", "IMAP_MOVE_EMAIL", // General "IMAP_DELETE", "IMAP_UNDELETE", "IMAP_SET_FLAGS", }; ////////////////////////////////////////////////////////////////////////////////// char *TrimWhite(char *c) { if (!c) return 0; char *s = c; while (*s && strchr(WhiteSpace, *s)) s++; char *e = s + strlen(s); while (e > s && strchr(WhiteSpace, e[-1])) e--; *e = 0; if (s > c) memmove(c, s, e-s+1); return c; } ////////////////////////////////////////////////////////////////////////////////// ImapStore::ImapStore(char *host, int port, char *user, char *pass, int flags, LDataEventsI *callback, LCapabilityClient *caps, MailProtocolProgress *prog[2], LStream *log, int accountid, LAutoPtr store) { Root = NULL; Cache = 0; Online = STATUS_OFFLINE; LastPulse = 0; ListingTime = SelectTime = 0; AccountId = accountid; Host = host; Port = port; User = user; Pass = pass; ConnectFlags = flags; Log = log; Callback = callback; ItemProgress = prog[0]; DataProgress = prog[1]; SettingStore = store; Thread = new ImapThread(this, caps, log, SettingStore); } ImapStore::~ImapStore() { FolderLoader.Reset(); DeleteObj(Thread); DeleteObj(Root); DeleteArray(Cache); } bool ImapStore::OnIdle() { if (Listing.Length() > 0) { LAutoPtr m(Listing[0]); Listing.DeleteAt(0, true); return Listing.Length() > 0; } int64 Now = LCurrentTime(); if (!LastPulse || (Now - LastPulse) > TIMEOUT_STORE_ONPULSE) { LastPulse = Now; if (Root) Root->OnPulse(); } return false; } const char *ImapStore::GetStr(int id) { switch (id) { case FIELD_ERROR: return ErrorMsg; } return NULL; } int64 ImapStore::GetInt(int id) { switch (id) { case FIELD_STORE_TYPE: return Store3Imap; case FIELD_VERSION: return 10; case FIELD_ACCOUNT_ID: return AccountId; case FIELD_IS_ONLINE: return Online == STATUS_ONLINE; case FIELD_PROFILE_IMAP_LISTING: return ListingTime; case FIELD_PROFILE_IMAP_SELECT: return SelectTime; case FIELD_STORE_STATUS: return Online; } return -1; } Store3Status ImapStore::SetInt(int id, int64 val) { if (id == FIELD_IS_ONLINE && !val) { DeleteObj(Thread); Online = STATUS_OFFLINE; return Store3Success; } return Store3Error; } ImapFolder *ImapStore::GetSystemFolder(int Type) { if (!Root) return NULL; Store3SystemFolder t = Store3SystemNone; switch (Type) { case FOLDER_INBOX: t = Store3SystemInbox; break; case FOLDER_OUTBOX: t = Store3SystemOutbox; break; case FOLDER_SENT: t = Store3SystemSent; break; case FOLDER_TRASH: t = Store3SystemTrash; break; } if (!t) return NULL; for (auto f: Root->Sub.a) { if (f->System == t) return f; } return NULL; } LDataPropI *ImapStore::GetObj(int id) { switch (id) { case FIELD_INBOX: return GetSystemFolder(FOLDER_INBOX); case FIELD_OUTBOX: return GetSystemFolder(FOLDER_OUTBOX); case FIELD_SENT: return GetSystemFolder(FOLDER_SENT); case FIELD_TRASH: return GetSystemFolder(FOLDER_TRASH); } return NULL; } void ImapStore::OnNew(const char *File, int Line, LDataFolderI *parent, LArray &new_items, int pos, bool is_new) { if (!Callback) return; Callback->SetContext(File, Line); Callback->OnNew(parent, new_items, pos, is_new); } bool ImapStore::OnDelete(const char *File, int Line, LDataFolderI *parent, LArray &del) { if (!Callback) return false; Callback->SetContext(File, Line); return Callback->OnDelete(parent, del); } bool ImapStore::OnChange(const char *File, int Line, LArray &items, int FieldHint) { if (!Callback) return false; Callback->SetContext(File, Line); return Callback->OnChange(items, FieldHint); } static bool CheckCreateFolder(char *s, LStream *Log) { if (LDirExists(s)) return true; if (FileDev->CreateFolder(s)) return true; char m[256]; Log->Write(m, sprintf_s(m, sizeof(m), "Can't create '%s'", s), LSocketI::SocketMsgError); return false; } char *ImapStore::GetCache() { if (!Cache) { char s[MAX_PATH_LEN], a[32]; LVariant Portable; LOptionsFile *Opts = Callback->GetOptions(); if (!Opts || !Opts->GetValue(OPT_IsPortableInstall, Portable)) Portable = true; sprintf_s(a, sizeof(a), "%i", AccountId); if (Portable.CastInt32()) LGetSystemPath(LSP_APP_INSTALL, s, sizeof(s)); else LGetSystemPath(LSP_APP_ROOT, s, sizeof(s)); if (!CheckCreateFolder(s, Log)) { LgiTrace("%s:%i - CheckCreateFolder(%s) failed.\n", _FL, s); return NULL; } LMakePath(s, sizeof(s), s, "ImapCache"); if (!CheckCreateFolder(s, Log)) { LgiTrace("%s:%i - CheckCreateFolder(%s) failed.\n", _FL, s); return NULL; } LMakePath(s, sizeof(s), s, a); if (!CheckCreateFolder(s, Log)) { LgiTrace("%s:%i - CheckCreateFolder(%s) failed.\n", _FL, s); return NULL; } Cache = NewStr(s); } return Cache; } uint64 ImapStore::Size() { return -1; } LDataI *ImapStore::Create(int Type) { switch ((uint32_t)Type) { case MAGIC_FOLDER: { return new ImapFolder(this, 0); } case MAGIC_MAIL: { return new ImapMail(this, _FL, 0); } } return 0; } class FolderLoaderThread : public LThread { bool Loop; ImapStore *Store; public: uint64_t StartTs; LAutoPtr Root; FolderLoaderThread(ImapStore *store, const char *base) : LThread("FolderLoaderThread") { Loop = true; Store = store; StartTs = LCurrentTime(); Root.Reset(new ImapFolder(Store, "/")); Run(); } ~FolderLoaderThread() { Loop = false; while (!IsExited()) LSleep(1); } int Main() { Root->LoadSub(true); Store->PostStore(new ImapMsg(IMAP_ONLINE, _FL)); return 0; } }; LDataFolderI *ImapStore::GetRoot(bool create) { if (!GetCache()) return NULL; if (!Root) { Root = new ImapFolder(this, "/"); #if 0 // Start a thread to load the folder tree.. FolderLoader.Reset(new FolderLoaderThread(this, GetCache())); #endif } return Root; } ImapFolder *ImapStore::GetTrash() { ImapFolder *Trash = Root->Find(0, "/Trash"); if (!Trash) Trash = Root->Find(0, "/Deleted Items"); // Should we create a trash folder if it doesn't exist? return Trash; } Store3Status ImapStore::Delete(LArray &Items, bool ToTrash) { if (Items.Length() == 0) return Store3Error; Store3Status Status = Store3Error; LArray Mv, Dl; ImapFolder *Trash = GetTrash(); LAutoPtr DelMsg(new ImapMsg(IMAP_DELETE, _FL)); for (unsigned n=0; n(Items[n]); if (f) { Status = f->Delete(); } else { ImapMail *m = dynamic_cast(Items[n]); if (m) { if (ToTrash && Root) { if (Trash && m->Parent != Trash) { Mv.Add(m); m = NULL; } } if (m) { if (!DelMsg->Parent) DelMsg->Parent = m->Parent->Remote.Get(); // This breaks messages up into blocks of the same parent folder // but no more than IMAP_BLOCK items. if (_stricmp(DelMsg->Parent, m->Parent->Remote) || DelMsg->Mail.Length() >= IMAP_BLOCK_SIZE) { PostThread(DelMsg.Release(), false); DelMsg.Reset(new ImapMsg(IMAP_DELETE, _FL)); DelMsg->Parent = m->Parent->Remote.Get(); Status = Store3Delayed; } Dl.Add(m); m->SetState(ImapMail::ImapMailDeleting); DelMsg->Mail.New().Uid = m->Uid; } } } } if (Trash && Mv.Length()) { Status = Move(Trash, Mv); } if (DelMsg->Mail.Length()) { if (PostThread(DelMsg.Release(), false)) { Status = Store3Delayed; OnChange(_FL, Dl, 0); } } return Status; } Store3Status ImapStore::Move(LDataFolderI *NewFolder, LArray &Items) { Store3Status Status = Store3Error; if (NewFolder) { ImapFolder *To = CastFld(NewFolder); if (To) { Status = To->Move(Items); } else LAssert(!"Not an imap folder."); } return Status; } Store3Status ImapStore::Change(LArray &Items, int PropId, LVariant &Value, LOperator Operator) { switch (PropId) { case FIELD_FLAGS: { uint32_t Val = Value.CastInt32(); LAutoPtr Msg; if (Operator != OpPlusEquals && Operator != OpMinusEquals) { LAssert(!"Operator not supported."); return Store3Error; } for (auto i: Items) { if (!Msg && !Msg.Reset(new ImapMsg(IMAP_SET_FLAGS, _FL))) return Store3Error; ImapMail *m = dynamic_cast(i); if (!m || !m->Parent) continue; auto Fld = m->Parent->Remote; if (Msg->Parent && (Fld != Msg->Parent || Msg->Mail.Length() >= IMAP_BLOCK_SIZE)) { LAssert(Msg->Parent); PostThread(Msg.Release(), false); if (!Msg.Reset(new ImapMsg(IMAP_SET_FLAGS, _FL))) return Store3Error; } if (!Msg->Parent) Msg->Parent = Fld; auto PrevFlags = m->RemoteFlags.All; if (Val & MAIL_READ) m->RemoteFlags.ImapSeen = Operator == OpPlusEquals; else if (Val & MAIL_REPLIED) m->RemoteFlags.ImapAnswered = Operator == OpPlusEquals; else LAssert(!"Unsupported flag."); if (Operator == OpPlusEquals) m->LocalFlags |= Val; else m->LocalFlags &= ~Val; auto t = m->GetMeta(); if (t) { #if IMAP_PROTOBUF t->set_remoteflags(m->RemoteFlags.All); t->set_localflags(m->LocalFlags); #else t->SetAttr(ATTR_FLAGS, m->RemoteFlags.Get()); t->SetAttr(ATTR_LOCAL, m->LocalFlags); #endif m->Parent->SetDirty(); } else LAssert(!"No tag."); if (m->RemoteFlags.All != PrevFlags) { ImapMailInfo &Info = Msg->Mail.New(); Info.Uid = m->Uid; Info.Flags = m->RemoteFlags; } } if (Msg && Msg->Mail.Length() > 0) { LAssert(Msg->Parent); if (!PostThread(Msg.Release(), false)) return Store3Error; } return Store3Delayed; break; } } return Store3Error; } -bool ImapStore::Compact(LViewI *Parent, LDataPropI *Props) +void ImapStore::Compact(LViewI *Parent, LDataPropI *Props, std::function OnStatus) { - return true; + if (OnStatus) + OnStatus(true); } bool ImapStore::PostThread(ImapMsg *m, bool UiPriority) { if (Thread) { Thread->PostThread(m, UiPriority); return true; } // This happens when the imap store is in offline mode. DeleteObj(m); return false; } ImapFolder *ImapStore::GetParent(char *Local, char *Remote) { if (!Root) return 0; LAutoString l(NewStr(Local)), r(NewStr(Remote)); char *s; if (l && (s = strrchr(l, DIR_CHAR))) *s = 0; if (r && (s = strrchr(r, '/'))) *s = 0; return Root->Find(l, r); } void MailInfToObject(LAutoPtr &DownloadMsg, LArray *OnNew, ImapFolder *Parent, ImapMail *o, ImapMailInfo &Inf) { o->Path = Parent->MailPath(Inf.Uid, false); if (Inf.Date) { o->DateSent.Decode(Inf.Date); o->DateSent.ToUtc(); ValidateImapDate(o->DateSent); } // LgiTrace("%s:%i - %p.DateSent = %s\n", _FL, o, Inf.Date.Get()); o->SetRemoteFlags(Inf.Flags); // LgiTrace("MailInfToObject %p -> %s / %s\n", o, Inf.Flags.Get(), EmailFlagsToStr(o->LocalFlags).Get()); o->Parent = Parent; if (o->RemoteFlags.ImapRecent || !LFileExists(o->Path)) { if (!DownloadMsg) { if (DownloadMsg.Reset(new ImapMsg(IMAP_DOWNLOAD, _FL))) DownloadMsg->Parent = Parent->Remote.Get(); } if (DownloadMsg) { ImapMailInfo &Info = DownloadMsg->Mail.New(); Info.Uid = o->Uid; Info.Local = o->Path.Get(); o->SetState(ImapMail::ImapMailGettingBody); } } Parent->WhenLoaded([Parent, o, OnNew](auto Status) { Parent->AddMail(o, Status == Store3Success ? OnNew : NULL); }); } static bool _in_download = false; void ImapStore::OnEvent(void *Param) { if (!Root) return; Msgs.Add((ImapMsg*)Param); while (Msgs.Length()) { int64 Start = LCurrentTime(); ImapMsg *m = Msgs[0]; Msgs.DeleteAt(0, true); switch (m->Type) { default: break; case IMAP_ONLINE: { Online = STATUS_ONLINE; // LProfile p("IMAP_ONLINE"); #if 1 if (Root) Root->LoadSub(); #else if (Root && Root->Sub.GetState() < Store3Loaded) { if (!FolderLoader) { // p.Add("CreateThread"); FolderLoader.Reset(new FolderLoaderThread(this, GetCache())); } else { FolderLoaderThread *t = dynamic_cast(FolderLoader.Get()); if (t && t->Root && t->Root->Sub.GetState() == Store3Loaded) { // p.Add("RootSwap"); Root->Swap(*t->Root); // LgiTrace("FolderLoadTime=" LPrintfInt64 "\n", LCurrentTime() - t->StartTs); } } } #endif // Select the inbox if (Root && Root->Sub.GetState() == Store3Loaded) { // p.Add("Inbox"); // Select the INBOX and start loading it's mail ImapFolder *Inbox = Root->Find(NULL, "/INBOX"); if (Inbox) { // p.Add("Inbox.Select"); Inbox->OnSelect(true); // p.Add("Inbox.Load"); Inbox->LoadMail(); } // p.Add("Callback"); if (Callback) Callback->OnPropChange(this, FIELD_IS_ONLINE, GV_BOOL); } break; } case IMAP_OFFLINE: { Online = STATUS_OFFLINE; if (Callback) Callback->OnPropChange(this, FIELD_IS_ONLINE, GV_BOOL); break; } case IMAP_ERROR: { Online = STATUS_ERROR; break; } case IMAP_ON_NEW: { if (!Root) break; // New folders? LArray NewEvent; for (unsigned i=0; iFld.Length(); i++) { ImapFolderInfo &Inf = m->Fld[i]; ImapFolder *Parent = GetParent(Inf.Local, 0); if (!Parent) { // This happens when there is "Inbox/abc/def" but no // "Inbox/abc". Apparently this is legal for some servers. char sep[] = {Inf.Sep, 0}; LString Sep(sep); LString Unix("/"); LAssert(Inf.Sep != 0); LString::Array a = LString(Inf.Remote).Split(Sep); Parent = Root; for (unsigned i=0; Parent && iFind(NULL, Rem); if (!f) { // Start creating entries... f = new ImapFolder(this); if (!f) break; f->Remote = Rem.Get(); char s[MAX_PATH_LEN]; LMakePath(s, sizeof(s), GetCache(), Rem); f->Local = s; if (!LDirExists(f->Local) && FileDev->CreateFolder(f->Local)) { // Probably should get the server to recreate the folder too? ImapMsg *Create = new ImapMsg(IMAP_CREATE_FOLDER, _FL); if (Create) { ImapFolderInfo &Inf = Create->Fld.New(); Inf.Local = f->Local.Get(); Inf.Remote = f->Remote.Get(); PostThread(Create, false); } } f->SetParent(Parent); NewEvent.Add(f); f->Field.State = Store3Loaded; } if (!f) break; Parent = f; } } if (Parent) { if (!LDirExists(Inf.Local)) { // Create local folder... if (FileDev->CreateFolder(Inf.Local)) { if (Parent->Sub.GetState() != Store3Loaded) { // This will cause the folder to be loaded as if it was pre-existing Parent->LoadSub(); ImapFolder *f = Parent->Find(Inf.Local, NULL); // Get the new folder ptr and pass it to the UI level if (f && Callback) NewEvent.Add(f); } else { LString RemotePath = Parent->MakeImapPath(Inf.Local); ImapFolder *f = new ImapFolder(this, RemotePath); if (f) { f->SetParent(Parent); // LgiTrace("IMAP Store '%s', parent state=%i\n", f->Remote.Get(), Parent->Sub.State); if (Callback) NewEvent.Add(f); } else if (Log) Log->Print("IMAP_ON_NEW: mem alloc failed\n"); } } else { LAssert(!"Failed to create folder."); if (Log) Log->Print("IMAP_ON_NEW: Failed to create folder\n"); } } } } for (unsigned i=0; i Items; Items.Add(NewEvent[i]); ImapFolder *Parent = NewEvent[i]->GetParent(); if (Parent->Sub.State != Store3Loaded) Parent->LoadSub(); Callback->OnNew(Parent, Items, -1, false); } // New email? ImapFolder *Parent = m->Parent ? Root->Find(0, m->Parent) : 0; if (Parent) { LArray Old, Recent; LAutoPtr DownloadMsg; ImapMail *o; for (unsigned i=0; iMail.Length(); i++) { ImapMailInfo &Inf = m->Mail[i]; if (!Inf.Uid) { LAssert(!"No server id."); continue; } bool ParentHasUid = Parent->GetMail(Inf.Uid) != NULL; if (!Inf.Flags.ImapDeleted && !ParentHasUid && (o = new ImapMail(this, _FL, Inf.Uid))) { LArray *a = Inf.Flags.ImapRecent && !Inf.Flags.ImapSeen && Parent->System == Store3SystemInbox ? &Recent : &Old; MailInfToObject(DownloadMsg, a, Parent, o, Inf); } } if (DownloadMsg) { PostThread(DownloadMsg.Release(), false); } if (Callback) { if (Recent.Length()) Callback->OnNew(Parent, Recent, -1, true); if (Old.Length()) Callback->OnNew(Parent, Old, -1, false); } } break; } case IMAP_ON_DEL: { if (!Root) { LAssert(!"No root?"); break; } LgiTrace("IMAP_ON_DEL %i,%i\n", (int)m->Fld.Length(), (int)m->Mail.Length()); // Folders for (unsigned i=0; iFld.Length(); i++) { ImapFolderInfo &Inf = m->Fld[i]; ImapFolder *f = Root->Find(Inf.Local, Inf.Remote); if (f) { LArray Lst; Lst.Add(f); if (Callback->OnDelete(f->GetParent(), Lst)) { f->OnDeleteComplete(); DeleteObj(f); } else LAssert(!"OnDelete failed."); } } // Email ImapFolder *Parent = m->Parent ? Root->Find(0, m->Parent) : 0; if (Parent) { LArray Lst; if (m->Mail.Length() > 0) { for (unsigned i=0; iMail.Length(); i++) { ImapMailInfo &Inf = m->Mail[i]; ImapMail *e = Parent->GetMail(Inf.Uid); if (!e) { LgiTrace("\t%s:%i - Can't find uid '%i' in folder '%s' (msg from %s:%i)\n", _FL, Inf.Uid, m->Parent.Get(), m->File, m->Line); // LAssert(!"No email to delete."); } else if (!Lst.HasItem(e)) { // LgiTrace("%s:%i - Adding %p %s to del lst.\n", _FL, e, e->Uid.Get()); Lst.Add(e); } } } else { for (unsigned i=0; iChildren().Length(); i++) { LDataI *di = Parent->Children()[i]; if (di) Lst.Add(di); } } LgiTrace("\tCallback->OnDelete %i\n", (int)Lst.Length()); if (!Callback || Callback->OnDelete(Parent, Lst)) { for (unsigned i=0; i(Lst[i]); if (e) { #if 0 LgiTrace("\t%s:%i - OnDelete '%s' from folder '%s' (msg from %s:%i)\n", _FL, e->Uid.Get(), Parent->Remote.Get(), m->File, m->Line); #endif LAssert(!_in_download); e->OnDelete(); DeleteObj(e); } } } } // LgiTrace("Ending IMAP_ON_DEL\n"); break; } case IMAP_FOLDER_LISTING: { if (m->Fld.Length() != 1) { LAssert(!"Not the right folder length."); break; } ImapFolder *f = Root->Find(0, m->Fld[0].Remote); if (f) f->OnListing(m); else LAssert(!"No folder"); break; } case IMAP_DOWNLOAD: { ImapFolder *f = Root->Find(0, m->Parent); if (f) { _in_download = true; LArray Changed; for (unsigned i=0; iMail.Length(); i++) { ImapMailInfo &Inf = m->Mail[i]; ImapMail *e = f->GetMail(Inf.Uid); if (e) { e->SetState(ImapMail::ImapMailIdle); auto t = e->GetMeta(); if (t) { e->ReadMime(t); Changed.Add(e); } else { f->Save(); LgiTrace("Error: Can't find meta data for email %s\n", strrchr(e->Path, DIR_CHAR)); LAssert(!"No meta tag for this email?"); } } else { // That can happen if you delete something before the IMAP_DOWNLOAD msg // gets back from the worker thread. } } _in_download = false; f->SetDirty(); if (Changed.Length()) OnChange(_FL, Changed, 0); } else LAssert(!"No folder"); break; } case IMAP_CREATE_FOLDER: { if (Callback && Root) { for (unsigned i=0; iFld.Length(); i++) { ImapFolderInfo &Inf = m->Fld[i]; ImapFolder *f = Root->Find(Inf.Local, Inf.Remote); if (f) { LArray New; New.Add(f); Callback->OnNew(f->GetParent(), New, -1, false); } } } break; } case IMAP_APPEND: { LDataFolderI *NewItemParent = 0; LArray NewItems; ImapFolder *f = Root->Find(0, m->Parent); if (f) { for (unsigned i=0; iMail.Length(); i++) { ImapMailInfo &Inf = m->Mail[i]; ImapMail *m = f->FindByFile(Inf.Local); if (m) { if (Inf.Uid) { // Ok rename the mail object, setup it's new UID m->Uid = Inf.Uid; LString Path = f->MailPath(m->Uid); if (FileDev->Move(m->Path, Path)) { m->Path = Path; } // Create the meta data tag and hook it up auto t = f->GetMeta(m->Uid, true); if (t) { LAssert(m->GetMeta() != NULL); m->Serialize(t, true); if (NewItemParent != NULL && NewItemParent != m->Parent) { // If items from different parents come through we should OnNew them // only when the parent is the same, so flush through ones that have // the old parent folder before we start collecting ones from the new // folder.. if (Callback && NewItems.Length() > 0) Callback->OnNew(NewItemParent, NewItems, -1, false); NewItems.Length(0); } NewItemParent = m->Parent; NewItems.Add(m); if (!m->Seg) { auto Meta = m->GetMeta(); m->Loaded = Store3Unloaded; m->ReadMime(Meta); } } } else { // No uid, just destroy it, we'll get it eventually through the // \recent mail search FileDev->Delete(m->Path, false); m->Path.Empty(); } } } } if (Callback && NewItems.Length()) { // Notify the client the object has arrived. Callback->OnNew(NewItemParent, NewItems, -1, false); } break; } case IMAP_EXPUNGE_FOLDER: { ImapFolder *f = Root->Find(0, m->Parent); if (f) f->OnExpunge(); break; } case IMAP_RENAME_FOLDER: { LArray Renamed; ImapFolder *f = Root->Find(0, m->Parent); if (f && f->OnRename(m->NewRemote)) Renamed.Add(f); OnChange(_FL, Renamed, FIELD_FOLDER_NAME); break; } case IMAP_SET_FLAGS: { LArray Change, Delete, OnNew; ImapFolder *f = Root->Find(0, m->Parent ); if (!f) { LgiTrace("%s:%i - Missing folder '%s'\n", _FL, m->Parent.Get()); break; } LAutoPtr DownloadMsg; #define USE_BINARY_SEARCH 1 // LProfile prof("IMAP_SET_FLAGS"); #if USE_BINARY_SEARCH // Sort the list for BinarySearch //prof.Add("sort"); f->Mail.a.Sort([](auto a, auto b) { return (int32_t)(*a)->Uid - (int32_t)(*b)->Uid; }); #endif // prof.Add("process"); for (auto &e: m->Mail) { if (e.Uid) { #if USE_BINARY_SEARCH ImapMail *Email = f->Mail.a.BinarySearch([Uid=e.Uid](auto a) { return (int32_t)a->Uid - (int32_t)Uid; }); #else // On large folders this is sooooo slow... ImapMail *Email = f->GetMail(e.Uid); #endif // This is causing deleted email to reappear?? if (!Email && !e.Flags.ImapDeleted) { // Existing email that we don't know about? // Ok, well add new entry to track it. ImapMail *NewMail = new ImapMail(this, _FL, e.Uid); if (NewMail) MailInfToObject(DownloadMsg, &OnNew, f, NewMail, e); } if (Email) { if (Email->RemoteFlags.ImapDeleted ^ e.Flags.ImapDeleted) { if (e.Flags.ImapDeleted) Delete.Add(Email); // LAssert(!"What now?"); } else { Change.Add(Email); } Email->SetRemoteFlags(e.Flags); } } else { LgiTrace("%s:%i - No UID: seq=%i\n", _FL, e.Seq); } } if (OnNew.Length()) { f->SetDirty(); Callback->OnNew(f, OnNew, -1, true); } if (Change.Length()) { f->SetDirty(); OnChange(_FL, Change, FIELD_FLAGS); } else if (Delete.Length()) { f->SetDirty(); Callback->OnDelete(f, Delete); } if (DownloadMsg) { PostThread(DownloadMsg.Release(), false); } break; } case IMAP_LOAD_FOLDER: { // This event comes from the delayed ImapFolder::LoadMail thread... NOT the imap worker thread. if (!m->Fld.Length()) { LAssert(!"Must have 1 entry."); break; } ImapFolder *f = Root->Find(m->Fld[0].Local, NULL); if (!f) { LAssert(!"Not an IMAP folder."); break; } f->OnLoadMail(true); break; } } int Time = (int) (LCurrentTime() - Start); if (Time > 1000) { LgiTrace("%s:%i - ImapStore::OnEvent took %ims processing msg %s (len=%i)\n", _FL, Time, m && m->Type < IMAP_MSG_MAX ? ImapMsgTypeNames[m->Type] : "", m ? m->Mail.Length() : 0); } DeleteObj(m); } } //////////////////////////////////////////////////////////////////////////////////////////////// bool ValidateImapDate(LDateTime &dt) { if (dt.Year() == 2000 && dt.Hours() == 0) { LAssert(!"Invalid IMAP date."); dt.Empty(); return false; } if (dt.GetTimeZone() != 0) { LgiTrace("%s:%i - Should be a UTC date.\n", _FL); return false; } return true; } LDataStoreI *OpenImap( char *Host, int Port, char *User, char *Pass, int ConnectFlags, LDataEventsI *Callback, LCapabilityClient *Caps, MailProtocolProgress *prog[2], LStream *Log, int AccoundId, LAutoPtr store) { return new ImapStore(Host, Port, User, Pass, ConnectFlags, Callback, Caps, prog, Log, AccoundId, store); } diff --git a/Code/Store3Mail3/Mail3.h b/Code/Store3Mail3/Mail3.h --- a/Code/Store3Mail3/Mail3.h +++ b/Code/Store3Mail3/Mail3.h @@ -1,754 +1,754 @@ #ifndef _MAIL3_H_ #define _MAIL3_H_ #include "lgi/common/Lgi.h" #include "Store3Common.h" #include "v3.6.14/sqlite3.h" // Debugging stuff #define MAIL3_TRACK_OBJS 0 #define MAIL3_DB_FILE "Database.sqlite" #define MAIL3_TBL_FOLDER "Folder" #define MAIL3_TBL_FOLDER_FLDS "FolderFields" #define MAIL3_TBL_MAIL "Mail" #define MAIL3_TBL_MAILSEGS "MailSegs" #define MAIL3_TBL_CONTACT "Contact" #define MAIL3_TBL_GROUP "ContactGroup" #define MAIL3_TBL_FILTER "Filter" #define MAIL3_TBL_CALENDAR "Calendar" class LMail3Store; class LMail3Mail; enum Mail3SubFormat { Mail3v1, // All the mail and segs in a single tables. Mail3v2, // All the mail and segs in per folders tables. }; struct GMail3Def { const char *Name; const char *Type; }; struct GMail3Idx { const char *IdxName; const char *Table; const char *Column; }; class Mail3BlobStream : public LStream { LMail3Store *Store; sqlite3_blob *b; int64 Pos, Size; int SegId; bool WriteAccess; bool OpenBlob(); bool CloseBlob(); public: const char *File; int Line; Mail3BlobStream(LMail3Store *Store, int segId, int size, const char *file, int line, bool Write = false); ~Mail3BlobStream(); int64 GetPos(); int64 SetPos(int64 p); int64 GetSize(); int64 SetSize(int64 sz); ssize_t Read(void *Buf, ssize_t Len, int Flags = 0); ssize_t Write(const void *Buf, ssize_t Len, int Flags = 0); }; extern GMail3Def TblFolder[]; extern GMail3Def TblFolderFlds[]; extern GMail3Def TblMail[]; extern GMail3Def TblMailSegs[]; extern GMail3Def TblContact[]; extern GMail3Def TblFilter[]; extern GMail3Def TblGroup[]; extern GMail3Def TblCalendar[]; #define SERIALIZE_STR(Var, Col) \ if (Write) \ { \ if (!s.SetStr(Col, Var.Str())) \ return false; \ } \ else \ Var = s.GetStr(Col); #define SERIALIZE_DATE(Var, Col) \ if (Write) \ { \ if (Var.IsValid()) Var.ToUtc(); \ if (!s.SetDate(Col, Var)) \ return false; \ } \ else \ { \ int Fmt = Var.GetFormat(); \ Var.SetFormat(GDTF_YEAR_MONTH_DAY|GDTF_24HOUR); \ Var.Set(s.GetStr(Col)); \ Var.SetFormat(Fmt); \ Var.SetTimeZone(0, false); \ } #define SERIALIZE_BOOL(Var, Col) \ if (Write) \ { \ if (!s.SetInt(Col, Var)) \ return false; \ } \ else \ Var = s.GetBool(Col); #define SERIALIZE_INT(Var, Col) \ if (Write) \ { \ if (!s.SetInt(Col, Var)) \ return false; \ } \ else \ Var = s.GetInt(Col); #define SERIALIZE_INT64(Var, Col) \ if (Write) \ { \ if (!s.SetInt64(Col, Var)) \ return false; \ } \ else \ Var = s.GetInt64(Col); #define SERIALIZE_COLOUR(Colour, Col) \ if (Write) \ { \ int64_t c = Colour.IsValid() ? Colour.c32() : 0; \ if (!s.SetInt64(Col, c)) \ return false; \ } \ else \ { \ int64_t c = s.GetInt64(Col); \ if (c > 0) Colour.c32((uint32_t)c); \ else Colour.Empty(); \ } #define SERIALIZE_AUTOSTR(var, Col) \ { \ if (Write) \ { \ if (!s.SetStr(Col, var)) \ return false; \ } \ else \ { \ var.Reset(NewStr(s.GetStr(Col))); \ } \ } #define SERIALIZE_GSTR(var, Col) \ { \ if (Write) \ { \ if (!s.SetStr(Col, var)) \ return false; \ } \ else \ { \ var = s.GetStr(Col); \ } \ } class LMail3Store : public LDataStoreI { friend class LMail3Obj; friend class LMail3Mail; friend class Mail3Trans; friend class CompactThread; LDataEventsI *Callback; sqlite3 *Db; LString Folder; LString DbFile; class LMail3Folder *Root; LHashTbl, GMail3Def*> Fields; LHashTbl, GMail3Idx*> Indexes; Store3Status OpenStatus; LHashTbl, Store3Status> TableStatus; LString ErrorMsg; LString StatusMsg; LString TempPath; struct TableDefn : LArray { LString::Array t; }; bool ParseTableFormat(const char *Name, TableDefn &Defs); Store3Status CheckTable(const char *Name, GMail3Def *Flds); bool UpdateTable(const char *Name, GMail3Def *Flds, Store3Status Check); bool DeleteMailById(int64 Id); bool OpenDb(); bool CloseDb(); LMail3Folder *GetSystemFolder(int Type); public: Mail3SubFormat Format; class LStatement { struct PostBlob { int64 Size; LVariant ColName; LStreamI *Data; }; LArray Post; protected: LMail3Store *Store; sqlite3_stmt *s; LVariant Table; LAutoString TempSql; public: LStatement(LMail3Store *store, const char *sql = 0); virtual ~LStatement(); operator sqlite3_stmt *() { return s; } bool IsOk() { return #ifndef __llvm__ this != 0 && #endif Store != 0 && s != 0; } bool Prepare(const char *Sql); bool Row(); bool Exec(); bool Reset(); bool Finalize(); int64 LastInsertId(); int GetSize(int Col); bool GetBool(int Col); int GetInt(int Col); bool SetInt(int Col, int n); int64 GetInt64(int Col); bool SetInt64(int Col, int64 n); char *GetStr(int Col); bool GetBinary(int Col, LVariant *v); bool SetStr(int Col, char *s); bool SetDate(int Col, LDateTime &d); bool SetStream(int Col, const char *ColName, LStreamI *s); bool SetBinary(int Col, const char *ColName, LVariant *v); virtual int64 GetRowId() { LAssert(0); return -1; } }; class LInsert : public LStatement { public: LInsert(LMail3Store *store, const char *Tbl); int64 GetRowId() { return LastInsertId(); } }; class LUpdate : public LStatement { int64 RowId; public: LUpdate(LMail3Store *store, const char *Tbl, int64 rowId, char *ExcludeField = 0); int64 GetRowId() { return RowId; } }; class LTransaction { LMail3Store *Store; bool Open; public: LTransaction(LMail3Store *store); ~LTransaction(); bool RollBack(); }; #if MAIL3_TRACK_OBJS struct SqliteObjs { LStatement *Stat; Mail3BlobStream *Stream; SqliteObjs() { Stat = 0; Stream = 0; } }; LArray All; template bool RemoveFromAll(T *p) { for (int i=0; i &Items); Store3Status Delete(LArray &Items, bool ToTrash); Store3Status Change(LArray &Items, int PropId, LVariant &Value, LOperator Operator); - bool Compact(LViewI *Parent, LDataPropI *Props); + void Compact(LViewI *Parent, LDataPropI *Props, std::function OnStatus); bool Upgrade(LViewI *Parent, LDataPropI *Props); bool Repair(LViewI *Parent, LDataPropI *Props); bool SetFormat(LViewI *Parent, LDataPropI *Props); void OnEvent(void *Param); bool Check(int Code, const char *Sql); GMail3Def *GetFields(const char *t) { return Fields.Find(t); } StoreTrans StartTransaction(); // LDataEventsI wrappers void OnNew(const char *File, int Line, LDataFolderI *parent, LArray &new_items, int pos, bool is_new); bool OnMove(const char *File, int Line, LDataFolderI *new_parent, LDataFolderI *old_parent, LArray &items); bool OnChange(const char *File, int Line, LArray &items, int FieldHint); bool OnDelete(const char *File, int Line, LDataFolderI *parent, LArray &items); private: class Mail3Trans : public LDataStoreI::LDsTransaction { Mail3Trans **Ptr; LTransaction Trans; public: Mail3Trans(LMail3Store *s, Mail3Trans **ptr); ~Mail3Trans(); } *Transaction; }; class LMail3Obj { protected: bool Check(int r, char *sql); public: LMail3Store *Store; int64 Id; int64 ParentId; LMail3Obj(LMail3Store *store) { Id = ParentId = -1; Store = store; } bool Write(const char *Table, bool Insert); virtual const char *GetClass() { return "LMail3Obj"; } virtual bool Serialize(LMail3Store::LStatement &s, bool Write) = 0; virtual void SetStore(LMail3Store *s) { Store = s; } }; class LMail3Thing : public LDataI, public LMail3Obj { friend class LMail3Store; LMail3Thing &operator =(LMail3Thing &p) = delete; protected: bool NewMail = false; virtual const char *GetTable() { LAssert(0); return 0; } virtual void OnSave() {}; public: LMail3Folder *Parent = NULL; LMail3Thing(LMail3Store *store) : LMail3Obj(store) { } ~LMail3Thing(); const char *GetClass() { return "LMail3Thing"; } bool IsOnDisk() { return Id > 0; } bool IsOrphan() { return false; } uint64 Size() { return sizeof(*this); } uint32_t Type() { LAssert(0); return 0; } Store3Status Delete(bool ToTrash); LDataStoreI *GetStore() { return Store; } LAutoStreamI GetStream(const char *file, int line) { LAssert(0); return LAutoStreamI(0); } bool Serialize(LMail3Store::LStatement &s, bool Write) { LAssert(0); return false; } Store3Status Save(LDataI *Folder = 0); virtual bool DbDelete() { LAssert(0); return false; } }; class LMail3Folder : public LDataFolderI, public LMail3Obj { public: LVariant Name; int Unread; int Open; int ItemType; int Sort; // Which field to sort contents on int Threaded; int SiblingIndex; // The index of this folder when sorting amongst other sibling folders union { int AccessPerms; struct { int16_t ReadPerm; int16_t WritePerm; }; }; Store3SystemFolder System; LMail3Folder *Parent; DIterator Sub; DIterator Items; DIterator Flds; LMail3Folder(LMail3Store *store); ~LMail3Folder(); Store3CopyDecl; bool Serialize(LMail3Store::LStatement &s, bool Write) override; const char *GetClass() override { return "LMail3Folder"; } uint32_t Type() override; bool IsOnDisk() override; bool IsOrphan() override; uint64 Size() override; Store3Status Save(LDataI *Folder) override; Store3Status Delete(bool ToTrash) override; LDataStoreI *GetStore() override; LAutoStreamI GetStream(const char *file, int line) override; bool SetStream(LAutoStreamI stream) override; LDataIterator &SubFolders() override; LDataIterator &Children() override; LDataIterator &Fields() override; Store3Status DeleteAllChildren() override; Store3Status FreeChildren() override; LMail3Folder *FindSub(char *Name); bool DbDelete(); bool GenSizes(); const char *GetStr(int id) override; Store3Status SetStr(int id, const char *str) override; int64 GetInt(int id) override; Store3Status SetInt(int id, int64 i) override; }; class LMail3Attachment : public Store3Attachment { int64 SegId; int64 BlobSize; LAutoString Headers; LString Name; LString MimeType; LString ContentId; LString Charset; /// This is set when the segment is not to be stored on disk. /// When signed and/or encrypted messages are stored, the original /// rfc822 image is maintained by not MIME decoding into separate /// segments but leaving it MIME encoded in one seg (headers and body). /// At runtime the segment is loaded and parsed into a temporary tree /// of LMail3Attachment objects. This flag is set for those temporary /// nodes. bool InMemoryOnly; public: LMail3Attachment(LMail3Store *store); ~LMail3Attachment(); void SetInMemoryOnly(bool b); int64 GetId() { return SegId; } LMail3Attachment *Find(int64 Id); bool Load(LMail3Store::LStatement &s, int64 &ParentId); bool ParseHeaders() override; char *GetHeaders(); bool HasHeaders() { return ValidStr(Headers); } Store3CopyDecl; const char *GetStr(int id) override; Store3Status SetStr(int id, const char *str) override; int64 GetInt(int id) override; Store3Status SetInt(int id, int64 i) override; Store3Status Delete(bool ToTrash = false) override; LAutoStreamI GetStream(const char *file, int line) override; bool SetStream(LAutoStreamI stream) override; uint32_t Type() override { return MAGIC_ATTACHMENT; } bool IsOnDisk() override { return SegId > 0; } bool IsOrphan() override { return false; } uint64 Size() override; uint64 SizeChildren(); Store3Status Save(LDataI *Folder = 0) override; void OnSave() override; }; class LMail3Mail : public LMail3Thing { LAutoString TextCache; LAutoString HtmlCache; LString IdCache; LAutoPtr SizeCache; LString InferredCharset; void LoadSegs(); void OnSave() override; const char *GetTable() override { return MAIL3_TBL_MAIL; } void ParseAddresses(char *Str, int CC); const char *InferCharset(); bool Utf8Check(LVariant &v); bool Utf8Check(LAutoString &v); public: int Priority = MAIL_PRIORITY_NORMAL; int Flags = 0; int AccountId = 0; int64 MailSize = 0; uint32_t MarkColour = Rgba32(0, 0, 0, 0); // FIELD_MARK_COLOUR, 32bit rgba colour LVariant Subject; DIterator To; LMail3Attachment *Seg = NULL; Store3Addr From; Store3Addr Reply; LVariant Label; LVariant MessageID; LVariant References; LVariant FwdMsgId; LVariant BounceMsgId; LVariant ServerUid; LDateTime DateReceived; LDateTime DateSent; LMail3Mail(LMail3Store *store); ~LMail3Mail(); Store3CopyDecl; void SetStore(LMail3Store *s) override; bool Serialize(LMail3Store::LStatement &s, bool Write) override; const char *GetClass() override { return "LMail3Mail"; } LMail3Attachment *GetAttachment(int64 Id); bool FindSegs(const char *MimeType, LArray &Segs, bool Create = false); int GetAttachments(LArray *Lst = 0); bool ParseHeaders() override; void ResetCaches(); uint32_t Type() override; uint64 Size() override; bool DbDelete() override; LDataStoreI *GetStore() override; LAutoStreamI GetStream(const char *file, int line) override; bool SetStream(LAutoStreamI stream) override; const char *GetStr(int id) override; Store3Status SetStr(int id, const char *str) override; int64 GetInt(int id) override; Store3Status SetInt(int id, int64 i) override; const LDateTime *GetDate(int id) override; Store3Status SetDate(int id, const LDateTime *t) override; LDataPropI *GetObj(int id) override; Store3Status SetObj(int id, LDataPropI *i) override; GDataIt GetList(int id) override; Store3Status SetRfc822(LStreamI *m) override; }; class LMail3Contact : public LMail3Thing { const char *GetTable() override { return MAIL3_TBL_CONTACT; } LHashTbl, LString*> f; public: LVariant Image; LDateTime DateMod; LMail3Contact(LMail3Store *store); ~LMail3Contact(); uint32_t Type() override { return MAGIC_CONTACT; } bool Serialize(LMail3Store::LStatement &s, bool Write) override; Store3CopyDecl; const char *GetClass() override { return "LMail3Contact"; } bool DbDelete() override; const char *GetStr(int id) override; Store3Status SetStr(int id, const char *str) override; int64 GetInt(int id) override { return -1; } LVariant *GetVar(int id) override; Store3Status SetVar(int id, LVariant *i) override; const LDateTime *GetDate(int id) override; Store3Status SetDate(int id, const LDateTime *i) override; }; class LMail3Group : public LMail3Thing { const char *GetTable() override { return MAIL3_TBL_GROUP; } LString Name; LString Group; LDateTime DateMod; public: LMail3Group(LMail3Store *store); ~LMail3Group(); uint32_t Type() override { return MAGIC_GROUP; } bool Serialize(LMail3Store::LStatement &s, bool Write) override; Store3CopyDecl; const char *GetClass() override { return "LMail3Group"; } bool DbDelete() override; const char *GetStr(int id) override; Store3Status SetStr(int id, const char *str) override; int64 GetInt(int id) override { return -1; } const LDateTime *GetDate(int id) override; Store3Status SetDate(int id, const LDateTime *i) override; }; class LMail3Filter : public LMail3Thing { int Index; int StopFiltering; int Direction; LAutoString Name; LAutoString ConditionsXml; LAutoString ActionsXml; LAutoString Script; const char *GetTable() override { return MAIL3_TBL_FILTER; } public: LMail3Filter(LMail3Store *store); ~LMail3Filter(); uint32_t Type() override { return MAGIC_FILTER; } LDataStoreI *GetStore() override { return Store; } bool Serialize(LMail3Store::LStatement &s, bool Write) override; Store3CopyDecl; const char *GetClass() override { return "LMail3Filter"; } bool DbDelete() override; const char *GetStr(int id) override; Store3Status SetStr(int id, const char *str) override; int64 GetInt(int id) override; Store3Status SetInt(int Col, int64 n) override; }; class LMail3Calendar : public LMail3Thing { LString ToCache; private: int CalType; // FIELD_CAL_TYPE LString To; // FIELD_TO CalendarPrivacyType CalPriv; // FIELD_CAL_PRIVACY int Completed; // FIELD_CAL_COMPLETED LDateTime Start; // FIELD_CAL_START_UTC LDateTime End; // FIELD_CAL_END_UTC LString TimeZone; // FIELD_CAL_TIMEZONE LString Subject; // FIELD_CAL_SUBJECT LString Location; // FIELD_CAL_LOCATION LString Uid; // FIELD_UID bool AllDay; // FIELD_CAL_ALL_DAY int64 StoreStatus; // FIELD_STATUS - ie the current Store3Status LString EventStatus; // FIELD_CAL_STATUS LString Reminders; // FIELD_CAL_REMINDERS LDateTime LastCheck; // FIELD_CAL_LAST_CHECK int ShowTimeAs; // FIELD_CAL_SHOW_TIME_AS int Recur; // FIELD_CAL_RECUR int RecurFreq; // FIELD_CAL_RECUR_FREQ int RecurInterval; // FIELD_CAL_RECUR_INTERVAL LDateTime RecurEnd; // FIELD_CAL_RECUR_END_DATE int RecurCount; // FIELD_CAL_RECUR_END_COUNT int RecurEndType; // FIELD_CAL_RECUR_END_TYPE LString RecurPos; // FIELD_CAL_RECUR_FILTER_POS int FilterDays; // FIELD_CAL_RECUR_FILTER_DAYS int FilterMonths; // FIELD_CAL_RECUR_FILTER_MONTHS LString FilterYears; // FIELD_CAL_RECUR_FILTER_YEARS LString Notes; // FIELD_CAL_NOTES LColour Colour; const char *GetTable() override { return MAIL3_TBL_CALENDAR; } public: LMail3Calendar(LMail3Store *store); ~LMail3Calendar(); uint32_t Type() override { return MAGIC_CALENDAR; } LDataStoreI *GetStore() override { return Store; } bool Serialize(LMail3Store::LStatement &s, bool Write) override; Store3CopyDecl; const char *GetClass() override { return "LMail3Filter"; } bool DbDelete() override; const char *GetStr(int id) override; Store3Status SetStr(int id, const char *str) override; int64 GetInt(int id) override; Store3Status SetInt(int Col, int64 n) override; const LDateTime *GetDate(int id) override; Store3Status SetDate(int id, const LDateTime *t) override; }; #endif diff --git a/Code/Store3Mail3/Mail3Store.cpp b/Code/Store3Mail3/Mail3Store.cpp --- a/Code/Store3Mail3/Mail3Store.cpp +++ b/Code/Store3Mail3/Mail3Store.cpp @@ -1,2205 +1,2174 @@ #include "Mail3.h" #include "resdefs.h" #include "lgi/common/SubProcess.h" #include #include "lgi/common/LgiRes.h" #include "lgi/common/Thread.h" #include "lgi/common/Store3MimeTree.h" #define SanityCheck() if (!IsOk()) return NULL; /////////////////////////////////////////////////////////////////////////////////// LMail3Thing::~LMail3Thing() { if (Parent) Parent->Items.a.Delete(this); } Store3Status LMail3Thing::Delete(bool ToTrash) { LArray Del; Del.Add(this); return Store->Delete(Del, ToTrash); } Store3Status LMail3Thing::Save(LDataI *Folder) { LMail3Folder *Fld = dynamic_cast(Folder); if (Folder && !Fld) { LAssert(!"Not the right folder type"); LgiTrace("%s:%i - Not the right folder type.\n", _FL); return Store3Error; } if (Fld && Fld->Store != Store) { SetStore(Fld->Store); } const char *Table = GetTable(); bool Create = false; bool IsNewMail = Type() == MAGIC_MAIL && TestFlag(GetInt(FIELD_FLAGS), MAIL_NEW); LArray NewItems; if (Id < 0) { if (Fld) { // Make sure the folder is loaded... Fld->Children(); Parent = Fld; ParentId = Fld->Id; LAssert(Parent->Items.IndexOf(this, true) < 0); // LgiTrace("%s:%i - Saving %i to %s (%i items)\n", _FL, (int)Id, Parent->GetStr(FIELD_FOLDER_NAME), Parent->Items.Length()); Parent->Items.Insert(this, -1, true); Create = true; NewItems.Add(this); if (IsNewMail) SetInt(FIELD_FLAGS, GetInt(FIELD_FLAGS) & ~MAIL_NEW); } else { LAssert(!"No parent to save to."); LgiTrace("%s:%i - No parent to save to.\n", _FL); return Store3Error; } } else if (Fld) { // Make sure the folder is loaded... Fld->Children(); ParentId = Fld->Id; if (Parent != Fld) { LDataFolderI *Old = Parent; if (Parent) { // Remove this item from the current parent folder... LAssert(Parent->Items.IndexOf(this) >= 0); Parent->Items.Delete(this); } // Add it to the new one... Parent = Fld; LAssert(Parent->Items.IndexOf(this, true) < 0); // LgiTrace("%s:%i - Saving %i to %s (%i items)\n", _FL, (int)Id, Parent->GetStr(FIELD_FOLDER_NAME), Parent->Items.Length()); Parent->Items.Insert(this, -1, true); LArray a; a.Add(this); if (Old) Store->OnMove(_FL, Parent, Old, a); else NewItems.Add(this); if (IsNewMail) SetInt(FIELD_FLAGS, GetInt(FIELD_FLAGS) & ~MAIL_NEW); } } bool Status = Write(Table, Create); if (Status) { OnSave(); if (NewItems.Length()) Store->OnNew(_FL, Parent, NewItems, -1, IsNewMail); } else { LgiTrace("%s:%i - Write failed.\n", _FL); } return Status ? Store3Success : Store3Error; } /////////////////////////////////////////////////////////////////////////////////// GMail3Idx Mail3Indexes[] = { {"MailFolderIdx", MAIL3_TBL_MAIL, "ParentId"}, {"MailSegMailIdx", MAIL3_TBL_MAILSEGS, "MailId"}, {"MailSegParentIdx", MAIL3_TBL_MAILSEGS, "ParentId"}, }; LMail3Store::LMail3Store(const char *Mail3Folder, LDataEventsI *callback, bool Create) : TableStatus(0, Store3Error) { Format = Mail3v1; Folder = Mail3Folder; Callback = callback; Db = 0; Transaction = 0; Root = 0; OpenStatus = Store3Success; Fields.Add(MAIL3_TBL_FOLDER, TblFolder); Fields.Add(MAIL3_TBL_FOLDER_FLDS, TblFolderFlds); Fields.Add(MAIL3_TBL_MAIL, TblMail); Fields.Add(MAIL3_TBL_MAILSEGS, TblMailSegs); Fields.Add(MAIL3_TBL_CONTACT, TblContact); Fields.Add(MAIL3_TBL_GROUP, TblGroup); Fields.Add(MAIL3_TBL_FILTER, TblFilter); Fields.Add(MAIL3_TBL_CALENDAR, TblCalendar); bool Exist = LDirExists(Mail3Folder); if (!Create && !Exist) { ErrorMsg.Printf(LLoadString(IDS_ERROR_FILE_DOESNT_EXIST), Mail3Folder); } else if (!Exist && !FileDev->CreateFolder(Mail3Folder)) { ErrorMsg.Printf(LLoadString(IDS_ERROR_CANT_CREATE_FOLDER), Mail3Folder); } else { OpenDb(); } #if _DEBUG LMail3Mail m(this); Store3MimeTree Tree(&m, m.Seg); if (!Tree.UnitTests(this, &m)) { LgiTrace("%s:%i - Error: Store3MimeTree unit tests failed\n", _FL); LAssert(!"Store3MimeTree unit tests failed.\n"); } #endif } LMail3Store::~LMail3Store() { CloseDb(); DeleteObj(Root); } LMail3Folder *LMail3Store::GetSystemFolder(int Type) { if (!Callback || !Root) return NULL; LVariant Path; if (Callback->GetSystemPath(FOLDER_INBOX, Path)) { auto p = LString(Path.Str()).Split("/"); for (auto f: Root->Sub.a) { auto Nm = f->GetStr(FIELD_FOLDER_NAME); if (p.Last().Equals(Nm)) { return f; } } } return NULL; } LDataPropI *LMail3Store::GetObj(int id) { switch (id) { case FIELD_INBOX: return GetSystemFolder(FOLDER_INBOX); case FIELD_OUTBOX: return GetSystemFolder(FOLDER_OUTBOX); case FIELD_SENT: return GetSystemFolder(FOLDER_SENT); case FIELD_TRASH: return GetSystemFolder(FOLDER_TRASH); } return NULL; } bool LMail3Store::OpenDb() { char f[MAX_PATH_LEN]; StatusMsg.Empty(); LMakePath(f, sizeof(f), Folder, MAIL3_DB_FILE); DbFile = f; if (!Check(sqlite3_open(f, &Db), 0)) { ErrorMsg.Printf(LLoadString(IDS_ERROR_CANT_CREATE_DB), f); return false; } else { // const char *Tbl; // for (GMail3Def *Flds=Fields.First(&Tbl); Flds; Flds=Fields.Next(&Tbl)) for (auto it : Fields) { Store3Status s = CheckTable(it.key, it.value); if (s == Store3Missing && UpdateTable(it.key, it.value, s)) { s = Store3Success; } TableStatus.Add(it.key, s); if (s == Store3Error || (s == Store3UpgradeRequired && OpenStatus != Store3Error)) OpenStatus = s; } LArray DelNames; { LStatement Tables( this, "SELECT name FROM sqlite_master " "WHERE type='table' " "ORDER BY name;"); while (Tables.Row()) { char *Name = Tables.GetStr(0); if (Name && strchr(Name, '_') && !stristr(Name, "sqlite")) DelNames.New().Reset(NewStr(Name)); } } for (unsigned i=0; i &new_items, int pos, bool is_new) { if (!Callback) return; Callback->SetContext(File, Line); Callback->OnNew(parent, new_items, pos, is_new); } bool LMail3Store::OnChange(const char *File, int Line, LArray &items, int FieldHint) { if (!Callback) return false; Callback->SetContext(File, Line); return Callback->OnChange(items, FieldHint); } bool LMail3Store::OnMove(const char *File, int Line, LDataFolderI *new_parent, LDataFolderI *old_parent, LArray &items) { if (!Callback) return false; Callback->SetContext(File, Line); return Callback->OnMove(new_parent, old_parent, items); } bool LMail3Store::OnDelete(const char *File, int Line, LDataFolderI *parent, LArray &items) { if (!Callback) return false; Callback->SetContext(File, Line); return Callback->OnDelete(parent, items); } const char *LMail3Store::GetStr(int id) { switch (id) { case FIELD_ERROR: return ErrorMsg; case FIELD_STATUS: return StatusMsg; case FIELD_TEMP_PATH: return TempPath; } return NULL; } Store3Status LMail3Store::SetStr(int id, const char *s) { switch (id) { case FIELD_ERROR: ErrorMsg = s; break; case FIELD_STATUS: StatusMsg = s; break; case FIELD_TEMP_PATH: TempPath = s; break; default: LAssert(0); return Store3Error; } return Store3Success; } int64 LMail3Store::GetInt(int id) { switch (id) { case FIELD_STATUS: { if (OpenStatus != Store3Success) return OpenStatus; return IsOk() ? Store3Success : Store3Error; } case FIELD_READONLY: return false; case FIELD_VERSION: return 3; case FIELD_FORMAT: return Format; case FIELD_STORE_TYPE: return Store3Sqlite; } return -1; } Store3Status LMail3Store::SetInt(int id, int64 i) { switch (id) { case FIELD_FORMAT: break; } return Store3Error; } bool LMail3Store::Check(int Code, const char *Sql) { if (Code == SQLITE_OK || Code == SQLITE_DONE) return true; const char *Err = sqlite3_errmsg(Db); LgiTrace("%s:%i - Sqlite error %i: %s\n%s", _FL, Code, Err, Sql?Sql:(char*)"", Sql?"\n":""); LAssert(!"Db Error"); #if MAIL3_TRACK_OBJS for (int i=0; iItems.State = Store3Loaded; Root->Serialize(s, false); } } else { // Create new root folder... if ((Root = new LMail3Folder(this))) { Root->Items.State = Store3Loaded; Root->Write(MAIL3_TBL_FOLDER, true); } } } } return Root; } #define PROFILE_MOVE 0 #if PROFILE_MOVE #define PROF_MOVE(...) prof.Add(__VA_ARGS__) #else #define PROF_MOVE(...) #endif Store3Status LMail3Store::Move(LDataFolderI *NewFolder, LArray &Items) { Store3Status Status = Store3Error; LMail3Folder *To = dynamic_cast(NewFolder); if (!To) return Status; if (Items.Length() == 0) return Store3Success; #if PROFILE_MOVE LProfile prof("LMail3Store::Move"); #endif LDataFolderI *OldParent = NULL; LArray Moved; StoreTrans Tr = StartTransaction(); for (unsigned n=0; n(Items[n]); if (Fld) { PROF_MOVE("0"); if (Fld->ParentId == To->Id) Status = Store3Success; else { char s[256]; sprintf_s(s, sizeof(s), "update " MAIL3_TBL_FOLDER " set ParentId=" LPrintfInt64 " where Id=" LPrintfInt64, To->Id, Fld->Id); PROF_MOVE("1"); LStatement Stmt(this, s); if (Stmt.Exec()) { PROF_MOVE("2"); Status = Store3Success; LAssert(Fld->Parent->Sub.IndexOf(Fld) >= 0); Fld->Parent->Sub.Delete(Fld); LDataFolderI *From = Fld->Parent; Fld->Parent = To; Fld->ParentId = Fld->Parent->Id; To->Sub.Insert(Fld); if (!OldParent) OldParent = From; if (Callback && OldParent && Moved.Length() && OldParent != From) { PROF_MOVE("3"); Callback->OnMove(To, OldParent, Moved); PROF_MOVE("4"); Moved.Length(0); OldParent = From; } Moved.Add(Fld); } } } else if ((Thing = dynamic_cast(Items[n]))) { PROF_MOVE("5"); if (Thing->ParentId == To->Id) Status = Store3Success; else if (To->ItemType != MAGIC_ANY && Thing->Type() != To->ItemType) { LgiTrace("%s:%i - Can't move item (type=%x) to folder containing type %x.\n", _FL, Thing->Type(), To->ItemType); Status = Store3Error; } else { // This needs to be before the SQL update so that duplicate Mail // objects don't get created when moving to an unloaded folder. if (To->Items.GetState() != Store3Loaded) To->Children(); PROF_MOVE("6"); char s[256]; sprintf_s(s, sizeof(s), "update %s set ParentId=" LPrintfInt64 " where Id=" LPrintfInt64, Thing->GetTable(), To->Id, Thing->Id); LStatement Stmt(this, s); if (Stmt.Exec()) { PROF_MOVE("6"); Status = Store3Success; LDataFolderI *From = Thing->Parent; if (Thing->Parent) { LAssert(Thing->Parent->Items.IndexOf(Thing) >= 0); Thing->Parent->Items.Delete(Thing); } Thing->Parent = To; Thing->ParentId = To->Id; LAssert(To->Items.IndexOf(Thing) < 0); #ifdef _DEBUG if (To->System != Store3SystemTrash) { // Check there is no duplicate ID.. for (unsigned k=0; kItems.a.Length(); k++) { if (To->Items.a[k]->Id == Thing->Id) { LAssert(!"Can't have duplicate IDs in the same folder."); } } } #endif // LgiTrace("%s:%i - Saving %p:%i to %s (%i items)\n", _FL, Thing, (int)Thing->Id, To->GetStr(FIELD_FOLDER_NAME), To->Items.Length()); To->Items.Insert(Thing); if (!OldParent) OldParent = From; if (Callback && OldParent && Moved.Length() && OldParent != From) { PROF_MOVE("7"); Callback->OnMove(To, OldParent, Moved); PROF_MOVE("8"); Moved.Length(0); OldParent = From; } Moved.Add(Thing); } } } } if (Callback && Moved.Length()) Callback->OnMove(To, OldParent, Moved); return Status; } Store3Status LMail3Store::Delete(LArray &Items, bool ToTrash) { if (Items.Length() == 0) return Store3Error; LVariant FolderName; LMail3Folder *Trash = 0; if (ToTrash && Callback->GetSystemPath(FOLDER_TRASH, FolderName)) { auto t = LString(FolderName.Str()).SplitDelimit("/"); if (t.Length() == 1) Trash = Root->FindSub(t[0]); } if (Trash) { LArray MoveItems; for (unsigned i=0; i(di); if (t && t->Parent != Trash) { // Move the item to the trash instead... MoveItems.Add(di); Items.DeleteAt(i--, true); } else { LMail3Folder *f = dynamic_cast(di); if (f && f->Parent != Trash) { // Move the folder to the trash instead... MoveItems.Add(di); Items.DeleteAt(i--, true); } } } if (MoveItems.Length() > 0) { Store3Status s = Move(Trash, MoveItems); if (s != Store3Success) return s; } if (Items.Length() == 0) return Store3Success; } Store3Status s = Store3Success; LDataI *Item = Items[0]; switch ((uint32_t)Item->Type()) { default: { LMail3Thing *t = dynamic_cast(Item); if (!t) { LAssert(!"What are you trying to do?"); s = Store3Error; } else if (Callback && !Callback->OnDelete(t->Parent, Items)) { s = Store3Error; } else { for (unsigned i=0; i(Items[i]))) { if (t->DbDelete()) { if (t->Parent) t->Parent->Items.Delete(t); else LAssert(0); DeleteObj(t); } else s = Store3Error; } } } break; } case MAGIC_ATTACHMENT: { for (auto i: Items) { LMail3Attachment *a = dynamic_cast(i); if (a) { a->Delete(ToTrash); a->Detach(); delete a; } } break; } case MAGIC_FOLDER: { LMail3Folder *f = dynamic_cast(Item); if (!f) s = Store3Error; else if (Callback && !Callback->OnDelete(f->Parent, Items)) s = Store3Error; else { for (unsigned i=0; i(Items[i]))) { if (f->DbDelete()) { if (f->Parent) f->Parent->Sub.Delete(f); else LAssert(0); DeleteObj(f); } else s = Store3Error; } } } break; } } return s; } Store3Status LMail3Store::Change(LArray &Items, int PropId, LVariant &Value, LOperator Operator) { if (Items.Length() == 0) return Store3Success; if (PropId != FIELD_FLAGS) { LAssert(!"Not impl."); return Store3NotImpl; } int32 Val = Value.CastInt32(); if (!Val) { LAssert(!"One flag must be set"); return Store3Error; } if (Operator != OpPlusEquals && Operator != OpMinusEquals) { LAssert(!"Operator not supported."); return Store3Error; } StoreTrans Tr = StartTransaction(); // Set/unset flag for (unsigned i=0; i Chunk; for (unsigned n=0; n<100 && i(Items[i]); if (!m) { LAssert(!"Invalid object."); continue; } if ( (Operator == OpMinusEquals && (m->Flags & Val)) || (Operator == OpPlusEquals && !(m->Flags & Val)) ) { p.Print("%sId=" LPrintfInt64, Chunk.Length()?" or ":"", m->Id); Chunk.Add(m); } } if (Chunk.Length()) { auto Sql = p.NewGStr(); LStatement s(this, Sql); if (!s.Exec()) return Store3Error; for (unsigned n=0; n(Chunk[n]); if (m) { if (Operator == OpMinusEquals) m->Flags &= ~Val; else m->Flags |= Val; } } OnChange(_FL, Chunk, PropId); } } return Store3Success; } bool LMail3Store::SetFormat(LViewI *Parent, LDataPropI *Props) { if (!Parent || !Props) return false; Mail3SubFormat NewFormat = (Mail3SubFormat)Props->GetInt(Store3UiNewFormat); if (NewFormat == Format) return true; bool Error = false; LStatement Folders(this, "select * from " MAIL3_TBL_FOLDER); char Sql[256]; LStatement Count(this, "select Count(*) as c from " MAIL3_TBL_FOLDER); if (Count.Row()) { char *c = Count.GetStr(0); if (c) Props->SetInt(Store3UiMaxPos, atoi(c)); } if (NewFormat == Mail3v1) { // Change format to single table for all email // Create the mail table Store3Status s = CheckTable(MAIL3_TBL_MAIL, TblMail); if (!UpdateTable(MAIL3_TBL_MAIL, TblMail, s)) Error = true; else { // Copy all the sub-folder mail into that one table... while (Folders.Row()) { int64 Id = Folders.GetInt64(0); char TblName[128]; sprintf_s(TblName, sizeof(TblName), "Mail_" LPrintfInt64, Id); // Set the parent ID on all mail rows in the sub-folder table (just to be sure) sprintf_s(Sql, sizeof(Sql), "update %s set ParentId=" LPrintfInt64, TblName, Id); LStatement SetId(this, Sql); if (!SetId.Exec()) { Error = true; break; } // Now copy all the rows into the main Mail table sprintf_s(Sql, sizeof(Sql), "insert into " MAIL3_TBL_MAIL " select * from %s", TblName); LStatement Copy(this, Sql); if (!Copy.Exec()) { Error = true; break; } // And drop the sub-folder table.. sprintf_s(Sql, sizeof(Sql), "drop table if exists %s", TblName); LStatement Del(this, Sql); if (!Del.Exec()) { Error = true; break; } } } } else if (NewFormat == Mail3v2) { // Change format to a table per folder of email { LTransaction Trans(this); int Pos = 0; while (Folders.Row()) { int64 Id = Folders.GetInt64(0); char TblName[128]; sprintf_s(TblName, sizeof(TblName), "Mail_" LPrintfInt64, Id); sprintf_s(Sql, sizeof(Sql), "create table if not exists %s as select * from Mail where ParentId=" LPrintfInt64, TblName, Id); LStatement Convert(this, Sql); if (!Convert.Exec()) { Error = true; Trans.RollBack(); break; } Props->SetInt(Store3UiCurrentPos, ++Pos); } } sprintf_s(Sql, sizeof(Sql), "drop table if exists " MAIL3_TBL_FOLDER); LStatement Del(this, Sql); if (!Del.Exec()) Error = true; } if (!Error) Format = NewFormat; return !Error; } bool LMail3Store::Upgrade(LViewI *Parent, LDataPropI *Props) { bool Status = true; LStringPipe p; // const char *Tbl; // for (GMail3Def *Flds=Fields.First(&Tbl); Flds; Flds=Fields.Next(&Tbl)) for (auto it : Fields) { Store3Status s = TableStatus.Find(it.key); if (s == Store3UpgradeRequired) { if (!UpdateTable(it.key, it.value, s)) { p.Print("Failed to upgrade %s\n", it.key); Status = false; } } } if (Props && !Status) { LAutoString a(p.NewStr()); Props->SetStr(Store3UiError, a); } return Status; } class SqliteRepairThread : public LThread { LString Exe; LString Db; LString RepairSql; LString OldDb; LViewI *Parent; LDataPropI *Props; LAutoPtr Shell; ssize_t Ch; char Line[512]; public: bool Status; SqliteRepairThread(LString exe, LString db, LViewI *parent, LDataPropI *props) : LThread("SqliteRepairThread") { Status = false; Ch = 0; Exe = exe; Db = db; Parent = parent; Props = props; char p[MAX_PATH_LEN]; LMakePath(p, sizeof(p), Db, "..\\Repair.sql"); for (char *c = p; *c; c++) { if (*c == '\\') *c = '/'; } RepairSql = p; LString s; LDateTime dt; dt.SetNow(); char sNow[64]; dt.Get(sNow, sizeof(sNow)); for (char *c = sNow; *c; c++) { if (*c == ':' || *c == '/') *c = '-'; } s.Printf("..\\Database %s.sqlite", sNow); LMakePath(p, sizeof(p), Db, s); OldDb = p; LgiTrace("Sqlite Repair paths:\n" "Exe: '%s'\n" "Db: '%s'\n" "Old: '%s'\n" "Sql: '%s'\n", Exe.Get(), Db.Get(), OldDb.Get(), RepairSql.Get()); Run(); } char *Read() { Ch = Shell->Read(Line, sizeof(Line)-1); if (Ch < 0) return NULL; Line[Ch] = 0; return Line; } bool GetPrompt() { char *l; while ((l = Read())) { if (stristr(l, "sqlite>")) return true; } if (Props) Props->SetStr(Store3UiError, "Failed to get prompt..."); return false; } void StatusMsg(const char *msg) { if (Props) { int64 p = Props->GetInt(Store3UiCurrentPos); Props->SetInt(Store3UiCurrentPos, p + 1); Props->SetStr(Store3UiStatus, msg); } } int Main() { if (Props) { Props->SetInt(Store3UiMaxPos, 7); Props->SetStr(Store3UiStatus, "Starting sqlite..."); } // Open the shell and start the export process LString Args; Args.Printf("-interactive %s", Db.Get()); if (!Shell.Reset(new LSubProcess(Exe, Args))) return -1; if (!Shell->Start(true, true)) { if (Props) Props->SetStr(Store3UiError, "Couldn't execute sqlite3 shell."); return false; } // http://froebe.net/blog/2015/05/27/error-sqlite-database-is-malformed-solved/ // // Run through the commands: // pragma integrity_check; // .mode insert // .output mydb_export.sql // .dump // .exit if (!GetPrompt()) return -2; StatusMsg("Setting mode..."); LString Cmd; Cmd.Printf(".mode insert\r\n"); Shell->Write(Cmd, Cmd.Length()); if (!GetPrompt()) return -3; StatusMsg("Setting output file..."); Cmd.Printf(".output \"%s\"\r\n", RepairSql.Get()); Shell->Write(Cmd, Cmd.Length()); if (!GetPrompt()) return -4; StatusMsg("Dumping SQL..."); Cmd.Printf(".dump\r\n"); Shell->Write(Cmd, Cmd.Length()); if (!GetPrompt()) return -5; Cmd.Printf(".exit\r\n"); Shell->Write(Cmd, Cmd.Length()); Shell->Wait(); StatusMsg("Renaming database..."); if (!FileDev->Move(Db, OldDb)) { LString s; s.Printf("Can't move '%s' to '%s'", Db.Get(), OldDb.Get()); Props->SetStr(Store3UiError, s); return -6; } StatusMsg("Importing SQL..."); char *r = RepairSql; for (char *c = r; *c; c++) { if (*c == '/' || *c == '\\') *c = DIR_CHAR; } #ifdef WINDOWS wchar_t wd[MAX_PATH_LEN]; _wgetcwd(wd, MAX_PATH_LEN); #else char wd[MAX_PATH_LEN]; #ifdef __GTK_H__ // Gtk:: #endif getcwd(wd, MAX_PATH_LEN); #endif char base[MAX_PATH_LEN]; LMakePath(base, sizeof(base), Db, ".."); #ifdef WINDOWS LAutoWString baseW(Utf8ToWide(base)); _wchdir(baseW); #else #ifdef __GTK_H__ // Gtk:: #endif chdir(base); #endif char *exe_leaf = strrchr(Exe, DIR_CHAR); Args.Printf("%s \"%s\" < \"%s\"", exe_leaf ? exe_leaf + 1 : Exe.Get(), Db.Get(), RepairSql.Get()); int Result = system(Args); LgiTrace("Import result = %i\n", Result); #ifdef WINDOWS _wchdir(wd); #else #ifdef __GTK_H__ // Gtk:: #endif chdir(wd); #endif StatusMsg("Deleting temporary files..."); FileDev->Delete(RepairSql, false); Status = true; return 0; } }; bool CheckPathForFile(const char *File, char *Exe, int ExeSize) { LString Path; #ifdef WINDOWS char *buffer = NULL; size_t sz = 0; errno_t err = _dupenv_s(&buffer, &sz, "PATH"); if (err) { LgiTrace("%s:%i - _dupenv_s failed with %i.\n", _FL, err); return false; } if (sz == 0) // PATH not found return false; Path = buffer; free(buffer); #else Path = getenv("PATH"); if (!Path) return false; #endif LString::Array p = Path.Split(LGI_PATH_SEPARATOR); for (unsigned i=0; i(Props); LString Msg; Msg.Printf("Error: sqlite shell binary missing (%s). " #ifdef WINDOWS "Download from:\n" "\n" " %s\n" #else "Install using your package manager:\n" "\n" " e.g. sudo apt-get install sqlite3\n" #endif "\n" "Download the shell binary and extract to this folder:\n" "\n" " %s\n" "\n" "Then try the Repair command again.\n", SqliteBin, DownloadUrl.Get(), base); LAlert Dlg( p?p:Parent, "LMail3Store::Repair", Msg, "Browse Download Site & Local Folder", "Cancel"); int Btn = Dlg.DoModal(); if (Btn == 1) { LExecute(DownloadUrl); LExecute(base); } return true; } // Close our database... CloseDb(); // Do the repair in a thread SqliteRepairThread Worker(exe, DbFile, Parent, Props); while (!Worker.IsExited()) { LSleep(20); LYield(); } OpenDb(); return Worker.Status; } int64 LMail3Store::GetFolderId(char *Path) { auto Parts = LString(Path).SplitDelimit("/"); LMail3Folder *Root = dynamic_cast(GetRoot()); if (!Root) return 0; uint64 Id = Root->Id; for (unsigned i=0; i onStatus) : + LThread("CompactThread") { Store = store; - Loop = true; - Return = -1; InboxId = inboxId; - Value = -1; - Max = -1; + Props = props; + OnStatus = onStatus; + Run(); } ~CompactThread() { - Loop = false; - while (!IsExited()) - { - LSleep(20); - LYield(); - } + Cancel(); + WaitForExit(); } void SetError(const char *s) { - if (Lock(_FL)) - { - Error = s; - Unlock(); - } + Props->SetStr(Store3UiError, s); } void SetStatus(const char *s) { - if (Lock(_FL)) - { - Status = s; - Unlock(); - } + Props->SetStr(Store3UiStatus, s); } int Main() { // Clean up orphaned mail segments. { SetStatus("Cleaning up orphaned Mail segments..."); LMail3Store::LStatement s(Store, "delete from " MAIL3_TBL_MAILSEGS " where MailId not in (select Id from " MAIL3_TBL_MAIL ")"); if (!s.Exec()) { SetError("Failed to delete orphaned segments."); - Return = false; - return 0; + OnStatus(false); + return -1; } } // Clean up orphaned mail. - if (Loop) + if (!IsCancelled()) { SetStatus("Cleaning up orphaned Mail..."); LMail3Store::LStatement count(Store, "select COUNT(*) from " MAIL3_TBL_MAIL " where ParentId not in (select Id from " MAIL3_TBL_FOLDER ")"); if (count.Row()) { int64 Rows = count.GetInt64(0); Max = Rows; } LDataStoreI::StoreTrans Trn = Store->StartTransaction(); LMail3Store::LStatement s(Store, "select Id from " MAIL3_TBL_MAIL " where ParentId not in (select Id from " MAIL3_TBL_FOLDER ")"); int64 RowPos = 0; - while (Loop && s.Row()) + while (!IsCancelled() && s.Row()) { int64 Id = s.GetInt64(0); - - #if 1 - Store->DeleteMailById(Id); - - #else - - char Sql[256]; - sprintf_s(Sql, sizeof(Sql), "update "MAIL3_TBL_MAIL" set ParentId="LPrintfInt64" where Id="LPrintfInt64, InboxId, Id); - LMail3Store::LStatement Move(Store, Sql); - if (!Move.Exec()) - { - if (Lock(_FL)) - { - SetError("Failed to move orphaned mail."); - Unlock(); - } - Return = false; - return 0; - } - - #endif - Value = ++RowPos; } Max = 1; Value = 0; } // Vacuum - if (Loop) + if (!IsCancelled()) { SetStatus("Vacuum unused space..."); LMail3Store::LStatement s(Store, "vacuum;"); if (!s.Exec()) { SetError("Reclaiming space failed."); - Return = false; - return 0; + OnStatus(false); + return -1; } } - Return = true; + OnStatus(true); return 0; } }; -bool LMail3Store::Compact(LViewI *Parent, LDataPropI *Props) +void LMail3Store::Compact(LViewI *Parent, LDataPropI *Props, std::function OnStatus) { + if (!Props || !Callback) + return; + bool Status = false; LVariant InboxPath; if (!Callback->GetSystemPath(FOLDER_INBOX, InboxPath)) Props->SetStr(Store3UiError, "Couldn't get path to Inbox."); else { int64 InboxId = GetFolderId(InboxPath.Str()); if (!InboxId) Props->SetStr(Store3UiError, "Couldn't get Inbox ID."); { - CompactThread Thread(this, InboxId); - while - ( - !Props->GetInt(Store3UiCancel) - && - (Thread.Return < 0 || !Thread.IsExited()) - ) - { - LSleep(100); - - if (Thread.Lock(_FL)) - { - if (Thread.Status) - { - Props->SetStr(Store3UiStatus, Thread.Status); - Thread.Status.Empty(); - } - if (Thread.Error) - { - Props->SetStr(Store3UiError, Thread.Error); - Thread.Error.Empty(); - } - if (Thread.Max > 0) - { - Props->SetInt(Store3UiMaxPos, Thread.Max); - Thread.Max = -1; - } - if (Thread.Value >= 0) - { - Props->SetInt(Store3UiCurrentPos, Thread.Value); - Thread.Value = -1; - } - Thread.Unlock(); - } - - LYield(); - } - - Status = Thread.Return > 0; + new CompactThread(this, InboxId, Props, OnStatus); + return; // without calling OnStatus, the CompactThread will do it. } } - return Status; + OnStatus(Status); } void LMail3Store::OnEvent(void *Param) { } bool LMail3Store::IsOk() { return #ifndef __llvm__ this != 0 && #endif Db != 0; } bool LMail3Store::ParseTableFormat(const char *Name, TableDefn &Defs) { char Sql[256]; sprintf_s(Sql, sizeof(Sql), "SELECT * FROM sqlite_master WHERE type='table' and name='%s'", Name); LStatement st(this, Sql); if (!st.IsOk()) return false; if (!st.Row()) return false; char *Fmt = st.GetStr(4); char *s = Fmt ? strchr(Fmt, '(') + 1 : 0; char *e = Fmt ? strrchr(Fmt, ')') : 0; if (!s || !e) return false; LString f(s, e - s); Defs.t = f.SplitDelimit(","); for (unsigned i=0; i= Defs.Length()) { LString Msg; Msg.Printf("\nTable '%s' is missing the schema field: '%s %s'", Name, Flds[i].Type, Flds[i].Name); StatusMsg += Msg; break; } GMail3Def &Def = Defs[i]; if (i >= FieldCount) // Schema field count is less than existing table { LString Msg; Msg.Printf("\nTable '%s' has an extra field: '%s %s'\n", Name, Def.Type, Def.Name); StatusMsg += Msg; break; } if (!Flds[i].Name || !Flds[i].Type || !Def.Name || !Def.Type) { Status = Store3Error; break; } if (_stricmp(Flds[i].Name, Def.Name) != 0 || _stricmp(Flds[i].Type, Def.Type) != 0) { LString Msg; Msg.Printf("\nTable '%s' has a field '%s %s' which is to the schema: '%s %s'\n", Name, Def.Type, Def.Name, Flds[i].Type, Flds[i].Name); StatusMsg += Msg; Status = Store3UpgradeRequired; break; } } if (Defs.Length() != FieldCount) { LString Msg; Msg.Printf("\nTable '%s' has %i fields (should have %i)\n", Name, Defs.Length(), FieldCount); StatusMsg += Msg; Status = Store3UpgradeRequired; } } else Status = Store3Missing; return Status; } bool LMail3Store::UpdateTable(const char *Name, GMail3Def *Flds, Store3Status Check) { LStatement st(this); LAutoString TempTable; TableDefn Defs; if (Check == Store3UpgradeRequired) { // Get the old table format so we can copy over field by field later... if (!ParseTableFormat(Name, Defs)) { return false; } char Tmp[256]; sprintf_s(Tmp, sizeof(Tmp), "%s_tmp", Name); TempTable.Reset(NewStr(Tmp)); char AlterSql[256]; sprintf_s(AlterSql, sizeof(AlterSql), "alter table %s rename to %s", Name, Tmp); LStatement Alter(this, AlterSql); if (!Alter.Exec()) { LAssert(!"Can't rename table."); return false; } } // Create table? LStringPipe p; LHashTbl, bool> AllFlds; p.Print("create table %s (", Name); for (int i=0; Flds[i].Name; i++) { AllFlds.Add(Flds[i].Name, true); if (i) p.Print(", "); p.Print("%s %s", Flds[i].Name, Flds[i].Type); } p.Print(")"); LAutoString s(p.NewStr()); if (s) { if (st.Prepare(s)) st.Exec(); else { LgiTrace("Sql='%s'\n", s.Get()); return false; } } // Copy over data from the old table if (TempTable) { LStringPipe p; p.Print("insert into %s (", Name); int Count = 0; for (unsigned i=0; iGetFields(Tbl); if (f) { LVariant v; LStringPipe p; p.Print("insert into '%s' values (", Tbl); for (int i=0; f[i].Name; i++) { if (i) p.Print(","); p.Print("?"); } p.Print(")"); v.OwnStr(p.NewStr()); Prepare(v.Str()); } } LMail3Store::LUpdate::LUpdate(LMail3Store *store, const char *Tbl, int64 rowid, char *ExcludeField) : LStatement(store) { RowId = rowid; Store = store; Table = Tbl; LAssert(Store != 0 && Tbl != 0 && RowId > 0); GMail3Def *f = Store->GetFields(Tbl); if (f) { LVariant v; LStringPipe p; p.Print("update %s set ", Tbl); for (int i=1; f[i].Name; i++) { if (ExcludeField && !_stricmp(f[i].Name, ExcludeField)) continue; if (i>1) p.Print(", "); p.Print("%s=?%i", f[i].Name, i + 1); } p.Print(" where %s=?1", f[0].Name); v.OwnStr(p.NewStr()); Prepare(v.Str()); } } //////////////////////////////////////////////////////////////////////// bool LMail3Obj::Check(int r, char *sql) { return Store->Check(r, sql); } #define DEBUG_MAIL3_WRITE 0 bool LMail3Obj::Write(const char *Table, bool Insert) { #if DEBUG_MAIL3_WRITE LProfile Prof("LMail3Obj::Write"); #endif LAutoPtr s; #if DEBUG_MAIL3_WRITE Prof.Add(_FL); #endif if (Insert) s.Reset(new LMail3Store::LInsert(Store, Table)); else s.Reset(new LMail3Store::LUpdate(Store, Table, Id)); #if DEBUG_MAIL3_WRITE Prof.Add(_FL); #endif if (s) { Serialize(*s, true); #if DEBUG_MAIL3_WRITE Prof.Add(_FL); #endif if (s->Exec()) { #if DEBUG_MAIL3_WRITE Prof.Add(_FL); #endif if (Insert) { Id = s->LastInsertId(); if (Id < 0) { LAssert(!"No ID returned."); LgiTrace("%s:%i - No ID from statement.\n", _FL); } } } else { LgiTrace("%s:%i - Exec failed.\n", _FL); } } else LAssert(!"No statement"); return Id >= 0; } ////////////////////////////////////////////////////////////////////////// LMail3Store::LStatement::LStatement(LMail3Store *store, const char *sql) { Store = store; s = 0; #if MAIL3_TRACK_OBJS LMail3Store::SqliteObjs &_d = Store->All.New(); _d.Stat = this; #endif if (sql) Prepare(sql); } LMail3Store::LStatement::~LStatement() { Finalize(); #if MAIL3_TRACK_OBJS Store->RemoveFromAll(this); #endif } int64 LMail3Store::LStatement::LastInsertId() { return sqlite3_last_insert_rowid(Store->GetDb()); } bool LMail3Store::LStatement::Prepare(const char *Sql) { Finalize(); TempSql.Reset(NewStr(Sql)); Store->Check(sqlite3_prepare_v2(Store->GetDb(), Sql, -1, &s, 0), Sql); return s != 0; } bool LMail3Store::LStatement::Finalize() { if (!s) return false; bool Status = Store->Check(sqlite3_finalize(s), 0); s = 0; return Status; } bool LMail3Store::LStatement::Row() { if (!IsOk()) return false; int r = sqlite3_step(s); return r == SQLITE_ROW; } bool LMail3Store::LStatement::Exec() { if (!IsOk()) return false; if (!Store->Check(sqlite3_step(s), TempSql)) return false; bool Status = true; if (Post.Length()) { int64 Id = GetRowId(); LAssert(Id > 0); for (unsigned i=0; iCheck(sqlite3_blob_open( Store->GetDb(), 0, Table.Str(), b.ColName.Str(), Id, true, &Blob), 0)) { LArray Buf; if (Buf.Length(32<<10)) { ssize_t r, Pos = 0; b.Data->SetPos(0); while ((r = b.Data->Read(&Buf[0], Buf.Length())) > 0) { if (!Store->Check(sqlite3_blob_write(Blob, &Buf[0], (int)r, (int)Pos), 0)) { Status = false; break; } Pos += r; } } else Status = false; Store->Check(sqlite3_blob_close(Blob), 0); } } Post.Length(0); } return Status; } bool LMail3Store::LStatement::Reset() { if (!IsOk()) return false; return Store->Check(sqlite3_reset(s), 0) && Store->Check(sqlite3_clear_bindings(s), 0); } /* All the getter functions use 'Col' as the first column is '0' */ int LMail3Store::LStatement::GetSize(int Col) { return IsOk() ? sqlite3_column_bytes(s, Col) : 0; } bool LMail3Store::LStatement::GetBool(int Col) { if (!IsOk() || sqlite3_column_type(s, Col) == SQLITE_NULL) return false; return sqlite3_column_int(s, Col) != 0; } int LMail3Store::LStatement::GetInt(int Col) { if (!IsOk() || sqlite3_column_type(s, Col) == SQLITE_NULL) return -1; return sqlite3_column_int(s, Col); } int64 LMail3Store::LStatement::GetInt64(int Col) { if (!IsOk() || sqlite3_column_type(s, Col) == SQLITE_NULL) return -1; return sqlite3_column_int64(s, Col); } bool LMail3Store::LStatement::SetInt64(int Col, int64 n) { if (!IsOk()) return false; if (n == -1) return Store->Check(sqlite3_bind_null(s, Col+1), 0); else return Store->Check(sqlite3_bind_int64(s, Col+1, n), 0); } char *LMail3Store::LStatement::GetStr(int Col) { return IsOk() ? (char*)sqlite3_column_text(s, Col) : 0; } bool LMail3Store::LStatement::GetBinary(int Col, LVariant *v) { if (!v) return false; const void *Ptr = sqlite3_column_blob(s, Col); if (!Ptr) return false; int Bytes = sqlite3_column_bytes(s, Col); return v->SetBinary(Bytes, (void*)Ptr); } /* All the setter functions use 'Col+1' as the first column is '1' */ bool LMail3Store::LStatement::SetInt(int Col, int n) { if (!IsOk()) return false; if (n == -1) return Store->Check(sqlite3_bind_null(s, Col+1), 0); else return Store->Check(sqlite3_bind_int(s, Col+1, n), 0); } bool LMail3Store::LStatement::SetStr(int Col, char *Str) { if (!IsOk()) return false; if (Str) return Store->Check(sqlite3_bind_text(s, Col+1, Str, -1, SQLITE_STATIC), 0); else return Store->Check(sqlite3_bind_null(s, Col+1), 0); } bool LMail3Store::LStatement::SetDate(int Col, LDateTime &Var) { if (!Var.Year()) return Store->Check(sqlite3_bind_null(s, Col+1), 0); char c[64]; sprintf_s(c, sizeof(c), "%4.4i-%2.2i-%2.2i %2.2i:%2.2i:%2.2i", Var.Year(), Var.Month(), Var.Day(), Var.Hours(), Var.Minutes(), Var.Seconds()); return Store->Check(sqlite3_bind_text(s, Col+1, (const char*)c, -1, SQLITE_TRANSIENT), 0); }; bool LMail3Store::LStatement::SetStream(int Col, const char *ColName, LStreamI *Data) { if (!IsOk() || !Data) return false; PostBlob &p = Post.New(); p.ColName = ColName; p.Data = Data; p.Size = Data->GetSize(); LAssert((p.Size & 0xffffffff00000000) == 0); return Store->Check(sqlite3_bind_zeroblob(s, Col+1, (int)p.Size), 0); } bool LMail3Store::LStatement::SetBinary(int Col, const char *ColName, LVariant *v) { if (!v || v->Type != GV_BINARY) { LAssert(!"Invalid binary type."); return false; } if (!Store->Check(sqlite3_bind_blob(s, Col+1, v->Value.Binary.Data, (int)v->Value.Binary.Length, SQLITE_TRANSIENT), NULL)) return false; return true; } ////////////////////////////////////////////////////////////////////////// LMail3Store::LTransaction::LTransaction(LMail3Store *store) { Store = store; const char *Sql = "begin;"; Open = Store ? Store->Check(sqlite3_exec(Store->GetDb(), Sql, 0, 0, 0), Sql) : false; } LMail3Store::LTransaction::~LTransaction() { if (Open) { const char *Sql = "end;"; Store->Check(sqlite3_exec(Store->GetDb(), Sql, 0, 0, 0), Sql); } } bool LMail3Store::LTransaction::RollBack() { bool Status = false; if (Open) { const char *Sql = "rollback;"; Status = Store->Check(sqlite3_exec(Store->GetDb(), Sql, 0, 0, 0), Sql); Open = false; } return Status; } ///////////////////////////////////////////////////////////////////////// LDataStoreI *OpenMail3(const char *Mail3Folder, LDataEventsI *Callback, bool Create) { return new LMail3Store(Mail3Folder, Callback, Create); } diff --git a/Code/Store3Mapi/ScribeMapi.h b/Code/Store3Mapi/ScribeMapi.h --- a/Code/Store3Mapi/ScribeMapi.h +++ b/Code/Store3Mapi/ScribeMapi.h @@ -1,822 +1,822 @@ #ifndef _SCRIBE_MAPI_H_ #define _SCRIBE_MAPI_H_ #ifdef WINDOWS #include #define INITGUID #define USES_IID_IMessage #include "lgi/common/Lgi.h" #include "Store3Common.h" #include "lgi/common/Store3.h" #include "mapix.h" // https://www.microsoft.com/en-us/download/details.aspx?id=12905 #include "mapiutil.h" #if _MSC_VER >= 1400 typedef ULONG_PTR UI_TYPE; #else typedef ULONG UI_TYPE; #endif typedef HRESULT (STDAPICALLTYPE *pWrapCompressedRTFStream) ( LPSTREAM lpCompressedRTFStream, ULONG ulFlags, LPSTREAM FAR *lpUncompressedRTFStream ); #define PR_SMTP_ADDRESS (PROP_TAG(PT_STRING8, 0x39fe)) #define PR_SENDER_SMTP_ADDRESS (PROP_TAG(PT_STRING8, 0x0065)) #define PR_SENDER_SMTP_ADDRESS2 (PROP_TAG(PT_STRING8, 0x0c1f)) #ifndef PR_INTERNET_MESSAGE_ID #define PR_INTERNET_MESSAGE_ID 0x1035001E #endif #ifndef PR_HTML #define PR_HTML 0x10130102 #endif #ifndef PR_BODY_HTML #define PR_BODY_HTML 0x1013001E #endif #ifndef PR_ATTACH_CONTENT_ID #define PR_ATTACH_CONTENT_ID 0x3712001E #endif #ifndef PR_INTERNET_CPID #define PR_INTERNET_CPID 0x3FDE0003 #endif #ifndef PR_ATTACH_CONTENT_ID_W #define PR_ATTACH_CONTENT_ID_W 0x3712001F #endif #ifndef PR_LAST_MODIFIER_NAME #define PR_LAST_MODIFIER_NAME 0x3FFA001F #endif extern uint32_t MapiContactEmailTags[]; class LMapiStore; class LMapiFolder; class LMapiMail; class ScribeMapiList; class LMapiAdviseSink; class LMapiBase { public: SPropValue *MapiGetField(SRow *Row, int Field) { if (!Row) return NULL; for (unsigned i=0; icValues; i++) { if (PROP_ID(Row->lpProps[i].ulPropTag) == PROP_ID(Field)) { return Row->lpProps + i; } } return NULL; } SPropValue *MapiGetProp(IMAPIProp *Props, int Field) { if (!Props) return NULL; SPropTagArray InTag; InTag.cValues = 1; InTag.aulPropTag[0] = Field; SPropValue *OutTag = 0; ULONG Tags = 0; if (Props) { HRESULT res = Props->GetProps(&InTag, 0, &Tags, &OutTag); if (SUCCEEDED(res)) { if (Tags == 1) { return OutTag; } } } return NULL; } int64 MapiCastInt(SPropValue *Val) { if (!Val) return 0; switch (PROP_TYPE(Val->ulPropTag)) { case PT_SHORT: return Val->Value.i; case PT_LONG: return Val->Value.l; case PT_I8: return Val->Value.li.QuadPart; } return 0; } LString MapiCastString(SPropValue *Val) { if (!Val) return LString(); switch (PROP_TYPE(Val->ulPropTag)) { case PT_STRING8: { return LString(Val->Value.lpszA); } case PT_UNICODE: { LAutoString u(WideToUtf8(Val->Value.lpszW)); return LString(u.Get()); } case PT_BINARY: { LString s((const char*)Val->Value.bin.lpb, Val->Value.bin.cb); return s; } } return LString(); } bool MapiCastBinary(SPropValue *Val, void *&Ptr, int &Size) { if (Val && PROP_TYPE(Val->ulPropTag) == PT_BINARY) { Ptr = Val->Value.bin.lpb; Size = Val->Value.bin.cb; return true; } return false; } bool MapiCastDate(LDateTime &dt, SPropValue *Val) { if (!Val) return false; if (PROP_TYPE(Val->ulPropTag) != PT_SYSTIME) return false; dt.Set((uint64)Val->Value.ft.dwHighDateTime << 32 | Val->Value.ft.dwLowDateTime); return true; } LString MapiGetPropStr(IMAPIProp *Props, int Field) { SPropValue *Val = MapiGetProp(Props, Field); if (Val) { return MapiCastString(Val); } return LString(); } int64 MapiGetPropInt(IMAPIProp *Props, int Field) { SPropValue *Val = MapiGetProp(Props, Field); if (Val) { return MapiCastInt(Val); } return 0; } bool MapiGetPropDate(LDateTime &dt, IMAPIProp *Props, int Field) { if (!Props) return false; SPropValue *p = MapiGetProp(Props, Field); if (!p) return false; return MapiCastDate(dt, p); } bool MapiSetPropStr(IMAPIProp *Props, int Field, const char *Str, bool Unicode = false) { if (Props && Str) { LAssert(PROP_TYPE(Field) == PT_STRING8); SPropValue p; LString n; p.ulPropTag = Field; if (Unicode) p.Value.lpszA = n = LToNativeCp(Str); else p.Value.lpszA = (LPSTR)Str; HRESULT res = Props->SetProps(1, &p, 0); return SUCCEEDED(res); } return false; } bool MapiSetPropLong(IMAPIProp *Props, int Field, ULONG lng) { if (!Props) return false; SPropValue p; p.ulPropTag = Field; p.Value.l = lng; HRESULT res = Props->SetProps(1, &p, 0); if (SUCCEEDED(res)) return true; LgiTrace("%s:%i - Failed to set prop %i\n", _FL, Field); return false; } bool MapiSetPropBool(IMAPIProp *Props, int Field, bool b) { if (Props) { SPropValue p; p.ulPropTag = Field; p.Value.b = b; HRESULT res = Props->SetProps(1, &p, 0); return SUCCEEDED(res); } return false; } bool MapiSetPropDate(IMAPIProp *Props, int Field, const LDateTime &d, bool AdjustTz = true) { if (d.Year()) { // Set sent date SPropValue Prop; Prop.ulPropTag = Field; LDateTime a = d; if (AdjustTz) a.ToUtc(); SYSTEMTIME st; st.wDay = a.Day(); st.wMonth = a.Month(); st.wYear = a.Year(); st.wMinute = a.Minutes(); st.wHour = a.Hours(); st.wSecond = a.Seconds(); st.wMilliseconds = 0; if (SystemTimeToFileTime(&st, &Prop.Value.ft)) { HRESULT res = Props->SetProps(1, &Prop, 0); return SUCCEEDED(res); } } return false; } }; class LMapiAddr : public LDataPropI { LMapiStore *Store; public: LMapiMail *m; int CC, Status; LString Name, Email; LMapiAddr(LMapiStore *store); Store3CopyDecl; const char *GetStr(int id); Store3Status SetStr(int id, const char *str); int64 GetInt(int id); Store3Status SetInt(int id, int64 i); }; class LMapiThing : public LDataI, public LMapiBase { friend class LMapiStore; protected: LArray Entry; LPMESSAGE MapiMsg; LString Class; LMapiFolder *Parent; bool IsDirty; public: LMapiStore *Store; LMapiThing(LMapiStore *store); ~LMapiThing(); virtual void Set(SPropValue *entry, LMapiFolder *parent, ScribeMapiList *lst) {} virtual LPMESSAGE Handle() { return MapiMsg; } virtual void ReleaseHandle(); void SetDirty(); uint32_t Type() { LAssert(0); return MAGIC_NONE; } bool IsOnDisk() { LAssert(0); return false; } bool IsOrphan() { LAssert(0); return false; } uint64 Size() { LAssert(0); return 0; } Store3Status Save(LDataI *Parent = 0) { LAssert(0); return Store3Error; } Store3Status Delete(bool ToTrash = true) { LAssert(0); return Store3Error; } LDataStoreI *GetStore(); LAutoStreamI GetStream(const char *file, int line) { LAssert(0); LAutoStreamI s; return s; } Store3Status SetRfc822(LStreamI *m) { LAssert(0); return Store3Error; } }; class LMapiAttachment : public Store3Attachment, public LMapiBase { friend class AttachStream; LPATTACH MapiAttach; LONG AttachNum; ULONG AttachMethod; uint64 DataSize; LString Name; LString MimeType; LString ContentId; LString Charset; LString Literal; public: LMapiAttachment(LMapiStore *store); ~LMapiAttachment(); LPATTACH Handle(); bool Set(LMapiMail *mail, ScribeMapiList *Lst); bool Set(const char *Content, const char *Charset, const char *MimeType); Store3CopyDecl; const char *GetStr(int id); Store3Status SetStr(int id, const char *str); int64 GetInt(int id); Store3Status SetInt(int id, int64 i); uint32_t Type(); bool IsOnDisk(); bool IsOrphan(); uint64 Size(); Store3Status Save(LDataI *Parent = NULL); Store3Status Delete(bool ToTrash = false); LAutoStreamI GetStream(const char *file, int line); void OnSave(); }; class LMapiMail : public LMapiThing { LString Subject; LMapiAddr From; LMapiAddr Reply; DIterator To; LDateTime Date; uint64 Flags; uint64 MsgSize; LString Charset; LString TxtBody; LString HtmlBody; LString MimeType; public: LMapiAttachment *Seg; LMapiMail(LMapiStore *store); ~LMapiMail(); void Set(SPropValue *entry, LMapiFolder *parent, ScribeMapiList *lst); LPMESSAGE Handle(); // LDataPropI API Store3CopyDecl; const char *GetStr(int id); Store3Status SetStr(int id, const char *str); int64 GetInt(int id); Store3Status SetInt(int id, int64 i); const LDateTime *GetDate(int id); Store3Status SetDate(int id, const LDateTime *i); LDataPropI *GetObj(int id); Store3Status SetObj(int id, LDataPropI *i); GDataIt GetList(int id); Store3Status SetRfc822(LStreamI *m); // LDataI API uint32_t Type(); bool IsOnDisk(); bool IsOrphan(); uint64 Size(); Store3Status Save(LDataI *Parent); Store3Status Delete(bool ToTrash = true); LAutoStreamI GetStream(const char *file, int line); }; class LMapiCalendar : public LMapiThing { LDateTime StartDt, EndDt; LString Subject, Location, Notes; CalendarShowTimeAs ShowAs; CalendarPrivacyType Priv; LString TimeZone; uint64 Colour; bool Recur; public: LMapiCalendar(LMapiStore *store); ~LMapiCalendar(); void Set(SPropValue *entry, LMapiFolder *parent, ScribeMapiList *lst); LPMESSAGE Handle(); // LDataPropI API LDataPropI &operator =(LDataPropI &p); const char *GetStr(int id); Store3Status SetStr(int id, const char *str); int64 GetInt(int id); Store3Status SetInt(int id, int64 i); LDateTime *GetDate(int id); Store3Status SetDate(int id, const LDateTime *i); LDataPropI *GetObj(int id); GDataIt GetList(int id); // LDataI API LDataI &operator =(LDataI &p); uint32_t Type(); bool IsOnDisk(); bool IsOrphan(); uint64 Size(); Store3Status Save(LDataI *Parent); Store3Status Delete(bool ToTrash = true); }; class LMapiContact : public LMapiThing { struct Address { LString Street, Suburb, Postcode, State, Country, Url; }; struct Numbers { LString Number, Fax, Mobile; }; LString PrimaryEmail; LString AltEmails; LString Title, First, Last, Nick, Spouse; Address Home, Work; Numbers HomePh, WorkPh; LString Company, Position; LString Notes; void ReadEmails(); char *CacheGetStr(LString &s, uint32_t Prop); public: LMapiContact(LMapiStore *store); ~LMapiContact(); void Set(SPropValue *entry, LMapiFolder *parent, ScribeMapiList *lst); LPMESSAGE Handle(); // LDataPropI API LDataPropI &operator =(LDataPropI &p); const char *GetStr(int id); Store3Status SetStr(int id, const char *str); int64 GetInt(int id); Store3Status SetInt(int id, int64 i); const LDateTime *GetDate(int id); Store3Status SetDate(int id, const LDateTime *i); LDataPropI *GetObj(int id); GDataIt GetList(int id); // LDataI API LDataI &operator =(LDataI &p); uint32_t Type(); bool IsOnDisk(); bool IsOrphan(); uint64 Size(); Store3Status Save(LDataI *Parent); Store3Status Delete(bool ToTrash = true); }; class LMapiFolderField : public LDataPropI { LMapiStore *Store; LString Name; int Id; int Width; public: LMapiFolderField(LMapiStore *store); ~LMapiFolderField(); // LDataPropI API LDataPropI &operator =(LDataPropI &p); const char *GetStr(int id); Store3Status SetStr(int id, const char *str); int64 GetInt(int id); Store3Status SetInt(int id, int64 i); const LDateTime *GetDate(int id); Store3Status SetDate(int id, const LDateTime *i); LDataPropI *GetObj(int id); GDataIt GetList(int id); Store3Status SetRfc822(LStreamI *m); }; class LMapiFolder : public LDataFolderI, public LMapiBase { friend class LMapiStore; LPMAPIFOLDER MapiFolder; LArray Entry; LMapiStore *Store; LMapiFolder *Parent; LString Name; LString Class; int64 Unread; bool IsOpen; int SortIndex; uint32_t ItemType; Store3SystemFolder FolderType; DIterator Sub; DIterator Items; DIterator Flds; public: LMapiFolder(LMapiStore *store); ~LMapiFolder(); bool Set(LPMAPIFOLDER f); bool Set(LMapiFolder *parent, ScribeMapiList *Lst); LPMAPIFOLDER Handle(); void ReleaseHandle(); // LDataPropI API Store3CopyDecl; const char *GetStr(int id); Store3Status SetStr(int id, const char *str); int64 GetInt(int id); Store3Status SetInt(int id, int64 i); const LDateTime *GetDate(int id); Store3Status SetDate(int id, const LDateTime *i); LDataPropI *GetObj(int id); GDataIt GetList(int id); Store3Status SetRfc822(LStreamI *m); // LDataI API uint32_t Type(); bool IsOnDisk(); bool IsOrphan(); uint64 Size(); Store3Status Save(LDataI *Parent); Store3Status Delete(bool ToTrash = true); LDataStoreI *GetStore(); LAutoStreamI GetStream(const char *file, int line); // LDataFolderI API LDataIterator &SubFolders(); LDataIterator &Children(); LDataIterator &Fields(); Store3Status DeleteAllChildren(); Store3Status FreeChildren(); void OnSelect(bool s); void OnCommand(const char *Name); }; class ScribeMapiList : public LMapiBase { LPMAPITABLE List; ULONG Rows; SRowSet *BaseRow; uint32_t i, StartIndex; bool ReleaseList; bool Status; public: ScribeMapiList(LPMAPITABLE list, bool release = true) { List = list; Rows = 0; BaseRow = 0; i = 0; StartIndex = 0; ReleaseList = release; Status = false; if (List) { HRESULT res = List->GetRowCount(0, &Rows); if (SUCCEEDED(res) && Rows) { res = List->SeekRow(BOOKMARK_BEGINNING, 0, NULL); if (SUCCEEDED(res)) { res = List->QueryRows(Rows, 0, &BaseRow); Status = SUCCEEDED(res); } } } } ~ScribeMapiList() { if (List && ReleaseList) { List->Release(); } } int Index() { return i; } int Length() { return Rows; } bool More() { return (BaseRow && i >= StartIndex && i < BaseRow->cRows + StartIndex); } void Next() { i++; if (BaseRow->cRows < Rows && i-StartIndex >= BaseRow->cRows) { // run into the end of the list section int s = StartIndex + BaseRow->cRows; HRESULT res = List->QueryRows(Rows-StartIndex, 0, &BaseRow); if (SUCCEEDED(res)) { StartIndex = s; } } } SRowSet *Current() { if (More()) // in range { return BaseRow + i - StartIndex; } return 0; } SPropValue *GetField(int Field) { if (More()) // in range { return MapiGetField(BaseRow->aRow + i - StartIndex, Field); } return 0; } }; class MapiEntryRef : public LMapiBase { LMapiStore *Store; public: LString DisplayName; LArray Entry; MapiEntryRef(LMapiStore *store) { Store = store; } bool OpenRoot(LPMAPISESSION Session, UI_TYPE UiHnd, IMsgStore **MsgStore, IMAPIFolder **RootFolder); }; class ScribeMsgStores : public LArray, public LMapiBase { LMapiStore *Store; public: bool Status; ScribeMsgStores(LMapiStore *store, LPMAPISESSION Session) { Store = store; Status = false; Session->AddRef(); ULONG r = Session->Release(); LPMAPITABLE MsgStores = 0; HRESULT res = Session->GetMsgStoresTable(0, &MsgStores); if (SUCCEEDED(res) && MsgStores) { for (ScribeMapiList Lst(MsgStores, false); Lst.More(); Lst.Next()) { SPropValue *DisplayName = Lst.GetField(PR_DISPLAY_NAME); SPropValue *Entry = Lst.GetField(PR_ENTRYID); if (DisplayName && Entry) { LAutoPtr Ref(new MapiEntryRef(Store)); if (Ref) { Ref->DisplayName = MapiCastString(DisplayName); void *p = NULL; int s = 0; if (Ref->DisplayName && MapiCastBinary(Entry, p, s)) { Ref->Entry.Add((uint8_t*)p, s); Add(Ref.Release()); } } } } Status = true; } } ~ScribeMsgStores() { DeleteObjects(); } }; class LMapiStore : public LDataStoreI, public LLibrary { friend class LMapiFolder; friend class LMapiThing; friend class LMapiMail; LDataEventsI *Callback; LMapiFolder *Root; LAutoPtr EntryRef; LString Profile, Username, Password, RootName; uint64 AccountId; LArray InboxEntry; LArray Dirty; LMapiAdviseSink *Notify; bool MapiInitialized; LPMAPISESSION Session; IMsgStore *MsgStore; UI_TYPE Ui; MAPIINITIALIZE *MAPIInitialize; MAPILOGONEX *MAPILogonEx; MAPIUNINITIALIZE *MAPIUninitialize; MAPIALLOCATEBUFFER *MAPIAllocateBuffer; MAPIFREEBUFFER *MAPIFreeBuffer; pWrapCompressedRTFStream WrapCompressedRTFStream; LMapiFolder *FindSystemFolder(Store3SystemFolder Type); public: LMapiStore( const char *Server, const char *Username, const char *Password, uint64 accountId, LDataEventsI *callback); ~LMapiStore(); // Util IMsgStore *Handle() { return MsgStore; } bool Error(const char *Fmt, ...); ULONG OnNotify(ULONG cNotif, LPNOTIFICATION lpNotifications); // MAPI API bool Login(); // LDataPropI API Store3Status SetInt(int id, int64 i); int64 GetInt(int id); const char *GetStr(int id); // LDataEventsI wrappers bool OnChange(const char *File, int Line, LArray &items, int FieldHint); // LDataStoreI API uint64 Size() { return 0; } LDataI *Create(int Type); LDataFolderI *GetRoot(bool create = false); Store3Status Move(LDataFolderI *NewFolder, LArray &Items); Store3Status Delete(LArray &Items, bool ToTrash); Store3Status Change(LArray &Items, int PropId, LVariant &Value, LOperator Operator); - bool Compact(LViewI *Parent, LDataPropI *Props); + void Compact(LViewI *Parent, LDataPropI *Props, std::function OnStatus); void OnEvent(void *Param); bool OnIdle(); LDataEventsI *GetEvents(); }; #endif #endif \ No newline at end of file diff --git a/Code/Store3Mapi/ScribeMapi_Store.cpp b/Code/Store3Mapi/ScribeMapi_Store.cpp --- a/Code/Store3Mapi/ScribeMapi_Store.cpp +++ b/Code/Store3Mapi/ScribeMapi_Store.cpp @@ -1,838 +1,838 @@ #define INITGUID #define USES_IID_IMAPIAdviseSink #include #if _MSC_VER < _MSC_VER_VS2013 #include #endif #include "ScribeMapi.h" ///////////////////////////////////////////////////////////////////////////// class LMapiAdviseSink : public IMAPIAdviseSink { LMapiStore *Store; volatile LONG Refs; public: #if _MSC_VER < _MSC_VER_VS2013 ULONG Connection; #else ULONG_PTR Connection; #endif LMapiAdviseSink(LMapiStore *store) { Store = store; Refs = 1; Connection = 0; } ~LMapiAdviseSink() { LAssert(Refs == 0); } HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, LPVOID FAR * ppvObj) { if (!ppvObj) return E_INVALIDARG; *ppvObj = NULL; if (IsEqualGUID(IID_IMAPIAdviseSink, riid)) { *ppvObj = this; AddRef(); return NOERROR; } return E_NOINTERFACE; } ULONG STDMETHODCALLTYPE AddRef() { return InterlockedIncrement(&Refs); } ULONG STDMETHODCALLTYPE Release() { if (Refs == 0) return 0; LONG r = InterlockedDecrement(&Refs); if (r == 0) delete this; return r; } ULONG STDMETHODCALLTYPE OnNotify(ULONG cNotif, LPNOTIFICATION lpNotifications) { if (!lpNotifications) return E_POINTER; return Store->OnNotify(cNotif, lpNotifications); } }; ///////////////////////////////////////////////////////////////////////////// LMapiStore::LMapiStore(const char *profile, const char *username, const char *password, uint64 accountId, LDataEventsI *callback) { Session = NULL; MsgStore = NULL; Profile = profile; Username = username; Password = password; Callback = callback; AccountId = accountId; Notify = NULL; MapiInitialized = false; Root = NULL; LViewI *v = dynamic_cast(Callback); Ui = #ifndef __GTK_H__ v ? (UI_TYPE)v->Handle() : #endif NULL; if (!Load("mapi32.dll")) { Error("%s:%i - Failed to load \"mapi32.dll\".\n", _FL); } MAPIInitialize = (MAPIINITIALIZE*) GetAddress("MAPIInitialize"); MAPILogonEx = (MAPILOGONEX*) GetAddress("MAPILogonEx"); MAPIAllocateBuffer = (MAPIALLOCATEBUFFER*) GetAddress("MAPIAllocateBuffer"); MAPIFreeBuffer = (MAPIFREEBUFFER*) GetAddress("MAPIFreeBuffer"); WrapCompressedRTFStream = (pWrapCompressedRTFStream) GetAddress("WrapCompressedRTFStream"); MAPIUninitialize = (MAPIUNINITIALIZE*) GetAddress("MAPIUninitialize"); if (Login()) { ScribeMsgStores Stores(this, Session); for (unsigned i=0; iDisplayName && stristr(e->DisplayName, Username)) { HRESULT res = Session->OpenMsgStore(Ui, (ULONG)e->Entry.Length(), // entry bytes (LPENTRYID)&e->Entry[0],// ptr to entry NULL, // default interface: IMsgStore MAPI_BEST_ACCESS, &MsgStore); if (SUCCEEDED(res)) { Stores.Delete(e); EntryRef.Reset(e); break; } } } if (MsgStore) { ULONG size = 0; LPENTRYID entry = NULL; HRESULT res = MsgStore->GetReceiveFolder ( NULL, // Get default receive folder MAPI_UNICODE, // Flags &size, // Size and ... &entry, // Value of the EntryID to be returned NULL // You don't care to see the class returned ); if (SUCCEEDED(res)) { InboxEntry.Add((uint8_t*)entry, size); MAPIFreeBuffer(entry); #if 1 // Setup notification Notify = new LMapiAdviseSink(this); if (Notify) { res = MsgStore->Advise( (ULONG)InboxEntry.Length(), (LPENTRYID)&InboxEntry[0], fnevNewMail | fnevObjectCreated | fnevObjectDeleted | fnevObjectModified | fnevObjectMoved, Notify, &Notify->Connection); } #endif } else Error("%s:%i - GetReceiveFolder failed (0x%x).\n", _FL, res); } } } LMapiStore::~LMapiStore() { MAPIUninitialize = NULL; MAPIInitialize = NULL; MAPILogonEx = NULL; MAPIAllocateBuffer = NULL; MAPIFreeBuffer = NULL; WrapCompressedRTFStream = NULL; // Make sure we have cleaned up SetInt(FIELD_IS_ONLINE, false); if (MapiInitialized && MAPIUninitialize) MAPIUninitialize(); } ULONG LMapiStore::OnNotify(ULONG cNotif, LPNOTIFICATION lpNotifications) { LMapiFolder *Inbox = FindSystemFolder(Store3SystemInbox); if (!Inbox) return S_OK; LArray NewItems, DelItems; for (ULONG i=0; iulEventType) { case fnevNewMail: { LAutoPtr nm(new LMapiMail(this)); if (nm) { SPropValue e; ZeroObj(e); e.Value.bin.lpb = (LPBYTE)n->info.newmail.lpEntryID; e.Value.bin.cb = n->info.newmail.cbEntryID; nm->Set(&e, Inbox, NULL); NewItems.Add(nm); Inbox->Items.Insert(nm.Release()); } break; } case fnevObjectDeleted: { OBJECT_NOTIFICATION *o = &n->info.obj; int asd=0; break; } case fnevObjectCreated: { OBJECT_NOTIFICATION *o = &n->info.obj; int asd=0; break; } case fnevObjectModified: { OBJECT_NOTIFICATION *o = &n->info.obj; int asd=0; break; } case fnevObjectMoved: { OBJECT_NOTIFICATION *o = &n->info.obj; int asd=0; break; } } } if (NewItems.Length() && Callback) Callback->OnNew(Inbox, NewItems, -1, true); if (DelItems.Length() && Callback) Callback->OnDelete(Inbox, DelItems); return S_OK; } bool LMapiStore::Error(const char *Fmt, ...) { va_list arg; va_start(arg, Fmt); char buffer[256]; int ch = vsprintf_s(buffer, sizeof(buffer), Fmt, arg); va_end(arg); if (ch > 0) LgiTrace("%s", buffer); return false; } bool LMapiStore::Login() { char16 Cur[MAX_PATH_LEN]; GetCurrentDirectory(CountOf(Cur), Cur); if (!IsLoaded() || !MAPIInitialize || !MAPILogonEx || !MAPIAllocateBuffer || !MAPIFreeBuffer) { Error("%s:%i - Missing address of MAPI functions.\n", _FL); return false; } HRESULT res = MAPIInitialize(NULL); if (FAILED(res)) { Error("%s:%i - MAPIInitialize failed with 0x%x.\n", _FL, res); return false; } LAutoWString wProfile(Utf8ToWide(Profile)); LAutoWString wPassword(Utf8ToWide(Password)); MapiInitialized = true; res = MAPILogonEx( Ui, wProfile, wPassword, MAPI_LOGON_UI | MAPI_EXTENDED, &Session); if (FAILED(res) || !Session) { if (MAPIUninitialize) MAPIUninitialize(); Error("%s:%i - MAPILogonEx failed (0x%x).\n", _FL, res); return false; } SetCurrentDirectory(Cur); return true; } LMapiFolder *LMapiStore::FindSystemFolder(Store3SystemFolder Type) { LDataFolderI *r = GetRoot(); if (!r) return NULL; LDataIterator &it = r->SubFolders(); for (LDataFolderI *f=it.First(); f; f=it.Next()) { if (f->GetInt(FIELD_SYSTEM_FOLDER) == Type) { LMapiFolder *Trash = dynamic_cast(f); LAssert(Trash != NULL); return Trash; } } return NULL; } Store3Status LMapiStore::SetInt(int id, int64 i) { switch (id) { case FIELD_IS_ONLINE: { if (!i) { if (Root) Root->ReleaseHandle(); EntryRef.Reset(); if (Notify) { ULONG r = Notify->Release(); Notify = NULL; } if (MsgStore) { ULONG r = MsgStore->Release(); MsgStore = NULL; } if (Session) { HRESULT res = Session->Logoff(Ui, 0, 0); if (FAILED(res)) { Error("%s:%i - Session->Logoff failed with %x\n", _FL, res); } ULONG r = Session->Release(); LAssert(r == 0); Session = NULL; } } break; } default: LAssert(0); return Store3Error; } return Store3Success; } int64 LMapiStore::GetInt(int id) { switch (id) { case FIELD_IS_ONLINE: return Session != NULL; break; case FIELD_ACCOUNT_ID: return AccountId; default: LAssert(0); break; } return -1; } const char *LMapiStore::GetStr(int id) { switch (id) { case FIELD_FOLDER_NAME: RootName = Username + "@" + Profile; return RootName; } return NULL; } bool LMapiStore::OnChange(const char *File, int Line, LArray &items, int FieldHint) { if (!Callback) return false; Callback->SetContext(File, Line); return Callback->OnChange(items, FieldHint); } LDataI *LMapiStore::Create(int Type) { switch (Type) { case MAGIC_MAIL: return new LMapiMail(this); case MAGIC_FOLDER: return new LMapiFolder(this); case MAGIC_CALENDAR: return new LMapiCalendar(this); case MAGIC_CONTACT: return new LMapiContact(this); default: LAssert(!"Unsupport item type."); Error("%s:%i - Unsupported create type 0x%x\n", _FL, Type); break; } return NULL; } LDataFolderI *LMapiStore::GetRoot(bool create) { if (Session && !Root) { if (EntryRef) { IMAPIFolder *r = NULL; if (EntryRef->OpenRoot(Session, Ui, &MsgStore, &r)) { if ((Root = new LMapiFolder(this))) { Root->SetStr(FIELD_FOLDER_NAME, EntryRef->DisplayName); Root->Set(r); } } } // else LAssert(0); } return Root; } Store3Status LMapiStore::Move(LDataFolderI *NewFolder, LArray &Items) { LMapiFolder *Dest = dynamic_cast(NewFolder); if (!Dest || Items.Length() == 0) { return Store3Error; } LArray Moved; LMapiFolder *Source = NULL; LArray Entries; for (unsigned i=0; i(Items[i]); if (t && t->Parent) { if (!Source) Source = t->Parent; else if (Source != t->Parent) { Items.DeleteAt(i--); continue; } SBinary &bin = Entries.New(); bin.lpb = &t->Entry[0]; bin.cb = (ULONG)t->Entry.Length(); if (t->UserData) Moved.Add(t); } else Items.DeleteAt(i--); } ENTRYLIST Msgs; Msgs.cValues = (ULONG)Entries.Length(); Msgs.lpbin = &Entries[0]; if (!Items.Length()) { LAssert(!"No valid items to move."); return Store3Error; } if (!Source->Handle() || !Dest->Handle()) { LAssert(!"Can't get folder handle."); return Store3Error; } HRESULT res = Source->Handle()->CopyMessages ( &Msgs, NULL, Dest->Handle(), Ui, NULL, // Progress MESSAGE_MOVE // Flags ); if (FAILED(res)) { LAssert(!"CopyMessages failed."); Error("%s:%i - CopyMessages failed with 0x%x\n", _FL, res); return Store3Error; } for (unsigned i=0; i(Moved[i]); if (t) { Source->Items.a.Delete(t); Dest->Items.a.Add(t); t->Parent = Dest; } } if (Moved.Length() > 0 && Callback && !Callback->OnMove(NewFolder, Source, Moved)) { return Store3Error; } return Store3Success; } Store3Status LMapiStore::Delete(LArray &Items, bool ToTrash) { if (Items.Length() == 0) return Store3Error; LMapiFolder *Trash = ToTrash ? FindSystemFolder(Store3SystemTrash) : NULL; if (Trash) { LArray MoveItems; for (unsigned i=0; i(di); if (t && t->Parent != Trash) { // Move the item to the trash instead... MoveItems.Add(di); Items.DeleteAt(i--, true); } } if (MoveItems.Length() > 0) { Store3Status s = Move(Trash, MoveItems); if (s != Store3Success) return s; } if (Items.Length() == 0) return Store3Success; } Store3Status s = Store3Success; LDataI *Item = Items[0]; switch (Item->Type()) { default: { LMapiThing *t = dynamic_cast(Item); if (!t) s = Store3Error; else if (Callback && !Callback->OnDelete(t->Parent, Items)) s = Store3Error; else { LArray Del; LMapiFolder *Parent = NULL; for (unsigned i=0; i(Items[i]); if (!t) continue; if (!Parent) Parent = t->Parent; if (t->Parent != NULL && t->Parent == Parent) { Del.Add(t); } } if (Del.Length() > 0) { ENTRYLIST Msgs; LArray Entries; Msgs.cValues = (ULONG)Del.Length(); Entries.Length(Del.Length()); for (unsigned i=0; iEntry.Length(); e.lpb = &t->Entry[0]; } Msgs.lpbin = &Entries[0]; HRESULT Status = Parent->Handle()->DeleteMessages( &Msgs, NULL/*UI*/, NULL/*Prog*/, 0/*Flags*/); if (SUCCEEDED(Status)) { for (unsigned i=0; iParent->Items.Delete(t); t->Parent = NULL; DeleteObj(t); } } else { Error("%s:%i - DeleteMessages failed with 0x%x\n", _FL, Status); s = Store3Error; } } } break; } case MAGIC_FOLDER: { for (unsigned i=0; i(Items[i]); if (!f) { s = Store3Error; break; } LArray Del; Del.Add(f); if (Callback && !Callback->OnDelete(f->Parent, Del)) { s = Store3Error; break; } if (f->Parent && f->Parent->MapiFolder) { HRESULT res = f->Parent->MapiFolder->DeleteFolder ( (ULONG)f->Entry.Length(), (LPENTRYID) &f->Entry[0], Ui, NULL, // Progress 0 // Flags ); if (FAILED(res)) { Error("%s:%i - Failed to delete folder '%s', err=0x%x\n", _FL, f->Name.Get(), res); s = Store3Error; break; } } if (f->Parent) f->Parent->Sub.Delete(f); else LAssert(0); DeleteObj(f); } break; } } return s; } Store3Status LMapiStore::Change(LArray &Items, int PropId, LVariant &Value, LOperator Operator) { switch (PropId) { case FIELD_FLAGS: { uint64 v = Value.CastInt64(); if (v == MAIL_READ) { LArray Changed; for (unsigned i=0; iGetInt(FIELD_FLAGS); if ((Old & MAIL_READ) == 0) { d->SetInt(FIELD_FLAGS, Old | MAIL_READ); Changed.Add(d); } } if (Changed.Length()) OnChange(_FL, Changed, PropId); } break; } default: { LAssert(!"Unsupport prop ID."); return Store3Error; } } return Store3Success; } -bool LMapiStore::Compact(LViewI *Parent, LDataPropI *Props) +void LMapiStore::Compact(LViewI *Parent, LDataPropI *Props, std::function OnStatus) { - LAssert(0); - return false; + if (OnStatus) + OnStatus(true); } void LMapiStore::OnEvent(void *Param) { } bool LMapiStore::OnIdle() { if (Dirty.Length() > 0) { for (unsigned i=0; iMapiMsg) { HRESULT res = t->MapiMsg->SaveChanges(KEEP_OPEN_READWRITE); if (FAILED(res)) { LgiTrace("%s:%i - SaveChanges failed with 0x%x\n", _FL, res); } t->IsDirty = false; } } Dirty.Length(0); } return false; } LDataEventsI *LMapiStore::GetEvents() { return NULL; } ////////////////////////////////////////////////////// LDataStoreI *LMapiThing::GetStore() { return Store; } bool MapiEntryRef::OpenRoot(LPMAPISESSION Session, UI_TYPE UiHnd, IMsgStore **MsgStore, IMAPIFolder **RootFolder) { bool Status = false; if (Session && MsgStore && RootFolder) { if (!*MsgStore) { HRESULT res = Session->OpenMsgStore(UiHnd, (ULONG)Entry.Length(), // entry bytes (LPENTRYID)&Entry[0], // ptr to entry NULL, // default interface: IMsgStore MAPI_BEST_ACCESS, MsgStore); if (FAILED(res)) { Store->Error("%s:%i - OpenMsgStore failed with 0x%x\n", _FL, res); } } if (*MsgStore) { SPropValue *SubTree = MapiGetProp(*MsgStore, PR_IPM_SUBTREE_ENTRYID); if (SubTree) { ULONG ObjType; HRESULT res = (*MsgStore)->OpenEntry(SubTree->Value.bin.cb, (LPENTRYID)SubTree->Value.bin.lpb, NULL, MAPI_MODIFY, &ObjType, (IUnknown**) RootFolder); if (SUCCEEDED(res) && *RootFolder) { Status = true; } else { (*MsgStore)->Release(); *MsgStore = 0; return Store->Error("%s:%i - OpenEntry failed with 0x%x\n", _FL, res); } } } } return Status; } ////////////////////////////////////////////////////// LDataStoreI *OpenMapiStore( const char *Profile, const char *Username, const char *Password, uint64 AccountId, LDataEventsI *Callback) { return new LMapiStore(Profile, Username, Password, AccountId, Callback); } diff --git a/Code/Store3Webdav/WebdavStore.h b/Code/Store3Webdav/WebdavStore.h --- a/Code/Store3Webdav/WebdavStore.h +++ b/Code/Store3Webdav/WebdavStore.h @@ -1,339 +1,339 @@ #ifndef _WEBDAV_STORE_H_ #define _WEBDAV_STORE_H_ #include "lgi/common/WebDav.h" ////////////////////////////////////////////////////////////////////// class WebdavFolder; class WebdavCalendar; class WebdavThread; struct WebdavEvent; class WebdavStore : public LDataStoreI { friend class WebdavThread; friend class WebdavCalendar; friend class WebdavContact; public: struct LRemote { LString Name, Url, User, Pass; }; protected: ScribeWnd *App; LRemote Remote; LDataEventsI *Callback; LString ContactUrl, CalUrl; WebdavFolder *Root, *ContactFolder, *CalFolder; LXmlTag *LockSettings(const char *File, int Line); void UnlockSettings(); public: WebdavStore(ScribeWnd *a, LDataEventsI *cb, LString optsPath); ~WebdavStore(); // Actions /* bool Read(); bool Write(); bool Delete(); Calendar *NewEvent(); bool Match(char *Email); bool GetEvents(LDateTime &Start, LDateTime &End, LArray &Events); */ // Events void OnChanged(); // LDataPropI int64 GetInt(int id); const char *GetStr(int id); LDataPropI *GetObj(int id); // LDataStoreI impl uint64 Size() { return 0; } LDataI *Create(int Type); LDataFolderI *GetRoot(bool create = false); Store3Status Move(LDataFolderI *NewFolder, LArray &Items) { return Store3Error; } Store3Status Delete(LArray &Items, bool ToTrash); Store3Status Change(LArray &Items, int PropId, LVariant &Value, LOperator Operator) { return Store3Error; } - bool Compact(LViewI *Parent, LDataPropI *Props) { return false; } + void Compact(LViewI *Parent, LDataPropI *Props, std::function OnStatus) { if (OnStatus) OnStatus(true); } void OnEvent(void *Param); bool OnIdle() { return true; } LDataEventsI *GetEvents() { return NULL; } // LDataEventsI impl void Post(LDataStoreI *store, void *Param); void OnNew(LDataFolderI *parent, LArray &new_items, int pos, bool is_new); bool OnDelete(LDataFolderI *parent, LArray &items); bool OnMove(LDataFolderI *new_parent, LDataFolderI *old_parent, LArray &items); bool OnChange(LArray &items, int FieldHint); }; ///////////////////////////////////////////////////////////////////////////////////// class WebdavObj : public LDataI { friend class WebdavStore; protected: WebdavStore *Store; LString Href; bool Converted; WebdavFolder *Parent; public: WebdavObj(WebdavStore *store) { Store = store; Parent = NULL; } const char *GetHref() { return Href; } virtual void FireOnChange(int Fld) {} virtual bool ConvertToText() { return false; } // LDataI impl uint32_t Type() { return MAGIC_NONE; } bool IsOnDisk() { return false; } bool IsOrphan() { return Store != NULL; } LDataStoreI *GetStore() { return Store; } // Stubs uint64 Size() { return 0; } Store3Status Save(LDataI *Parent = NULL) { return Store3NotImpl; } Store3Status Delete(bool ToTrash = true) { return Store3NotImpl; } LAutoStreamI GetStream(const char *file, int line) { return LAutoStreamI(NULL); } bool SetStream(LAutoStreamI stream) { return false; } bool ParseHeaders() { return false; } LDataPropI *GetObj(int id) { EmptyVirtual(NULL); } Store3Status SetObj(int id, LDataPropI *i) { EmptyVirtual(Store3Error); } GDataIt GetList(int id) { EmptyVirtual(NULL); } Store3Status SetRfc822(LStreamI *Rfc822Msg) { return Store3Error; } }; class WebdavFld : public LDataPropI { LDataStoreI *Store; WebdavFolder *Parent; int Id, Width; public: WebdavFld(LDataStoreI *s, WebdavFolder *p = 0, int id = 0, int width = 100); const char *GetStr(int id); int64 GetInt(int id); Store3Status SetInt(int id, int64 i); }; class WebdavFolder : public LDataFolderI { friend class WebdavStore; WebdavFolder &operator =(const WebdavFolder &f) = delete; WebdavStore *Store; WebdavFolder *Parent; LString Name; Store3ItemTypes ItemType = MAGIC_ANY; int64 Sort = 0; Store3State State = Store3Unloaded; public: LString Url; const char *Extension = NULL; DIterator Items; DIterator Sub; DIterator Field; LAutoPtr Thread; WebdavFolder(WebdavStore *store, WebdavFolder *parent = NULL); LString AllocateAddress(); // LDataPropI impl bool CopyProps(LDataPropI &p); const char *GetStr(int id); Store3Status SetStr(int id, const char *str); int64 GetInt(int id); Store3Status SetInt(int id, int64 i); // LDataI impl uint32_t Type() { return MAGIC_FOLDER; } bool IsOnDisk() { return false; } bool IsOrphan() { return false; } uint64 Size() { return 0; } Store3Status Save(LDataI *Parent = NULL); Store3Status Delete(bool ToTrash = true); LDataStoreI *GetStore() { return Store; } LAutoStreamI GetStream(const char *file, int line); bool SetStream(LAutoStreamI stream) { return false; } bool ParseHeaders() { return false; } // LDataFolderI impl LDataIterator &SubFolders(); LDataIterator &Children(); LDataIterator &Fields(); }; ///////////////////////////////////////////////////////////////////////////////////// #define WebdavCalendarDates() \ _(FIELD_CAL_START_UTC, Start) \ _(FIELD_CAL_END_UTC, End) \ _(FIELD_CAL_RECUR_END_DATE, RecurEnd) \ _(FIELD_CAL_LAST_CHECK, LastCheck) \ _(FIELD_DATE_MODIFIED, DateMod) #define WebdavCalendarInts() \ _(FIELD_CAL_TYPE, ObjType) \ _(FIELD_CAL_COMPLETED, Completed) \ _(FIELD_CAL_SHOW_TIME_AS, ShowTimeAs) \ _(FIELD_CAL_RECUR, Recur) \ _(FIELD_CAL_RECUR_FREQ, RecurFreq) \ _(FIELD_CAL_RECUR_INTERVAL, RecurInterval) \ _(FIELD_CAL_RECUR_END_COUNT, RecurEndCount) \ _(FIELD_CAL_RECUR_END_TYPE, RecurEndType) \ _(FIELD_CAL_RECUR_FILTER_DAYS, RecurFilterDays) \ _(FIELD_CAL_RECUR_FILTER_MONTHS, RecurFilterMonths) \ _(FIELD_CAL_ALL_DAY, AddDay) \ _(FIELD_CAL_PRIVACY, Privacy) \ _(FIELD_COLOUR, Colour) \ _(FIELD_STATUS, StoreStatus) #define WebdavCalendarStrings() \ _(FIELD_UID, Uid) \ _(FIELD_CAL_TIMEZONE, TimeZone) \ _(FIELD_CAL_SUBJECT, Subject) \ _(FIELD_CAL_LOCATION, Location) \ _(FIELD_CAL_REMINDERS, Reminders) \ _(FIELD_CAL_RECUR_FILTER_POS, FilterPos) \ _(FIELD_CAL_RECUR_FILTER_YEARS, FilterYears) \ _(FIELD_CAL_NOTES, Notes) \ _(FIELD_CAL_STATUS, CalStatus) class WebdavCalendar : public WebdavObj { LString vCal, Href; #define _(f,v) LDateTime v; WebdavCalendarDates() #undef _ #define _(f,v) uint64_t v = 0; WebdavCalendarInts() #undef _ #define _(f,v) LString v; WebdavCalendarStrings() #undef _ public: WebdavCalendar(WebdavStore *store, WebdavEvent *e); void FireOnChange(int Fld); bool ConvertToText(); // Stubs uint32_t Type() { return MAGIC_CALENDAR; } uint64 Size() { return vCal.Length(); } // LDataI Impl Store3Status Save(LDataI *Parent = 0); Store3Status Delete(bool ToTrash = true); LAutoStreamI GetStream(const char *file, int line); bool CopyProps(LDataPropI &p); const char *GetStr(int id); Store3Status SetStr(int id, const char *str); int64 GetInt(int id); Store3Status SetInt(int id, int64 i); const LDateTime *GetDate(int id); Store3Status SetDate(int id, const LDateTime *i); }; ///////////////////////////////////////////////////////////////////////////////////// #define WebdavContactDates() \ _(FIELD_DATE_MODIFIED, DateMod) #define WebdavContactVariants() \ _(FIELD_CONTACT_IMAGE, Image) #define WebdavContactInts() \ _(FIELD_STATUS, Status) #define WebdavContactStrings() \ _(FIELD_UID, Uid) \ _(FIELD_TITLE, Title) \ _(FIELD_FIRST_NAME, First) \ _(FIELD_LAST_NAME, Last) \ _(FIELD_EMAIL, Email) \ _(FIELD_ALT_EMAIL, AltEmail) \ _(FIELD_NICK, Nick) \ _(FIELD_SPOUSE, Spouse) \ _(FIELD_NOTE, Notes) \ _(FIELD_TIMEZONE, TimeZone) \ \ _(FIELD_HOME_STREET, HomeStreet) \ _(FIELD_HOME_SUBURB, HomeSuburb) \ _(FIELD_HOME_POSTCODE, HomePostCode) \ _(FIELD_HOME_STATE, HomeState) \ _(FIELD_HOME_COUNTRY, HomeCountry) \ _(FIELD_HOME_PHONE, HomePhone) \ _(FIELD_HOME_MOBILE, HomeMobile) \ _(FIELD_HOME_IM, HomeIM) \ _(FIELD_HOME_FAX, HomeFax) \ _(FIELD_HOME_WEBPAGE, HomeWebpage) \ \ _(FIELD_WORK_STREET, WorkStreet) \ _(FIELD_WORK_SUBURB, WorkSuburb) \ _(FIELD_WORK_POSTCODE, WorkPostCode) \ _(FIELD_WORK_STATE, WorkState) \ _(FIELD_WORK_COUNTRY, WorkCountry) \ _(FIELD_WORK_PHONE, WorkPhone) \ _(FIELD_WORK_MOBILE, WorkMobile) \ _(FIELD_WORK_IM, WorkIM) \ _(FIELD_WORK_FAX, WorkFax) \ _(FIELD_WORK_WEBPAGE, WorkWebpage) \ _(FIELD_COMPANY, Company) \ \ _(FIELD_CONTACT_JSON, JSON) class WebdavContact : public WebdavObj { bool Converted; LString vCard; #define _(f,v) LDateTime v; WebdavContactDates() #undef _ #define _(f,v) LVariant v; WebdavContactVariants() #undef _ #define _(f,v) int64 v = 0; WebdavContactInts() #undef _ #define _(f,v) LString v; WebdavContactStrings() #undef _ public: WebdavContact(WebdavStore *store, WebdavEvent *e); void FireOnChange(int Fld); bool ConvertToText(); // Stubs uint32_t Type() { return MAGIC_CONTACT; } uint64 Size() { return vCard.Length(); } // LDataI Impl Store3Status Save(LDataI *Parent = 0); Store3Status Delete(bool ToTrash = true); LAutoStreamI GetStream(const char *file, int line); bool CopyProps(LDataPropI &p); const char *GetStr(int id); Store3Status SetStr(int id, const char *str); int64 GetInt(int id); Store3Status SetInt(int id, int64 i); const LDateTime *GetDate(int id); Store3Status SetDate(int id, const LDateTime *i); const LVariant *GetVar(int id); Store3Status SetVar(int id, LVariant *i); }; #endif