diff --git a/Code/Store3Mail3/Mail3.h b/Code/Store3Mail3/Mail3.h --- a/Code/Store3Mail3/Mail3.h +++ b/Code/Store3Mail3/Mail3.h @@ -1,772 +1,775 @@ #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); \ } \ } struct LMail3StoreMsg { enum Type { MsgNone, MsgCompactComplete, + MsgRepairComplete, } Msg; int64_t Int; LString Str; LMail3StoreMsg(Type type) { Msg = type; } }; 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; + std::function CompactOnStatus; + std::function RepairOnStatus; 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); void Compact(LViewI *Parent, LDataPropI *Props, std::function OnStatus); void Upgrade(LViewI *Parent, LDataPropI *Props, std::function OnStatus); void Repair(LViewI *Parent, LDataPropI *Props, std::function OnStatus); bool SetFormat(LViewI *Parent, LDataPropI *Props); void PostStore(LMail3StoreMsg *m) { Callback->Post(this, m); } 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,2202 +1,2222 @@ #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; } void LMail3Store::Upgrade(LViewI *Parent, LDataPropI *Props, std::function OnStatus) { bool Status = true; LStringPipe p; 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); } if (OnStatus) OnStatus(Status); } class SqliteRepairThread : public LThread { + LMail3Store *Store; LString Exe; LString Db; LString RepairSql; LString OldDb; - LViewI *Parent; - LDataPropI *Props; + LViewI *Parent = NULL; + LDataPropI *Props = NULL; LAutoPtr Shell; - ssize_t Ch; - char Line[512]; + ssize_t Ch = 0; + char Line[512] = {}; public: - bool Status; + bool Status = false; - SqliteRepairThread(LString exe, LString db, LViewI *parent, LDataPropI *props) : LThread("SqliteRepairThread") + SqliteRepairThread(LMail3Store *store, LString exe, LString db, LViewI *parent, LDataPropI *props) : + Store(store), + 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 OnStatus(int s) + { + // Send status event back to the store... + auto msg = new LMail3StoreMsg(LMail3StoreMsg::MsgRepairComplete); + msg->Int = s; + Store->PostStore(msg); + + return s; + } 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; + return OnStatus(-1); if (!Shell->Start(true, true)) { if (Props) Props->SetStr(Store3UiError, "Couldn't execute sqlite3 shell."); - return false; + return OnStatus(-2); } // 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; + return OnStatus(-3); StatusMsg("Setting mode..."); LString Cmd; Cmd.Printf(".mode insert\r\n"); Shell->Write(Cmd, Cmd.Length()); if (!GetPrompt()) - return -3; + return OnStatus(-4); StatusMsg("Setting output file..."); Cmd.Printf(".output \"%s\"\r\n", RepairSql.Get()); Shell->Write(Cmd, Cmd.Length()); if (!GetPrompt()) - return -4; + return OnStatus(-5); StatusMsg("Dumping SQL..."); Cmd.Printf(".dump\r\n"); Shell->Write(Cmd, Cmd.Length()); if (!GetPrompt()) - return -5; + return OnStatus(-6); 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; + return OnStatus(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 OnStatus) { // Is the sqlite3 shell binary available? char base[MAX_PATH_LEN]; char exe[MAX_PATH_LEN] = ""; LMakePath(base, sizeof(base), DbFile, ".."); #ifdef WINDOWS const char *SqliteBin = "sqlite3.exe"; LMakePath(exe, sizeof(exe), base, SqliteBin); #else const char *SqliteBin = "sqlite3"; CheckPathForFile(SqliteBin, exe, sizeof(exe)); #endif if (!LFileExists(exe)) { LString DownloadUrl = "https://www.sqlite.org/download.html"; LViewI *p = dynamic_cast(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); auto Dlg = new LAlert(p?p:Parent, "LMail3Store::Repair", Msg, "Browse Download Site & Local Folder", "Cancel"); Dlg->DoModal([this, Dlg, DownloadUrl, base](auto dlg, auto ctrlId) { if (ctrlId == 1) { LExecute(DownloadUrl); LExecute(base); } delete dlg; }); if (OnStatus) OnStatus(true); return; } // Close our database... CloseDb(); // Do the repair in a thread - SqliteRepairThread Worker(exe, DbFile, Parent, Props); - while (!Worker.IsExited()) - { - LSleep(20); - LYield(); - } - - OpenDb(); - - if (OnStatus) - OnStatus(Worker.Status); - return; + RepairOnStatus = OnStatus; + new SqliteRepairThread(this, exe, DbFile, Parent, Props); } 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; iSetStr(Store3UiError, s); } void SetStatus(const char *s) { Props->SetStr(Store3UiStatus, s); } void OnStatus(bool s) { // Send status event back to the store... auto msg = new LMail3StoreMsg(LMail3StoreMsg::MsgCompactComplete); msg->Int = s; Store->PostStore(msg); } 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."); OnStatus(false); return -1; } } // Clean up orphaned mail. 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 (!IsCancelled() && s.Row()) { int64 Id = s.GetInt64(0); Store->DeleteMailById(Id); Value = ++RowPos; } Max = 1; Value = 0; } // Vacuum if (!IsCancelled()) { SetStatus("Vacuum unused space..."); LMail3Store::LStatement s(Store, "vacuum;"); if (!s.Exec()) { SetError("Reclaiming space failed."); OnStatus(false); return -1; } } OnStatus(true); return 0; } }; 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."); { // Save this locally, because the thread isn't suitable for calling it. // Instead it will send us a message, and then the store can call it from // the GUI thread. This way any client implementing the callback can talk // to or delete UI elements. CompactOnStatus = OnStatus; new CompactThread(this, InboxId, Props); return; // without calling OnStatus, the CompactThread will do it. } } OnStatus(Status); } void LMail3Store::OnEvent(void *Param) { auto msg = (LMail3StoreMsg*)Param; switch (msg->Msg) { case LMail3StoreMsg::MsgCompactComplete: { if (CompactOnStatus) + { CompactOnStatus(msg->Int); + CompactOnStatus = NULL; + } + else LgiTrace("%s:%i - No CompactOnStatus to call on MsgCompactComplete.\n", _FL); + break; + } + case LMail3StoreMsg::MsgRepairComplete: + { + OpenDb(); + + if (RepairOnStatus) + { + RepairOnStatus(msg->Int >= 0); + RepairOnStatus = NULL; + } + break; + } + default: + { + LgiTrace("%s:%i - Unhandled mail3store event.\n", _FL); break; } } } 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); }