diff --git a/include/lgi/common/DbTable.h b/include/lgi/common/DbTable.h --- a/include/lgi/common/DbTable.h +++ b/include/lgi/common/DbTable.h @@ -1,160 +1,160 @@ #ifndef _DB_TABLE_H_ #define _DB_TABLE_H_ #include "lgi/common/Variant.h" #include "Store3.h" struct DbTablePriv; class LDbTable; struct LDbDate { size_t Sizeof(); bool Serialize(LPointer &p, LDateTime &dt, bool Write); }; struct LDbField { int Id; LVariantType Type; int Offset; int DataSize(); size_t Sizeof(); bool Serialize(LPointer &p, bool Write); }; class LDbRow : public LDataPropI { friend class LDbTable; friend struct DbTablePriv; // Global table specific data DbTablePriv *d; // The doubly linked list of rows. LDbRow *Next, *Prev; // This is the position in the tables read-only data // for this row. ssize_t Pos; // When editing a record, it can grow in size, so we copy the // Read-only data in the table into an edit buffer own by this // record. LArray Edit; // This pointers to the record data. // Format // uint32 Magic; // char FixedSizeData[d->FixedSz] // uint32 VariableOffsets[d->Variable] // char VariableData[??] LPointer Base; // This points to the offset data: // [0] -> Variable offset table (part of this record) // [1] -> Fixed offset table (owned by 'd') int32 *Offsets[2]; // Date cache LDateTime Cache; LDbRow(struct DbTablePriv *priv); bool StartEdit(); void PostEdit(); bool Compact(); uint32_t GetInitialSize(); public: static int HeaderSz; ~LDbRow(); // Fields size_t GetFields(); LDbField &GetField(size_t Idx); // Row level op bool Delete(); uint32_t Size(uint32_t Set = 0); LString ToString(); // Data access 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); LVariant *GetVar(int id); Store3Status SetVar(int id, LVariant *i); LDataPropI *GetObj(int id); Store3Status SetObj(int id, LDataPropI *i); - GDataIt GetList(int id); + LDataIt GetList(int id); Store3Status SetRfc822(LStreamI *Rfc822Msg); }; class DbIndex { DbTablePriv *d; public: DbIndex(DbTablePriv *priv); virtual ~DbIndex(); virtual bool OnNew(LDbRow *r) = 0; virtual bool OnDelete(LDbRow *r) = 0; }; class DbArrayIndex : public DbIndex, public LArray { friend class LDbTable; LDbField Fld; bool Ascend; DbArrayIndex(DbTablePriv *priv); bool Sort(LDbField *fld, bool ascend); public: bool OnNew(LDbRow *r); bool OnDelete(LDbRow *r); bool Resort(); }; class LDbTable { struct DbTablePriv *d; public: LDbTable(const char *File = NULL); ~LDbTable(); // Fields bool AddField(int Id, LVariantType Type); bool DeleteField(int Id); int GetFields(); LDbField &GetField(int Idx); // Rows bool Empty(); bool Iterate(LDbRow *&Ptr); int GetRows(); LDbRow *NewRow(); bool DeleteRow(LDbRow *r); /// This returns a sorted array of rows according to the specified /// id and sort direction. The array remains the property of the /// table. When done just free the index object. DbArrayIndex *Sort(int Id, bool Ascending = true); // IO bool Serialize(const char *Path, bool Write); // Testing LString ToString(); static bool UnitTests(); }; #endif diff --git a/include/lgi/common/Store3.h b/include/lgi/common/Store3.h --- a/include/lgi/common/Store3.h +++ b/include/lgi/common/Store3.h @@ -1,726 +1,726 @@ /// \file /// \author Matthew Allen, fret@memecode.com #ifndef _MAIL_STORE_H_ #define _MAIL_STORE_H_ #include #include "Mail.h" #include "Store3Defs.h" #undef GetObject /* Handling of attachments in the Store3 API ----------------------------------------- Given the mail object ptr: LDataI *m; Query that the mail for it's root mime segment: LDataI *Seg = dynamic_cast(m->GetObj(FIELD_MIME_SEG)); Query a seg for it's children: - GDataIt Children = Seg->GetList(FIELD_MIME_SEG); - LDataI *FirstChild = Children->First(); + auto Children = Seg->GetList(FIELD_MIME_SEG); + auto FirstChild = Children->First(); Access segment's charset and mimetype: char *Charset = Seg->GetStr(FIELD_CHARSET); char *MimeType = Seg->GetStr(FIELD_MIME_TYPE); Get/Set the segment for it's content: GAutoStreamI Data = Seg->GetStream(_FL); // get Seg->SetStream(new MyStream(Data)); // set Delete an mime segment: Seg->Delete(); Add a new segment somewhere in the tree, including reparenting it to another segments or mail object, even if the target parent it not attached yet: NewSeg->Save(ParentSeg); */ #include "LgiInterfaces.h" #include "lgi/common/Mime.h" #include "lgi/common/OptionsFile.h" #include "lgi/common/Variant.h" class LDataI; class LDataFolderI; class LDataStoreI; class LDataPropI; typedef LAutoPtr LAutoStreamI; void ParseIdList(char *In, List &Out); extern const char *Store3ItemTypeToMime(Store3ItemTypes type); /// A storage event /// a = StoreId /// b = (void*)UserParam /// \sa LDataEventsI::Post #define M_STORAGE_EVENT (M_USER+0x500) /// The storage class has this property (positive properties are owned by the app #define FIELD_IS_ONLINE -100 #define FIELD_PROFILE_IMAP_LISTING -101 #define FIELD_PROFILE_IMAP_SELECT -102 #define LDATA_INT32_PROP(name, id) \ int32 Get##name() { return GetObject() ? (int32)GetObject()->GetInt(id) : OnError(_FL); } \ bool Set##name(int32 val) { return GetObject() ? GetObject()->SetInt(id, val) >= Store3Delayed : OnError(_FL); } #define LDATA_INT64_PROP(name, id) \ int64 Get##name() { return GetObject() ? GetObject()->GetInt(id) : OnError(_FL); } \ bool Set##name(int64 val) { return GetObject() ? GetObject()->SetInt(id, val) >= Store3Delayed : OnError(_FL); } #define LDATA_ENUM_PROP(name, id, type) \ type Get##name() { return (type) (GetObject() ? GetObject()->GetInt(id) : OnError(_FL)); } \ bool Set##name(type val) { return GetObject() ? GetObject()->SetInt(id, (int)val) >= Store3Delayed : OnError(_FL); } #define LDATA_STR_PROP(name, id) \ const char *Get##name() { auto o = GetObject(); return o ? o->GetStr(id) : (const char*)OnError(_FL); } \ bool Set##name(const char *val) { return GetObject() ? GetObject()->SetStr(id, val) >= Store3Delayed : OnError(_FL); } #define LDATA_DATE_PROP(name, id) \ const LDateTime *Get##name() { return (GetObject() ? GetObject()->GetDate(id) : (LDateTime*)OnError(_FL)); } \ bool Set##name(const LDateTime *val) { return GetObject() ? GetObject()->SetDate(id, val) >= Store3Delayed : OnError(_FL); } #define LDATA_INT_TYPE_PROP(type, name, id, defaultVal) \ type Get##name() { return (type) (GetObject() ? GetObject()->GetInt(id) : OnError(_FL)); } \ bool Set##name(type val = defaultVal) { return GetObject() ? GetObject()->SetInt(id, val) >= Store3Delayed : OnError(_FL); } /// This class is an interface to a collection of objects (NOT thread-safe). typedef std::function LIteratorProgressFn; template class LDataIterator { public: virtual ~LDataIterator() {} /// \returns an empty object of the right type. virtual T Create(LDataStoreI *Store) = 0; /// \returns the first object (NOT thread-safe) virtual T First() = 0; /// \returns the first object (NOT thread-safe) virtual T Next() = 0; /// \returns the number of items in the collection virtual size_t Length() = 0; /// \returns the 'nth' item in the collection virtual T operator [](size_t idx) = 0; /// \returns the index of the given item in the collection virtual ssize_t IndexOf(T n, bool NoAssert = false) = 0; /// Deletes an item /// \returns true on success virtual bool Delete(T ptr) = 0; /// Inserts an item at 'idx' or the end if not supplied. /// \returns true on success virtual bool Insert(T ptr, ssize_t idx = -1, bool NoAssert = false) = 0; /// Clears list, but doesn't delete objects. /// \returns true on success virtual bool Empty() = 0; /// Deletes all the objects from memory /// \returns true on success virtual bool DeleteObjects() = 0; /// Gets the current loading/loaded state. virtual Store3State GetState() = 0; /// Sets the progress function virtual void SetProgressFn(LIteratorProgressFn cb) = 0; }; -typedef LDataIterator *GDataIt; +typedef LDataIterator *LDataIt; #define EmptyVirtual(t) LAssert(0); return t #define Store3CopyDecl bool CopyProps(LDataPropI &p) override #define Store3CopyImpl(Cls) bool Cls::CopyProps(LDataPropI &p) /// A generic interface for getting / setting properties. class LDataPropI : virtual public LDom { LDataPropI &operator =(LDataPropI &p) = delete; public: virtual ~LDataPropI() {} /// Copy all the values from 'p' over to this object virtual bool CopyProps(LDataPropI &p) { return false; } /// Gets a string property virtual const char *GetStr(int id) { EmptyVirtual(NULL); } /// Sets a string property, it will make a copy of the string, so you /// still retain ownership of the string you're passing in. virtual Store3Status SetStr(int id, const char *str) { EmptyVirtual(Store3Error); } /// Gets an integer property. virtual int64 GetInt(int id) { EmptyVirtual(false); } /// Sets an integer property. virtual Store3Status SetInt(int id, int64 i) { EmptyVirtual(Store3Error); } /// Gets a date property virtual const LDateTime *GetDate(int id) { EmptyVirtual(NULL); } /// Sets a date property virtual Store3Status SetDate(int id, const LDateTime *i) { EmptyVirtual(Store3Error); } /// Gets a variant virtual const LVariant *GetVar(int id) { EmptyVirtual(NULL); } /// Sets a variant property virtual Store3Status SetVar(int id, LVariant *i) { EmptyVirtual(Store3Error); } /// Gets a sub object pointer virtual LDataPropI *GetObj(int id) { EmptyVirtual(NULL); } /// Sets a sub object pointer virtual Store3Status SetObj(int id, LDataPropI *i) { EmptyVirtual(Store3Error); } /// Gets an iterator interface to a list of sub-objects. - virtual GDataIt GetList(int id) { EmptyVirtual(NULL); } + virtual LDataIt GetList(int id) { EmptyVirtual(NULL); } /// Set the mime segments virtual Store3Status SetRfc822(LStreamI *Rfc822Msg) { - LAssert(!"Pretty sure you should be implementing this") - ; return Store3Error; + LAssert(!"Pretty sure you should be implementing this"); + return Store3Error; } }; #pragma warning(default:4263) class LDataUserI { friend class LDataI; LDataI *Object; public: LString SetterRef; LDataUserI(); virtual ~LDataUserI(); LDataI *GetObject(); virtual bool SetObject ( /// The client side object to link with this object. LDataI *o, /// In the special case that 'Object' is being deleted, and is an /// orphaned objects, SetObject must not attempt to delete 'Object' /// a second time. This flag allows for that case. bool InDestuctor, /// The file name of the caller const char *File, /// The line number of the caller int Line ); }; -/// This class is an interface between the UI and the backend for things +/// This class is an interface between the UI and the back end for things /// like email, contacts, calendar events, groups and filters class LDataI : virtual public LDataPropI { friend class LDataUserI; LDataI &operator =(LDataI &p) = delete; public: LDataUserI *UserData; LDataI() { UserData = NULL; } virtual ~LDataI() { if (UserData) UserData->Object = NULL; } /// Returns the type of object /// \sa MAGIC_MAIL and it's like virtual uint32_t Type() = 0; /// \return true if the object has been written to disk. By default the object /// starts life in memory only. virtual bool IsOnDisk() = 0; /// \return true if the object is owned by some other object... virtual bool IsOrphan() = 0; /// \returns size of object on disk virtual uint64 Size() = 0; /// Saves the object to disk. If this function fails the object /// is deleted, so if it returns false, stop using the ptr you /// have to it. /// \returns true if successful. virtual Store3Status Save(LDataI *Parent = 0) = 0; /// Delete the on disk representation of the object. This will cause LDataEventsI::OnDelete /// to be called after which this object will be freed from heap memory automatically. So /// Once you call this method assume the object pointed at is gone. virtual Store3Status Delete(bool ToTrash = true) = 0; /// Gets the storage that this object belongs to. virtual LDataStoreI *GetStore() = 0; /// \returns a stream to access the data stored at this node. The caller /// is responsible to free the stream when finished with it. /// For Type == MAGIC_ATTACHMENT: the decoded body of the MIME segment. /// For Type == MAGIC_MAIL: is an RFC822 encoded version of the email. /// For other objects the stream is not defined. virtual LAutoStreamI GetStream(const char *file, int line) = 0; /// Sets the stream, which is used during the next call to LDataI::Save, which /// also deletes the object when it's used. The caller loses ownership of the /// object passed into this function. virtual bool SetStream(LAutoStreamI stream) { return false; } /// Parses the headers of the object and updates all the metadata fields virtual bool ParseHeaders() { return false; } }; /// An interface to a folder structure class LDataFolderI : virtual public LDataI { LDataFolderI &operator =(LDataFolderI &p) = delete; public: virtual ~LDataFolderI() {} /// \returns an iterator for the sub-folders. virtual LDataIterator &SubFolders() = 0; /// \returns an iterator for the child objects virtual LDataIterator &Children() = 0; /// \returns an iterator for the fields this folder defines virtual LDataIterator &Fields() = 0; /// Deletes all child objects from disk and memory. /// \return true on success; virtual Store3Status DeleteAllChildren() { return Store3Error; } /// Frees all the memory used by children objects without deleting from disk virtual Store3Status FreeChildren() { return Store3Error; } /// Called when the user selects the folder in the UI virtual void OnSelect(bool s) {} /// Called when the user selects a relevant context menu command virtual void OnCommand(const char *Name) {} }; #pragma warning(error:4263) /// Event callback interface. Calls to these methods may be in a worker /// thread, so make appropriate locking or pass the event off to the GUI /// thread via a message. class LDataEventsI { public: virtual ~LDataEventsI() {} /// This allows the caller to pass source:line info for debugging /// It should be called prior to one of the following functions and /// expires immediately after the function call. virtual void SetContext(const char *file, int line) {} /// Posts something to the GUI thread /// \sa M_STORAGE_EVENT virtual void Post(LDataStoreI *store, void *Param) {} /// \returns the system path virtual bool GetSystemPath(int Folder, LVariant &Path) { return false; } /// \returns the options object virtual LOptionsFile *GetOptions(bool Create = false) { return 0; } /// A new item is available virtual void OnNew(LDataFolderI *parent, LArray &new_items, int pos, bool is_new) = 0; /// When an item is deleted virtual bool OnDelete(LDataFolderI *parent, LArray &items) = 0; /// When an item is moved to a new folder virtual bool OnMove(LDataFolderI *new_parent, LDataFolderI *old_parent, LArray &items) = 0; /// When an item changes virtual bool OnChange(LArray &items, int FieldHint) = 0; /// Notifcation of property change virtual void OnPropChange(LDataStoreI *Store, int Prop, LVariantType Type) {} /// Get the logging stream virtual LStreamI *GetLogger(LDataStoreI *store) { return 0; } /// Search for a object by type and name virtual bool Match(LDataStoreI *store, LDataPropI *Addr, int ObjectType, LArray &Matches) { return 0; } }; /// The virtual mail storage interface from which all mail stores inherit from. /// /// The data store should implement LDataPropI::GetInt and handle these properties: /// - FIELD_STATUS, acceptable returns value are: /// * 0 - mail store is in error. /// * 1 - mail store is ready to use / ok. /// * 2 - mail store requires upgrading to use. /// These are codified in the enum LDataStoreI::DataStoreStatus /// - FIELD_READONLY, return values are: /// * false - mail store is read/write /// * true - mail store is read only /// - FIELD_VERSION, existing return values are: /// * 3 - a 'mail3' Scribe sqlite database. /// * 10 - the Scribe IMAP implementation. /// If you are create a new mail store use, values above 10 for your implementation /// and optionally register them with Memecode for inclusion here. /// - FIELD_IS_ONLINE, optionally returned if mail store is online or not. /// - FIELD_ACCOUNT_ID, optionally return if the mail store is associated with an account. /// /// The data store may optionally implement LDataPropI::SetInt to handle this property: /// - FIELD_IS_ONLINE, acceptable values are: /// * false - take the mail store offline. /// * true - go online. /// This is currently only implemented on the IMAP mail store. class LDataStoreI : virtual public LDataPropI { public: static LHashTbl,LDataStoreI*> Map; int Id; class LDsTransaction { protected: LDataStoreI *Store; public: LDsTransaction(LDataStoreI *s) { Store = s; } virtual ~LDsTransaction() {} }; typedef LAutoPtr StoreTrans; LDataStoreI() { LAssert(LAppInst->InThread()); while (Map.Find(Id = LRand(1000))) ; Map.Add(Id, this); } virtual ~LDataStoreI() { LAssert(LAppInst->InThread()); if (!Map.Delete(Id)) LAssert(!"Delete failed."); } /// \returns size of object on disk virtual uint64 Size() = 0; /// Create a new data object that isn't written to disk yet virtual LDataI *Create(int Type) = 0; /// Get the root folder object virtual LDataFolderI *GetRoot(bool create = false) = 0; /// Move objects into a different folder. /// /// Success: /// 'Items' are owned by 'NewFolder', and not any previous folder. /// Any LDataEventsI interface owned by the data store has it's 'OnMove' method called. /// /// Failure: /// 'Item' is owned by it's previous folder. /// /// \return true on success. virtual Store3Status Move ( /// The folder to move the object to LDataFolderI *NewFolder, /// The object to move LArray &Items ) = 0; /// Deletes items, which results in either the items being moved to the local trash folder /// or the items being completely deleted if there is no local trash. The items should all /// be in the same folder and of the same type. /// /// Success: /// 'Items' are either owned by the local trash and not any previous folder, and /// LDataEventsI::OnMove is called. /// -or- /// 'Items' are completely deleted and removed from it's parent and /// LDataEventsI::OnDelete is called, after which the objects are freed. /// /// Failure: /// 'Items' are owned by it's previous folder. /// /// \return true on success. virtual Store3Status Delete ( /// The object to delete LArray &Items, /// Send to the trash or not... bool ToTrash ) = 0; /// Changes items, which results in either the items properties being adjusted. /// /// Success: /// The items properties are changed, and the LDataEventsI::OnChange callback /// is made. /// /// Failure: /// Items are not changed. No callback is made. /// /// \return true on success. virtual Store3Status Change ( /// The object to change LArray &Items, /// The property to change... int PropId, /// The value to assign /// (GV_INT32/64 -> SetInt, GV_DATETIME -> SetDateTime, GV_STRING -> SetStr) LVariant &Value, /// Optional operator for action LOperator Operator ) = 0; /// Compact the mail store virtual void Compact ( /// The parent window of the UI LViewI *Parent, /// The store should pass information up to the UI via setting various parameters from Store3UiFields LDataPropI *Props, /// The callback to get status, could be called by a worker thread... std::function OnStatus ) = 0; /// Upgrades the mail store to the current version for this build. You should call this in response /// to getting Store3UpgradeRequired back from this->GetInt(FIELD_STATUS). virtual void Upgrade ( /// The parent window of the UI LViewI *Parent, /// The store should pass information up to the UI via setting various parameters from Store3UiFields LDataPropI *Props, /// The callback to get status, could be called by a worker thread... std::function OnStatus ) { if (OnStatus) OnStatus(false); } /// Tries to repair the database. virtual void Repair ( /// The parent window of the UI LViewI *Parent, /// The store should pass information up to the UI via setting various parameters from Store3UiFields LDataPropI *Props, /// The callback to get status, could be called by a worker thread... std::function OnStatus ) { if (OnStatus) OnStatus(false); } /// Set the sub-format virtual bool SetFormat ( /// The parent window of the UI LViewI *Parent, /// The store should pass information up to the UI via setting various parameters from Store3UiFields LDataPropI *Props ) { return false; } /// Called when event posted virtual void OnEvent(void *Param) = 0; /// Called when the application is not receiving messages. /// \returns false to wait for more messages. virtual bool OnIdle() = 0; /// Gets the events interface virtual LDataEventsI *GetEvents() = 0; /// Start a scoped transaction virtual StoreTrans StartTransaction() { return StoreTrans(0); } }; /// Open a mail3 folder /// \return a valid ptr or NULL on failure extern LDataStoreI *OpenMail3 ( /// The file to open const char *Mail3Folder, /// Event interface, LDataEventsI *Callback, /// true if you want to create a new mail3 file. bool Create = false ); /// Open am imap store /// \return a valid ptr or NULL on failure extern LDataStoreI *OpenImap ( /// The host name of the IMAP server char *Host, /// The port to connect to, or <= 0 means use default int Port, /// The user name of the account to connect to char *User, /// [Optional] The password of the user char *Pass, /// Various flags that control the type of connection made: /// \sa #MAIL_SSL, #MAIL_SECURE_AUTH int ConnectFlags, /// Callback interface for various events... LDataEventsI *Callback, /// This allows the IMAP client to request SSL support from the /// parent applications. LCapabilityClient *caps, /// Pointers to the progress info bars, or NULL if not needed. MailProtocolProgress *prog[2], /// The logging stream. LStream *Log, /// The identifier for the account int AccoundId, /// An interface into the persistant storage area. LAutoPtr store ); #ifdef WIN32 /// Open a MAPI store /// \return a valid ptr or NULL on failure extern LDataStoreI *OpenMapiStore ( /// The MAPI profile name const char *Profile, /// The username to login as const char *Username, /// Their password const char *Password, /// The account ID uint64 AccountId, /// Event interface, LDataEventsI *Callback ); #endif ////////////////////////////////////////////////////////////////////////////// // Common implementation of interfaces template class DNullIterator : public LDataIterator { public: T First() { return 0; } T Next() { return 0; } int Length() { return 0; } T operator [](int idx) { return 0; } bool Delete(T ptr) { return 0; } bool Insert(T ptr, int idx = -1, bool NoAssert = false) { return 0; } bool DeleteObjects() { return 0; } bool Empty() { return false; } int IndexOf(T n, bool NoAssert = false) { return -1; } }; template class DIterator : public LDataIterator { int Cur; public: LArray a; Store3State State; LIteratorProgressFn Prog; DIterator() { Cur = -1; State = Store3Unloaded; } Store3State GetState() { return State; } void SetProgressFn(LIteratorProgressFn cb) { Prog = cb; } void Swap(DIterator &di) { LSwap(Cur, di.Cur); LSwap(State, di.State); a.Swap(di.a); } TPub *Create(LDataStoreI *Store) { LAssert(State == Store3Loaded); return new TPriv(dynamic_cast(Store)); } TPub *First() { LAssert(State == Store3Loaded); Cur = 0; return (int)a.Length() > Cur ? a[Cur] : 0; } TPub *Next() { LAssert(State == Store3Loaded); Cur++; return (int)a.Length() > Cur ? a[Cur] : 0; } size_t Length() { return a.Length(); } TPub *operator [](size_t idx) { LAssert(State == Store3Loaded); return a[idx]; } bool Delete(TPub *pub_ptr) { LAssert(State == Store3Loaded); TPriv *priv_ptr = dynamic_cast(pub_ptr); if (!priv_ptr) { LAssert(!"Not the right type of object."); return false; } ssize_t i = a.IndexOf(priv_ptr); if (i < 0) return false; a.DeleteAt(i, true); return true; } bool Insert(TPub *pub_ptr, ssize_t idx = -1, bool NoAssert = false) { if (!NoAssert) LAssert(State == Store3Loaded); TPriv *priv_ptr = dynamic_cast(pub_ptr); if (!priv_ptr) { LAssert(!"Not the right type of object."); return false; } return a.AddAt(idx < 0 ? a.Length() : idx, priv_ptr); } bool Empty() { LAssert(State == Store3Loaded); a.Length(0); return true; } bool DeleteObjects() { a.DeleteObjects(); return true; } ssize_t IndexOf(TPub *pub_ptr, bool NoAssert = false) { if (!NoAssert) LAssert(State == Store3Loaded); TPriv *priv_ptr = dynamic_cast(pub_ptr); if (!priv_ptr) { LAssert(!"Not the right type of object."); return -1; } return a.IndexOf(priv_ptr); } }; #endif diff --git a/include/lgi/common/Store3Defs.h b/include/lgi/common/Store3Defs.h --- a/include/lgi/common/Store3Defs.h +++ b/include/lgi/common/Store3Defs.h @@ -1,456 +1,456 @@ #ifndef _STORE3_DEFS_H_ #define _STORE3_DEFS_H_ ////////////////////////////////////////////////////////////////////////////////////////////////////// // CORE DEFINITIONS (relevant to any implementation) ////////////////////////////////////////////////////////////////////////////////////////////////////// /// Load state /// /// This is used by both objects AND iterators enum Store3State { /// The object is not loaded at all.. Store3Unloaded, /// The object is currently loading (in some worker thread or something) /// Best to not try and access anything like data fields. Store3Loading, /// The mail object has only it's headers loaded, and not the body. Store3Headers, /// The object is fully loaded and can be accessed normally. Store3Loaded, }; inline const char *toString(Store3State s) { #define _(s) case s: return #s; switch (s) { _(Store3Unloaded) _(Store3Loading) _(Store3Headers) _(Store3Loaded) } #undef _ LAssert(0); return "#invalidStore3State"; } /// Folder system type enum Store3SystemFolder { Store3SystemNone, Store3SystemInbox, Store3SystemTrash, Store3SystemOutbox, Store3SystemSent, Store3SystemCalendar, Store3SystemContacts, Store3SystemSpam, }; inline const char *toString(Store3SystemFolder s) { #define _(s) case s: return #s; switch (s) { _(Store3SystemNone) _(Store3SystemInbox) _(Store3SystemTrash) _(Store3SystemOutbox) _(Store3SystemSent) _(Store3SystemCalendar) _(Store3SystemContacts) _(Store3SystemSpam) } #undef _ LAssert(0); return "#invalidStore3SystemFolder"; } /// This defines the possible outcomes of calling a function. enum Store3Status { // Open mail store specific: //-------------------------------------------------------------------------------- /// The store file is missing Store3Missing = -1, /// A format upgrade is required to open the store. Store3UpgradeRequired = -2, /// Function not implemented. Store3NotImpl = -3, /// Missing permissions for operation. Store3NoPermissions = -4, // General: //-------------------------------------------------------------------------------- /// The method failed and no action was taken. Store3Error = 0, /// The method succeeded but the action was not completed immediately, notification /// of the actions completion will come later via the callback interface. Store3Delayed = 1, /// The method succeeded and the action has been already completed. Store3Success = 2, }; /// Possible parts of UI enum Store3UiFields { Store3UiCurrentPos, // [Set] sets the current progress value Store3UiMaxPos, // [Set] sets the maximum progress value Store3UiStatus, // [Set] set a status/progress string Store3UiError, // [Set] set an error string Store3UiInteractive, // [Get] returns a bool if the user is expecting interaction Store3UiCancel, // [Get] returns a bool indicating if the user has cancelled the operation Store3UiNewFormat, // [Get] returns a integer/enum describing the new format to use }; enum Store3Backend { Store3Sqlite, Store3Imap, Store3Mapi, Store3Webdav, }; ////////////////////////////////////////////////////////////////////////////////////////////////////// // MAIL/CALENDAR CLIENT RELATED DEFINITIONS ////////////////////////////////////////////////////////////////////////////////////////////////////// enum EmailFlags { // Mail flags MAIL_SENT = (1 << 0), MAIL_RECEIVED = (1 << 1), MAIL_CREATED = (1 << 2), MAIL_FORWARDED = (1 << 3), MAIL_REPLIED = (1 << 4), MAIL_ATTACHMENTS = (1 << 5), MAIL_READ = (1 << 6), // #define MAIL_MARK (1 << 7), // Deprecated MAIL_READY_TO_SEND = (1 << 8), // If this flag is set then the user // wants to send the mail on the next // run. When the user just saves a new // message in the outbox this isn't set // and isn't sent until they go in and // say "send". At which point this flag // is set and the message sent MAIL_READ_RECEIPT = (1 << 9), MAIL_IGNORE = (1 << 10), MAIL_FIXED_WIDTH_FONT = (1 << 11), MAIL_BOUNCED = (1 << 12), // The bounce source mail MAIL_BOUNCE = (1 << 13), // The outgoing copy of a bounced mail MAIL_SHOW_IMAGES = (1 << 14), // User selected to show images in HTML MAIL_NEW = (1 << 18), // Mail is new, cleared after the OnNew event happens MAIL_STORED_FLAT = (1 << 19), // Message is signed and/or encrypted and needs // to be stored in such a way as the RFC822 image // is not damaged. // Bayesian filter flags: MAIL_BAYES_HAM = (1 << 16), // Bayesian classified originally as ham MAIL_BAYES_SPAM = (1 << 17), // Bayesian classified originally as spam MAIL_HAM_DB = (1 << 20), // In Bayesian ham word database MAIL_SPAM_DB = (1 << 21), // In Bayesian spam word database }; extern LString EmailFlagsToStr(int flags); enum EmailAddressType { MAIL_ADDR_TO = 0, MAIL_ADDR_CC = 1, MAIL_ADDR_BCC = 2, MAIL_ADDR_FROM = 3, }; enum EmailPriority { // FIELD_PRIORITY is equivilent to the header field: X-Priority // 1 - High // ... // 5 - Low MAIL_PRIORITY_HIGH = 1, MAIL_PRIORITY_NORMAL = 3, MAIL_PRIORITY_LOW = 5, }; enum CalendarReminderType { CalEmail, CalPopup, CalScriptCallback, CalMaxType, }; enum CalendarReminderUnits { CalMinutes, CalHours, CalDays, CalWeeks, CalMaxUnit, }; enum CalendarShowTimeAs { CalFree = 0, CalTentative = 1, CalBusy = 2, CalOut = 3 }; enum CalendarType { CalEvent, CalTodo, CalJournal, CalRequest, CalReply, }; enum CalendarPrivacyType { CalDefaultPriv = 0, CalPublic, CalPrivate }; // To convert to string use 'Store3ItemTypeName' enum Store3ItemTypes { MAGIC_NONE = 0x00000000, MAGIC_BASE = 0xAAFF0000, MAGIC_MAIL = (MAGIC_BASE+1), // Mail item MAGIC_CONTACT = (MAGIC_BASE+2), // Contact item MAGIC_FOLDER_OLD = (MAGIC_BASE+3), // Old Store1 folder of items MAGIC_MAILBOX = (MAGIC_BASE+4), // Root of mail tree (nothing-abstract) MAGIC_ATTACHMENT = (MAGIC_BASE+5), // Mail attachment MAGIC_ANY = (MAGIC_BASE+6), // Trash folder type (accepts any object) MAGIC_FILTER = (MAGIC_BASE+7), // Used to match messages against MAGIC_FOLDER = (MAGIC_BASE+8), // Folder v2 MAGIC_CONDITION = (MAGIC_BASE+9), // Filter condition MAGIC_ACTION = (MAGIC_BASE+10), // Filter action MAGIC_CALENDAR = (MAGIC_BASE+11), // Calendar event MAGIC_ATTENDEE = (MAGIC_BASE+12), // Event attendee MAGIC_GROUP = (MAGIC_BASE+13), // Group of contacts MAGIC_MAX = (MAGIC_BASE+14) // One past the end }; // When setting this via LDataI::SetInt the return value is: // - true if you need to mark the object dirty so that it gets saved // - false if the flags is stored elsewhere and you don't have to save. enum Store3Fields { FIELD_NULL = 0, FIELD_FLAGS = 1, // (EmailFlags) The message flags FIELD_TO = 2, // (DIterator) List of recipients FIELD_CC = 3, // (EmailAddressType) FIELD_FROM = 4, // (Store3Addr*) The From address FIELD_REPLY = 5, // (Store3Addr*) The Reply-To address FIELD_SUBJECT = 6, // (char*) Subject of the message FIELD_TEXT = 7, // (char*) Textual body FIELD_MESSAGE_ID = 8, // (char*) The message ID FIELD_DATE_RECEIVED = 9, // (LDateTime*) Received date FIELD_INTERNET_HEADER = 10, // (char*) The internet headers // Contact fields FIELD_FIRST_NAME = 11, // (char*) First name FIELD_LAST_NAME = 12, // (char*) Last/surname FIELD_EMAIL = 13, // (char*) The default email address // \sa 'FIELD_ALT_EMAIL' FIELD_HOME_STREET = 14, // (char*) FIELD_HOME_SUBURB = 15, // (char*) FIELD_HOME_POSTCODE = 16, // (char*) FIELD_HOME_STATE = 17, // (char*) FIELD_HOME_COUNTRY = 18, // (char*) FIELD_WORK_PHONE = 19, // (char*) This was already defined FIELD_HOME_PHONE = 20, // (char*) FIELD_HOME_MOBILE = 21, // (char*) FIELD_HOME_IM = 22, // (char*) FIELD_HOME_FAX = 23, // (char*) FIELD_HOME_WEBPAGE = 24, // (char*) FIELD_NICK = 25, // (char*) FIELD_SPOUSE = 26, // (char*) FIELD_NOTE = 27, // (char*) FIELD_PLUGIN_ASSOC = 28, // (char*) // More fields FIELD_SIZE = 29, // (uint64) FIELD_DATE_SENT = 30, // (LDateTime*) FIELD_COLUMN = 31, // (uint64) // FIELD_BCC = 32, // Deprecated FIELD_MIME_TYPE = 33, // (char*) The MIME type FIELD_PRIORITY = 34, // (EmailPriority) FIELD_FOLDER_OPEN = 35, // (bool) True if the folder is expanded to show child folders // FIELD_CODE_PAGE = 36, // Deprecated FIELD_COLOUR = 37, // (uint32) Rgba32 FIELD_ALTERNATE_HTML = 38, // (char*) The HTML part of the message FIELD_CONTENT_ID = 39, // (char*) An attachment content ID // Filter fields FIELD_FILTER_NAME = 40, // (char*) // FIELD_ACT_TYPE = 46, // Deprecated // FIELD_ACT_ARG = 47, // Deprecated // FIELD_DIGEST_INDEX = 48, // Deprecated FIELD_COMBINE_OP = 49, FIELD_FILTER_INDEX = 50, FIELD_FILTER_SCRIPT = 52, FIELD_STOP_FILTERING = 54, FIELD_FILTER_INCOMING = 55, FIELD_FILTER_OUTGOING = 56, FIELD_FILTER_CONDITIONS_XML = 57, FIELD_FILTER_ACTIONS_XML = 58, FIELD_FILTER_INTERNAL = 59, // Calendar fields // FIELD_CAL_START_LOCAL = 60, // **deprecated** // FIELD_CAL_END_LOCAL = 61, // **deprecated** FIELD_CAL_SUBJECT = 62, // (char*) event title FIELD_CAL_LOCATION = 63, // (char*) location of event FIELD_CAL_ALL_DAY = 64, // (bool) All Day setting // FIELD_CAL_REMINDER_ACTION = 65, // **deprecated** (CalendarReminderType) The reminder type // FIELD_CAL_REMINDER_UNIT = 66, // **deprecated** (CalendarReminderUnits) The unit of time FIELD_CAL_SHOW_TIME_AS = 67, // (CalendarShowTimeAs) Busy/Free etc FIELD_CAL_RECUR_FREQ = 68, // (CalRecurFreq) Base time unit of recurring: e.g. Day/Week/Month/Year. FIELD_CAL_RECUR_INTERVAL = 69, // (int) Number of FIELD_CAL_RECUR_FREQ units of time between recurring events. FIELD_CAL_RECUR_FILTER_DAYS = 70 , // (int) Bitmask of days: Bit 0 = Sunday, Bit 1 = Monday etc FIELD_CAL_RECUR_FILTER_MONTHS = 71, // (int) Bitmask of months: Bit 0 = January, Bit 1 = Feburary etc FIELD_CAL_RECUR_FILTER_YEARS = 72, // (char*) Comma separated list of years to filter on. FIELD_CAL_NOTES = 73, // (char*) Textual notes FIELD_CAL_TYPE = 74, // (CalendarType) The type of event FIELD_CAL_COMPLETED = 75, // (int) 0 -> 100% FIELD_CAL_START_UTC = 76, // (LDateTime*) The start time and date FIELD_CAL_END_UTC = 77, // (LDateTime*) The end time and date FIELD_CAL_RECUR_FILTER_POS = 78, // (char*) Comma separated list of integers defining positions in the month to filter on. FIELD_CAL_RECUR_END_DATE = 79, // (LDateTime*) The end of recurence if FIELD_CAL_RECUR_END_TYPE == CalEndOnDate. FIELD_CAL_RECUR_END_COUNT = 80, // (int) Times to recur if FIELD_CAL_RECUR_END_TYPE == CalEndOnCount. FIELD_CAL_RECUR_END_TYPE = 81, // (CalRecurEndType) Which ending to use... needs an enum FIELD_CAL_RECUR = 82, // (int) true if the event recurs. FIELD_CAL_TIMEZONE = 83, // (char*) The timezone as text FIELD_CAL_PRIVACY = 84, // (CalendarPrivacyType) The privacy setting // Attendee fields FIELD_ATTENDEE_JSON = 85, // FIELD_ATTENDEE_EMAIL = 86, // FIELD_ATTENDEE_ATTENDENCE = 87, // FIELD_ATTENDEE_NOTE = 88, // FIELD_ATTENDEE_RESPONSE = 89, // 2nd lot of contact fields FIELD_WORK_STREET = 90, // (char*) FIELD_WORK_SUBURB = 91, // (char*) FIELD_WORK_POSTCODE = 92, // (char*) FIELD_WORK_STATE = 93, // (char*) FIELD_WORK_COUNTRY = 94, // (char*) // #define FIELD_WORK_PHONE is previously defined FIELD_WORK_MOBILE = 95, // (char*) FIELD_WORK_IM = 96, // (char*) FIELD_WORK_FAX = 97, // (char*) FIELD_WORK_WEBPAGE = 98, // (char*) FIELD_COMPANY = 99, // (char*) /* Deprecated FIELD_CONTACT_CUST_FLD1 = 100, // (char*) FIELD_CONTACT_CUST_VAL1 = 101, // (char*) FIELD_CONTACT_CUST_FLD2 = 102, // (char*) FIELD_CONTACT_CUST_VAL2 = 103, // (char*) FIELD_CONTACT_CUST_FLD3 = 104, // (char*) FIELD_CONTACT_CUST_VAL3 = 105, // (char*) FIELD_CONTACT_CUST_FLD4 = 106, // (char*) FIELD_CONTACT_CUST_VAL4 = 107, // (char*) */ FIELD_CONTACT_IMAGE = 108, // (LSurface*) FIELD_CONTACT_JSON = 109, // (char*) // Misc additional fields FIELD_LABEL = 110, // (char*) Mail label FIELD_CHARSET = 111, // (char*) A character set FIELD_ALT_EMAIL = 112, // (char*) Comma separated list of alternative, // non-default email addresses. The default addr // is stored under 'FIELD_EMAIL' FIELD_UID = 113, // (char*) FIELD_TITLE = 114, // (char*) FIELD_TIMEZONE = 115, // (char*) FIELD_REFERENCES = 116, // (char*) FIELD_SERVER_UID = 117, // (int64) Server identifier FIELD_FOLDER_PERM_READ = 118, // (int64) FIELD_FOLDER_PERM_WRITE = 119, // (int64) FIELD_GROUP_NAME = 120, // (char*) The name of a contact group FIELD_HTML_CHARSET = 121, // (char*) The character set of the HTML body part FIELD_POSITION = 122, // (char*) Role in organisation FIELD_GROUP_LIST = 123, // (char*) FIELD_FOLDER_THREAD = 124, // (int64) FIELD_ACCOUNT_ID = 125, // (int64) The ID of an account FIELD_FROM_CONTACT_NAME = 126, // (char*) Not used at the Store3 level FIELD_FWD_MSG_ID = 127, // (char*) Mail::FwdMsgId FIELD_FOLDER_TYPE = 128, // (Store3ItemTypes) FIELD_FOLDER_NAME = 129, // (char*) Name of the folder FIELD_UNREAD = 130, // (int64) Count of unread items FIELD_SORT = 131, // (int64) Sort setting FIELD_STATUS = 132, // (Store3Status) FIELD_VERSION = 133, // (int64) FIELD_ID = 134, // (int64) FIELD_READONLY = 135, // (bool) FIELD_NAME = 136, // (char*) FIELD_WIDTH = 137, // (int64) // #define FIELD_ATTACHMENT_CONTENT 139 - Deprecated // #define FIELD_ATTACHMENTS 140 - Deprecated, use FIELD_MIME_SEG instead FIELD_CACHE_FILENAME = 141, // (char*) IMAP backend: the filename FIELD_CACHE_FLAGS = 142, // (char*) IMAP backend: IMAP flags FIELD_DONT_SHOW_PREVIEW = 143, // (bool) FIELD_STORE_PASSWORD = 144, // (char*) FIELD_LOADED = 145, // (Store3State) FIELD_DEBUG = 146, // (char*) - FIELD_MIME_SEG = 147, // (GDataIt) + FIELD_MIME_SEG = 147, // (LDataIt) FIELD_STORE_TYPE = 148, // (Store3Backend) FIELD_BOUNCE_MSG_ID = 149, // (char*) FIELD_FOLDER_INDEX = 150, // (int64) FIELD_FORMAT = 151, // (int64) FIELD_SYSTEM_FOLDER = 152, // (Store3SystemFolder) FIELD_ERROR = 153, // (char*) An error message FIELD_TYPE = 154, // (char*) The type of the object FIELD_ATTACHMENTS_DATA = 155, // Meta field for specifying attachment data contents FIELD_ATTACHMENTS_NAME = 156, // Meta field for specifying attachment file names FIELD_MEMBER_OF_GROUP = 157, // Meta field for specifying membership of a content group FIELD_TEMP_PATH = 158, // (char*) A temporary path to store files... FIELD_HTML_RELATED = 159, // Array of related attachments for the HTML content. // Pass this to an email's SetObj member to add a // related attachment. FIELD_CAL_REMINDERS = 160, // Individual reminders as CSV, fields are: // Number, CalendarReminderUnits, CalendarReminderType, Param // // CalEmail: param option email address to send to as well as the guests // CalPopup: not used // CalScriptCallback: not impl (but will be the script function and args) // FIELD_CAL_LAST_CHECK = 161, // (LDateTime) Ts the calendar event was last checked for reminders FIELD_DATE_MODIFIED = 162, // (LDateTime) Ts of modification FIELD_INBOX = 163, // (LDataFolderI*) Inbox for mail store FIELD_OUTBOX = 164, // (LDataFolderI*) Outbox for mail store FIELD_SENT = 165, // (LDataFolderI*) Sent folder for mail store FIELD_TRASH = 166, // (LDataFolderI*) Trash folder for mail store FIELD_IMAP_SEQ = 167, // (uint32_t) IMAP sequence number FIELD_CAL_STATUS = 168, // (char*) Status of the vCal event. FIELD_STORE_STATUS = 169, // (ScribeAccountletStatusIcon) Status (icon) of a LDataStoreI FIELD_RECEIVED_DOMAIN = 170, // (char*) First "Received:" header domain. (See also SdReceivedDomain) FIELD_FOLDER_ITEMS = 171, // (int64) Number of items in a folder.. FIELD_MAX, }; #endif diff --git a/include/lgi/common/Store3MimeTree.h b/include/lgi/common/Store3MimeTree.h --- a/include/lgi/common/Store3MimeTree.h +++ b/include/lgi/common/Store3MimeTree.h @@ -1,351 +1,345 @@ #ifndef _STORE3_MIME_TREE_H_ #define _STORE3_MIME_TREE_H_ /* This class can build a mime type from segments. Example valid mime trees: text/plain text/html multipart/alternative text/plain text/html multipart/mixed text/plain application/pdf application/octet-stream multipart/mixed multipart/alternative text/plain text/html application/octet-stream multipart/mixed multipart/alternative text/plain multipart/related text/html image/jpeg image/png application/octet-stream multipart/related text/html image/jpeg image/png */ template class Store3MimeTree { - bool InRelated; - TMail *Mail; - TStore *Store; + bool InRelated = false; + TMail *Mail = NULL; + TStore *Store = NULL; public: TAttachment *&Root; - TAttachment *MsgText; - TAttachment *MsgHtml; + TAttachment *MsgText = NULL; + TAttachment *MsgHtml = NULL; LArray MsgHtmlRelated; - TAttachment *Related; - TAttachment *Alternative; - TAttachment *Mixed; + TAttachment *Related = NULL; + TAttachment *Alternative = NULL; + TAttachment *Mixed = NULL; LArray Attachments; LArray Unknown; Store3MimeTree(TMail *mail, TAttachment *&root) : Root(root) { Mail = mail; Store = dynamic_cast(Mail->GetStore()); - MsgText = NULL; - MsgHtml = NULL; - Related = NULL; - Alternative = NULL; - Mixed = NULL; - InRelated = false; - if (Root) Scan(Root); } void Scan(TAttachment *Seg) { if (!Root) Root = Seg; bool PrevInRelated = InRelated; Add(Seg); - GDataIt It = Seg->GetList(FIELD_MIME_SEG); + auto It = Seg->GetList(FIELD_MIME_SEG); if (It) { - for (LDataPropI *i=It->First(); i; i=It->Next()) + for (auto i = It->First(); i; i=It->Next()) { - TAttachment *a = dynamic_cast(i); - if (!a) continue; + auto a = dynamic_cast(i); + if (!a) + continue; Scan(a); } } InRelated = PrevInRelated; } void Add(TAttachment *Seg) { if (Seg->IsMixed()) { if (Mixed) Unknown.Add(Seg); else Mixed = Seg; } else if (Seg->IsAlternative()) { if (Alternative) Unknown.Add(Seg); else Alternative = Seg; } else if (Seg->IsRelated()) { if (Related) Unknown.Add(Seg); else Related = Seg; InRelated = true; } else if (Seg->IsPlainText()) { if (MsgText) Attachments.Add(Seg); else MsgText = Seg; } else if (Seg->IsHtml()) { if (MsgHtml) Attachments.Add(Seg); else MsgHtml = Seg; } else if (Seg->IsMultipart()) { Unknown.Add(Seg); } else { if (InRelated) MsgHtmlRelated.Add(Seg); else Attachments.Add(Seg); } } bool Build() { if (Attachments.Length() > 0) { // We must have multipart/mixed at the root if (!Root || !Root->IsMixed()) { if (Mixed) { // hmmmm, an existing mixed but not root? Mixed->Detach(); Root = Mixed; } else { // Create new mixed root Root = new TAttachment(Store); if (!Root) return false; Mixed = Root; Root->SetStr(FIELD_MIME_TYPE, sMultipartMixed); } } if (Mixed) { LAssert(Mixed->IsMixed()); // Add all our attachments now for (unsigned i=0; iAttachTo(Mixed); } } TAttachment *Html = NULL; if (MsgHtml) { if (MsgHtmlRelated.Length() > 0) { // Create the HTML related tree if needed if (!Related) { Related = new TAttachment(Store); if (!Related) return false; Related->SetStr(FIELD_MIME_TYPE, sMultipartRelated); } MsgHtml->AttachTo(Related); for (unsigned i=0; iAttachTo(Related); Html = Related; } else { Html = MsgHtml; } } if (Html && MsgText) { // We need an alternative if (!Alternative) { Alternative = new TAttachment(Store); if (!Alternative) return false; Alternative->SetStr(FIELD_MIME_TYPE, sMultipartAlternative); } Html->AttachTo(Alternative); MsgText->AttachTo(Alternative); if (Root && Root->IsMultipart() && Root != Alternative) Alternative->AttachTo(Root); else Root = Alternative; } else if (MsgText) { if (Root && Root->IsMultipart()) MsgText->AttachTo(Root); else Root = MsgText; } else if (Html) { if (Root && Root->IsMultipart()) Html->AttachTo(Root); else Root = Html; } if (Root) Root->AttachTo(Mail); else return false; // What to do with the Unknown segments? for (unsigned i=0; iDetach(); DeleteObj(Unknown[i]); } return true; } #ifdef _DEBUG bool UnitTests(TStore *Store, TMail *Mail) { const char *Tests[] = { "text/plain", "text/html", "multipart/alternative\n" " text/plain\n" " text/html\n", "multipart/mixed\n" " text/plain\n" " application/pdf\n" " application/octet-stream\n", "multipart/mixed\n" " multipart/alternative\n" " text/plain\n" " text/html\n" " application/octet-stream\n", "multipart/mixed\n" " multipart/alternative\n" " text/plain\n" " multipart/related\n" " text/html\n" " image/jpeg\n" " image/png\n" " application/octet-stream\n", "multipart/related\n" " text/html\n" " image/jpeg\n" " image/png\n" }; for (unsigned int i=0; i t(Mail, Root); LString Src = Tests[i]; LString::Array Lines = Src.SplitDelimit(" \t\r\n"); for (unsigned ln = 0; ln < Lines.Length(); ln++) { TAttachment *a = new TAttachment(Store); if (!a) { LgiTrace("%s:%i - Alloc err\n", _FL); return false; } a->SetStr(FIELD_MIME_TYPE, Lines[ln]); if (Lines[ln].Find("multipart") >= 0) { DeleteObj(a); }// no-op else if (Lines[ln].Find("application/") >= 0) t.Attachments.Add(a); else if (Lines[ln].Find("html") >= 0) t.MsgHtml = a; else if (Lines[ln].Find("plain") >= 0) t.MsgText = a; else if (Lines[ln].Find("image") >= 0) t.MsgHtmlRelated.Add(a); else DeleteObj(a) } if (!t.Build()) { LgiTrace("%s:%i - Failed to build tree for test %i:\n%s\n", _FL, i, Tests[i]); return false; } for (unsigned n = 0; n < Lines.Length(); n++) { LArray Results; if (!Root->FindSegs(Lines[n], Results)) { LgiTrace("%s:%i - Failed to find '%s' in test %i:\n%s\n", _FL, Lines[n].Get(), i, Tests[i]); return false; } } } return true; } #endif }; #endif // _STORE3_MIME_TREE_H_ \ No newline at end of file diff --git a/src/common/Db/DbTable.cpp b/src/common/Db/DbTable.cpp --- a/src/common/Db/DbTable.cpp +++ b/src/common/Db/DbTable.cpp @@ -1,1149 +1,1149 @@ #include "lgi/common/Lgi.h" #include "lgi/common/DbTable.h" /////////////////////////////////////////////////////////////////// #define MAGIC(v) LgiSwap32(v) #define OBJ_HEAD(magic) \ char *Start = p.c; \ uint32_t *Sz = NULL; \ if (Write) \ { \ *p.u32++ = magic; \ Sz = p.u32++; \ } \ else if (*p.u32 != FieldMagic) \ return false; \ else \ { \ p.u32++; \ Sz = p.u32++; \ } #define SERIALIZE(type, var) \ if (Write) *p.type++ = var; \ else var = *p.type++; #define SERIALIZE_FN(fn, type) \ if (Write) *p.type++ = fn(); \ else fn(*p.type++); #define SERIALIZE_CAST(cast, type, var) \ if (Write) *p.type++ = var; \ else var = (cast) *p.type++; #define OBJ_TAIL() \ if (Write) \ *Sz = (uint32_t) (p.c - Start); \ else \ p.c = Start + *Sz; \ LAssert(Sizeof() == *Sz); \ return true; #define DB_DATE_SZ \ ( \ 2 + /* Year */ \ 1 + /* Month */ \ 1 + /* Day */ \ 1 + /* Hour */ \ 1 + /* Min */ \ 1 + /* Sec */ \ 2 /* TimeZone */ \ ) /////////////////////////////////////////////////////////////////// enum OffType { VariableOff, FixedOff }; enum DbMagic { TableMagic = MAGIC('tbl\0'), FieldMagic = MAGIC('fld\0'), RowMagic = MAGIC('row\0'), }; /////////////////////////////////////////////////////////////////// inline bool IsFixed(LVariantType t) { return t != GV_STRING && t != GV_BINARY; } struct Info { LVariantType Type; int Index; Info(LVariantType t = GV_NULL, int index = -1) { Type = t; Index = index; } bool operator !=(const Info &i) { return Type != i.Type && Index != i.Index; } bool operator ==(const Info &i) { return !(*this != i); } }; ////////////////////////////////////////////////////////////////////////////////////// struct DbTablePriv { // Fields unsigned Fixed; // Count of fixed size fields. unsigned FixedSz; // Byte size of fixed size fields. unsigned Variable; // Count of variable sized fields. LArray Fields; LArray FixedOffsets; LHashTbl, Info> Map; // Rows int Rows; LDbRow *First, *Last; LArray Data; bool Dirty; // Indexes LArray Indexes; // Methods DbTablePriv() : Map(0, Info()) { First = Last = NULL; Rows = 0; Fixed = 0; Variable = 0; FixedSz = 0; Dirty = false; } ~DbTablePriv() { Indexes.DeleteObjects(); } void SetDirty(bool b = true) { Dirty = b; } LDbField *FindField(int Id) { for (unsigned i=0; i 0) { LAssert(!"Adding fields with records not supported yet."); return false; } LDbField f; f.Id = Id; f.Type = Type; f.Offset = -1; if (IsFixed(Type)) Fields.AddAt(Fixed, f); else // Is variable size Fields.New() = f; return OffsetFields(); } bool DeleteField(int Id) { for (unsigned i=0; i 0); Fixed--; } else { LAssert(i >= Fixed && Variable > 0); Variable--; } Fields.DeleteAt(i, true); } } return OffsetFields(); } bool DeleteRow(LDbRow *r) { if (r->Prev) { r->Prev->Next = r->Next; } else { LAssert(r == First); First = r->Next; } if (r->Next) { r->Next->Prev = r->Prev; } else { LAssert(r == Last); Last = r->Prev; } r->Prev = NULL; r->Next = NULL; Rows--; DeleteObj(r); return true; } }; ////////////////////////////////////////////////////////////////////////////////////// DbIndex::DbIndex(DbTablePriv *priv) { d = priv; } DbIndex::~DbIndex() { LAssert(d->Indexes.HasItem(this)); d->Indexes.Delete(this); } DbArrayIndex::DbArrayIndex(DbTablePriv *priv) : DbIndex(priv) { Fld.Id = -1; Fld.Type = GV_NULL; Fld.Offset = -1; } bool DbArrayIndex::OnNew(LDbRow *r) { return Delete(r); } bool DbArrayIndex::OnDelete(LDbRow *r) { Add(r); return true; } struct CompareParams { int Id; bool Ascend; CompareParams(int i, bool a) { Id = i; Ascend = a; } }; bool DbArrayIndex::Sort(LDbField *fld, bool ascend) { if (!fld) return false; Fld = *fld; Ascend = ascend; CompareParams p(Fld.Id, Ascend); switch (Fld.Type) { case GV_INT32: case GV_INT64: LArray::Sort([&p](auto a, auto b) { int64 A = (*a)->GetInt(p.Id); int64 B = (*b)->GetInt(p.Id); return (int) (p.Ascend ? A - B : B - A); }); break; case GV_STRING: LArray::Sort([&p](auto a, auto b) { const char *A = (*a)->GetStr(p.Id); if (!A) A = ""; const char *B = (*b)->GetStr(p.Id); if (!B) B = ""; return p.Ascend ? stricmp(A, B) : stricmp(B, A); }); break; case GV_DATETIME: LArray::Sort([&p](auto a, auto b) { const LDateTime *A = (*a)->GetDate(p.Id); const LDateTime *B = (*b)->GetDate(p.Id); if (!A || !B) { LAssert(0); return 0; } uint64 UtcA, UtcB; if (!A->Get(UtcA) || !B->Get(UtcB)) { LAssert(0); return 0; } int64 r = p.Ascend ? UtcA - UtcB : UtcB - UtcA; if (r < 0) return -1; if (r > 0) return 1; return 0; }); break; default: LAssert(0); return false; } return true; } bool DbArrayIndex::Resort() { return Sort(&Fld, Ascend); } /////////////////////////////////////////////////////////////////////////////////// size_t LDbDate::Sizeof() { return DB_DATE_SZ; } bool LDbDate::Serialize(LPointer &p, LDateTime &dt, bool Write) { #ifdef _DEBUG char *Start = p.c; #endif SERIALIZE_FN(dt.Year, u16); SERIALIZE_FN(dt.Month, u8); SERIALIZE_FN(dt.Day, u8); SERIALIZE_FN(dt.Hours, u8); SERIALIZE_FN(dt.Minutes, u8); SERIALIZE_FN(dt.Seconds, u8); uint16 Tz = dt.GetTimeZone(); SERIALIZE(u16, Tz); if (!Write) dt.SetTimeZone(Tz, false); LAssert(p.c - Start == DB_DATE_SZ); return true; } /////////////////////////////////////////////////////////////////////////////////// int LDbField::DataSize() { switch (Type) { case GV_BOOL: return 1; case GV_INT32: return 4; case GV_INT64: return 8; case GV_DATETIME: return DB_DATE_SZ; default: LAssert(!"Impl me."); break; } return 0; } size_t LDbField::Sizeof() { return 8 + sizeof(Id) + 2 + // Type sizeof(Offset); } bool LDbField::Serialize(LPointer &p, bool Write) { OBJ_HEAD(FieldMagic); SERIALIZE(s32, Id); SERIALIZE(s32, Offset); SERIALIZE_CAST(LVariantType, u16, Type); OBJ_TAIL(); } /////////////////////////////////////////////////////////////////////////////////// int LDbRow::HeaderSz = sizeof(uint32_t) << 1; // Magic + Size LDbRow::LDbRow(DbTablePriv *priv) { d = priv; Next = NULL; Prev = NULL; Pos = -1; Base.c = NULL; Offsets[FixedOff] = d->FixedOffsets.AddressOf(); Offsets[VariableOff] = NULL; } LDbRow::~LDbRow() { } size_t LDbRow::GetFields() { return d->Fields.Length(); } LDbField &LDbRow::GetField(size_t Idx) { return d->Fields[Idx]; } struct VarBlock : public LRange { int Index; }; uint32_t LDbRow::GetInitialSize() { return HeaderSz + d->FixedSz + (d->Variable * sizeof(uint32_t)); } bool LDbRow::Compact() { if (Edit.Length()) { // The variable sized fields can get fragmented, this function removes unused space. LArray v; for (unsigned i=0; iVariable; i++) { if (Offsets[VariableOff][i] >= 0) { VarBlock &b = v.New(); b.Index = i; b.Start = Offsets[VariableOff][i]; b.Len = strlen(Base.c + b.Start) + 1; } } v.Sort([](auto a, auto b) { return (int) (a->Start - b->Start); }); uint32_t Pos = GetInitialSize(); for (unsigned i=0; i (ssize_t)Pos) { // Move block down memcpy(Base.c + Pos, Base.c + b.Start, b.Len); Offsets[VariableOff][b.Index] = Pos; b.Start = Pos; } Pos = (int32) (b.Start + b.Len); } if (Base.u32[1] > Pos) Base.u32[1] = Pos; } return true; } LString LDbRow::ToString() { LString::Array a; a.SetFixedLength(false); for (unsigned i=0; iFields.Length(); i++) { LDbField &f = d->Fields[i]; switch (f.Type) { case GV_INT32: case GV_INT64: a.New().Printf(LPrintfInt64, GetInt(f.Id)); break; case GV_STRING: { LString s = GetStr(f.Id); if (s.Length() > 0) a.New() = s; else a.New() = "NULL"; break; } case GV_DATETIME: { const LDateTime *dt = GetDate(f.Id); if (dt) a.New() = dt->Get(); else a.New() = "NULL"; break; } default: LAssert(0); break; } } LString Sep(", "); return Sep.Join(a); } uint32_t LDbRow::Size(uint32_t Set) { if (Base.c) { if (Set) Base.u32[1] = Set; return Base.u32[1]; } LAssert(0); return 0; } bool LDbRow::Delete() { return d->DeleteRow(this); } bool LDbRow::CopyProps(LDataPropI &p) { for (size_t i=0; iMap.Find(id); if (i.Index < 0 || i.Type != GV_STRING || !Base.c) return NULL; LAssert((unsigned)i.Index < d->Variable); return Base.c + Offsets[VariableOff][i.Index]; } bool LDbRow::StartEdit() { if (Edit.Length() == 0) { if (Base.c) { if (!Edit.Length(Base.u32[1])) return false; memcpy(Edit.AddressOf(), Base.c, Base.u32[1]); } else { int InitialSize = GetInitialSize(); if (!Edit.Length(InitialSize)) return false; Base.c = Edit.AddressOf(); Base.u32[0] = RowMagic; Base.u32[1] = InitialSize; // Initialize fixed fields to zero memset(Base.c + 8, 0, d->FixedSz); if (d->Variable > 0) { // And the variable offset table to -1 memset(Base.c + 8 + d->FixedSz, 0xff, InitialSize - d->FixedSz); } } PostEdit(); } return true; } void LDbRow::PostEdit() { Base.c = Edit.AddressOf(); if (Base.c) { Size((uint32_t)Edit.Length()); Offsets[VariableOff] = (int32*) Edit.AddressOf(8 + d->FixedSz); } } Store3Status LDbRow::SetStr(int id, const char *str) { Info i = d->Map.Find(id); if (i.Index < 0) return Store3Error; if (!StartEdit()) return Store3Error; size_t len = str ? strlen(str) + 1 : 1; Offsets[VariableOff][i.Index] = (int32)Edit.Length(); if (str) Edit.Add((char*)str, len); else Edit.Add((char*)"", 1); PostEdit(); d->SetDirty(); return Store3Success; } int64 LDbRow::GetInt(int id) { Info i = d->Map.Find(id); if (i.Index < 0 || !Base.c) return -1; LPointer p = { Base.s8 + Offsets[FixedOff][i.Index] }; if (i.Type == GV_INT32) return *p.s32; if (i.Type == GV_INT64) return *p.s64; return -1; } Store3Status LDbRow::SetInt(int id, int64 val) { Info i = d->Map.Find(id); if (i.Index < 0) return Store3Error; if (!Base.c) StartEdit(); LPointer p = { Base.s8 + Offsets[FixedOff][i.Index] }; if (i.Type == GV_INT32) { *p.s32 = (int32)val; d->SetDirty(); return Store3Success; } if (i.Type == GV_INT64) { *p.s64 = val; d->SetDirty(); return Store3Success; } return Store3Error; } const LDateTime *LDbRow::GetDate(int id) { Info i = d->Map.Find(id); if (i.Index < 0 || !Base.c) return NULL; LPointer p = { Base.s8 + Offsets[FixedOff][i.Index] }; if (i.Type != GV_DATETIME) return NULL; LDbDate dd; dd.Serialize(p, Cache, false); return &Cache; } Store3Status LDbRow::SetDate(int id, const LDateTime *dt) { Info i = d->Map.Find(id); if (i.Index < 0 || !dt) return Store3Error; if (!Base.c) StartEdit(); LPointer p = { Base.s8 + Offsets[FixedOff][i.Index] }; if (i.Type != GV_DATETIME) return Store3Error; LDbDate dd; LDateTime n = *dt; dd.Serialize(p, n, true); d->SetDirty(); return Store3Success; } LVariant *LDbRow::GetVar(int id) { LAssert(0); return NULL; } Store3Status LDbRow::SetVar(int id, LVariant *i) { LAssert(0); return Store3Error; } LDataPropI *LDbRow::GetObj(int id) { LAssert(0); return NULL; } Store3Status LDbRow::SetObj(int id, LDataPropI *i) { LAssert(0); return Store3Error; } -GDataIt LDbRow::GetList(int id) +LDataIt LDbRow::GetList(int id) { LAssert(0); return NULL; } Store3Status LDbRow::SetRfc822(LStreamI *Rfc822Msg) { LAssert(0); return Store3Error; } //////////////////////////////////////////////////////////////////////////////////// LDbTable::LDbTable(const char *File) { d = new DbTablePriv; if (File) Serialize(File, false); } LDbTable::~LDbTable() { Empty(); DeleteObj(d); } bool LDbTable::AddField(int Id, LVariantType Type) { return d->AddField(Id, Type); } bool LDbTable::DeleteField(int Id) { return d->DeleteField(Id); } int LDbTable::GetFields() { return (int)d->Fields.Length(); } LDbField &LDbTable::GetField(int Idx) { return d->Fields[Idx]; } bool LDbTable::Iterate(LDbRow *&Ptr) { if (Ptr && Ptr->d != d) { LAssert(!"Wrong table."); return false; } if (Ptr) Ptr = Ptr->Next; else Ptr = d->First; return Ptr != NULL; } int LDbTable::GetRows() { return d->Rows; } LDbRow *LDbTable::NewRow() { LDbRow *r = new LDbRow(d); if (!r) return NULL; if (d->Last) { LAssert(d->Last->Next == NULL); d->Last->Next = r; r->Prev = d->Last; d->Last = r; d->Rows++; } else { LAssert(d->Rows == 0); d->First = d->Last = r; d->Rows = 1; } for (unsigned i=0; iIndexes.Length(); i++) d->Indexes[i]->OnNew(r); return r; } bool LDbTable::Empty() { d->Indexes.DeleteObjects(); LDbRow *n; for (LDbRow *r = d->First; r; r = n) { n = r->Next; delete r; } d->First = d->Last = NULL; d->Rows = 0; d->Fixed = 0; d->FixedSz = 0; d->Variable = 0; d->Fields.Empty(); d->FixedOffsets.Empty(); d->Map.Empty(); d->Data.Empty(); return true; } bool LDbTable::DeleteRow(LDbRow *r) { if (!r || r->d != d) return false; for (unsigned i=0; iIndexes.Length(); i++) d->Indexes[i]->OnDelete(r); return d->DeleteRow(r); } DbArrayIndex *LDbTable::Sort(int Id, bool Ascending) { LDbField *f = d->FindField(Id); if (!f) return NULL; // Collect all the records DbArrayIndex *i = new DbArrayIndex(d); if (!i) return NULL; if (!i->Length(d->Rows)) { delete i; return NULL; } int Idx = 0; for (LDbRow *r = d->First; r; r = r->Next) (*i)[Idx++] = r; LAssert(Idx == d->Rows); // Sort them i->Sort(f, Ascending); // Save the index d->Indexes.Add(i); return i; } bool LDbTable::Serialize(const char *Path, bool Write) { LFile f; if (!f.Open(Path, Write ? O_WRITE : O_READ)) return false; if (Write) { if (d->Fields.Length() > 0) { size_t Sz = sizeof(uint32_t) + d->Fields.Length() * d->Fields[0].Sizeof(); if (d->Data.Length() < Sz) d->Data.Length(Sz); LPointer p = {(int8*)d->Data.AddressOf()}; *p.u32++ = TableMagic; for (unsigned i=0; iFields.Length(); i++) { if (!d->Fields[i].Serialize(p, true)) return false; } f.SetSize(0); size_t Bytes = p.c - d->Data.AddressOf(); LAssert(Bytes == Sz); if (f.Write(d->Data.AddressOf(), Bytes) != Bytes) return false; } for (LDbRow *r = d->First; r; r = r->Next) { // Fix the size before we write LAssert(r->Base.u32[0] == RowMagic); if (r->Edit.Length()) r->Compact(); if (f.Write(r->Base.c, r->Size()) != r->Size()) return false; } } else { Empty(); // Read all the data into memory if (!d->Data.Length((size_t)f.GetSize())) return false; auto Rd = f.Read(d->Data.AddressOf(0), (ssize_t)f.GetSize()); if (Rd != f.GetSize()) return false; LPointer p = {(int8*)d->Data.AddressOf()}; if (*p.u32++ != TableMagic) return false; // Create all the fields char *End = p.c + d->Data.Length(); d->Fixed = 0; d->FixedSz = 0; d->Variable = 0; d->Map.Empty(); d->FixedOffsets.Empty(); while (p.c < End - 8 && *p.u32 == FieldMagic) { LDbField &f = d->Fields.New(); if (!f.Serialize(p, false)) return false; if (IsFixed(f.Type)) { d->FixedSz += f.DataSize(); d->FixedOffsets[d->Fixed] = f.Offset; d->Map.Add(f.Id, Info(f.Type, d->Fixed++)); } else { d->Map.Add(f.Id, Info(f.Type, d->Variable++)); } } // Create the rows d->Rows = 0; while (p.c < End - 8 && *p.u32 == RowMagic) { LDbRow *r = new LDbRow(d); if (!r) return false; r->Base = p; r->Offsets[FixedOff] = d->FixedOffsets.AddressOf(); r->Offsets[VariableOff] = (int32*) (r->Base.c + 8 + d->FixedSz); if (d->Last) { r->Prev = d->Last; d->Last->Next = r; d->Last = r; d->Rows++; } else { d->First = d->Last = r; d->Rows = 1; } p.c += p.u32[1]; } } return true; } /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// enum TestFields { TestUid = 100, TestInt32, TestString, TestDate, TestString2, }; LString LDbTable::ToString() { LString::Array a; a.SetFixedLength(false); for (LDbRow *r = NULL; Iterate(r); ) { LString s = r->ToString(); a.Add(s); } LString Sep("\n"); return Sep.Join(a); } bool LDbTable::UnitTests() { LDbTable t; t.AddField(TestUid, GV_INT64); t.AddField(TestInt32, GV_INT32); t.AddField(TestString, GV_STRING); t.AddField(TestDate, GV_DATETIME); t.AddField(TestString2, GV_STRING); const char *Days[] = {"Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday" }; for (unsigned i=0; iSetInt(TestUid, 1000+i); r->SetInt(TestInt32, i * 3); if (i % 2) { r->SetStr(TestString, Days[i]); r->SetStr(TestString2, "asdasdasdasdasd"); } else { r->SetStr(TestString2, "asdasdasdasdasd"); r->SetStr(TestString, Days[i]); } } LgiTrace("Export table:\n"); for (LDbRow *r = NULL; t.Iterate(r); ) { int64 Id = r->GetInt(TestUid); int64 Int = r->GetInt(TestInt32); auto s = r->GetStr(TestString); auto s2 = r->GetStr(TestString2); LgiTrace("\t%i: %i, %s, %s\n", (int)Id, (int)Int, s, s2); } const char *File = "test.db"; t.Serialize(File, true); LDbTable in; if (!in.Serialize(File, false)) return false; LgiTrace("Import table:\n"); LDbRow *Nine = NULL; for (LDbRow *r = NULL; in.Iterate(r); ) { int64 Start = LgiMicroTime(); int64 Id = r->GetInt(TestUid); int64 Int = r->GetInt(TestInt32); auto s = r->GetStr(TestString); auto s2 = r->GetStr(TestString2); int64 Time = LgiMicroTime()-Start; LgiTrace("\t%i: %i, %s, %s (%.4fms)\n", (int)Id, (int)Int, s, s2, (double)Time/1000.0); if (Int == 9) Nine = r; else if (Int == 12) r->SetStr(TestString2, "This is a new string."); else if (Int == 15) r->SetStr(TestString, NULL); else if (Int == 6) r->SetInt(TestInt32, 66); } if (!Nine) return false; in.DeleteRow(Nine); LgiTrace("Post delete:\n"); for (LDbRow *r = NULL; in.Iterate(r); ) { int64 Id = r->GetInt(TestUid); int64 Int = r->GetInt(TestInt32); auto s = r->GetStr(TestString); auto s2 = r->GetStr(TestString2); LgiTrace("\t%i: %i, %s, %s\n", (int)Id, (int)Int, s, s2); } if (!in.Serialize(File, true)) return false; if (!t.Serialize(File, false)) return false; LgiTrace("Reread:\n"); for (LDbRow *r = NULL; t.Iterate(r); ) { int64 Id = r->GetInt(TestUid); int64 Int = r->GetInt(TestInt32); auto s = r->GetStr(TestString); auto s2 = r->GetStr(TestString2); LgiTrace("\t%i: %i, %s, %s\n", (int)Id, (int)Int, s, s2); } return true; }