diff --git a/src/Calendar.cpp b/src/Calendar.cpp --- a/src/Calendar.cpp +++ b/src/Calendar.cpp @@ -1,3710 +1,3714 @@ /*hdr ** FILE: Calendar.cpp ** AUTHOR: Matthew Allen ** DATE: 23/11/2001 ** DESCRIPTION: Scribe calender support ** ** Copyright (C) 2001 Matthew Allen ** fret@memecode.com */ #include "Scribe.h" #include "lgi/common/vCard-vCal.h" #include "lgi/common/Combo.h" #include "lgi/common/DateTimeCtrls.h" #include "lgi/common/TabView.h" #include "lgi/common/DisplayString.h" #include "lgi/common/Edit.h" #include "lgi/common/ColourSelect.h" #include "lgi/common/Button.h" #include "lgi/common/LgiRes.h" #include "lgi/common/Json.h" #include "lgi/common/FileSelect.h" #include "CalendarView.h" #include "PrintContext.h" #include "resdefs.h" #include "resource.h" #include "AddressSelect.h" #include "ObjectInspector.h" #define MAX_RECUR 1024 #define DEBUG_REMINDER 0 #define DEBUG_DATES 0 #if DEBUG_DATES #define LOG_DEBUG(...) LgiTrace(__VA_ARGS__) #else #define LOG_DEBUG(...) #endif ////////////////////////////////////////////////////////////////////////////// ItemFieldDef CalendarFields[] = { {"Start", SdStart, GV_DATETIME, FIELD_CAL_START_UTC, IDC_START_DATE, 0, true}, {"End", SdEnd, GV_DATETIME, FIELD_CAL_END_UTC, IDC_END_DATE, 0, true}, {"Subject", SdSubject, GV_STRING, FIELD_CAL_SUBJECT, IDC_SUBJECT}, {"Location", SdLocation, GV_STRING, FIELD_CAL_LOCATION, IDC_LOCATION}, {"Show Time As", SdShowTimeAs, GV_INT32, FIELD_CAL_SHOW_TIME_AS, IDC_AVAILABLE_TYPE}, {"All Day", SdAllDay, GV_BOOL, FIELD_CAL_ALL_DAY, IDC_ALL_DAY}, // TRUE if the calendar event recurs {"Recur", SdRecur, GV_INT32, FIELD_CAL_RECUR, -1}, // Base time unit of recurring event. See enum CalRecurFreq: days, weeks, months, years. {"Recur Freq", SdRecurFreq, GV_INT32, FIELD_CAL_RECUR_FREQ, -1}, // Number of FIELD_CAL_RECUR_FREQ units of time between recurring events. (Minimum is '1') {"Recur Interval", SdRecurInterval, GV_INT32, FIELD_CAL_RECUR_INTERVAL, -1}, // Bitfield of days, Bit 0 = Sunday, Bit 1 = Monday, Bit 2 = Teusday etc. {"Filter Days", SdFilterDays, GV_INT32, FIELD_CAL_RECUR_FILTER_DAYS, -1}, // Bitfield of months, Bit 0 = Janurary, Bit 1 = feburary, Bit 2 = March etc. {"Filter Months", SdFilterMonths, GV_INT32, FIELD_CAL_RECUR_FILTER_MONTHS, -1}, // String of year numbers separated by commas: "2010,2014,2018" {"Filter Years", SdFilterYears, GV_STRING, FIELD_CAL_RECUR_FILTER_YEARS, -1}, // Position in month, "1" means the first matching day of the month. Multiple indexes can be joined with ',' // like: "1,3" {"Filter Pos", SdFilterPos, GV_STRING, FIELD_CAL_RECUR_FILTER_POS, -1}, // See the CalRecurEndType enumeration {"Recur End Type", SdRecurEndType, GV_INT32, FIELD_CAL_RECUR_END_TYPE, -1}, // If FIELD_CAL_RECUR_END_TYPE==CalEndOnDate then this specifies the date to end {"Recur End Date", SdRecurEndDate, GV_DATETIME, FIELD_CAL_RECUR_END_DATE, -1}, // If FIELD_CAL_RECUR_END_TYPE==CalEndOnCount then this specifies the count of events {"Recur End Count", SdRecurEndCount, GV_INT32, FIELD_CAL_RECUR_END_COUNT, -1}, // The timezone the start and end are referencing {"Timezone", SdTimeZone, GV_STRING, FIELD_CAL_TIMEZONE, IDC_TIMEZONE}, // User defined notes for the object {"Notes", SdNotes, GV_STRING, FIELD_CAL_NOTES, IDC_DESCRIPTION}, // See the CalendarType enumeration {"Type", SdType, GV_INT32, FIELD_CAL_TYPE, -1}, {"Reminders", SdReminders, GV_STRING, FIELD_CAL_REMINDERS, -1}, {"LastCheck", SdLastCheck, GV_DATETIME, FIELD_CAL_LAST_CHECK, -1}, {"DateModified", SdDateModified, GV_DATETIME, FIELD_DATE_MODIFIED, -1}, {0} }; ////////////////////////////////////////////////////////////////////////////// const char *sReminderType[] = { "Email", "Popup", "ScriptCallback" }; const char *sReminderUnits[] = { "Minutes", "Hours", "Days", "Weeks" }; class ReminderItem : public LListItem { CalendarReminderType Type; float Value; CalendarReminderUnits Units; LString Param; public: ReminderItem(CalendarReminderType type, float value, CalendarReminderUnits units, LString param) { Type = type; Value = value; Units = units; Param = param; Update(); SetText("x", 1); } ReminderItem(LString s) { if (!SetString(s)) { // Default Type = CalPopup; Value = 1.0; Units = CalMinutes; Param.Empty(); } Update(); SetText("x", 1); } CalendarReminderType GetType() { return Type; } float GetValue() { return Value; } CalendarReminderUnits GetUnits() { return Units; } LString GetParam() { return Param; } LString GetString() { // See also FIELD_CAL_REMINDERS LString s; s.Printf("%g,%i,%i,%s", Value, Units, Type, Param?LUrlEncode(Param, ",\r\n").Get():""); return s; } bool SetString(LString s) { // See also FIELD_CAL_REMINDERS LString::Array a = s.Split(","); if (a.Length() < 3) return false; Value = (float) a[0].Float(); Units = (CalendarReminderUnits) a[1].Int(); Type = (CalendarReminderType) a[2].Int(); if (a.Length() > 3) Param = LUrlDecode(a[3]); return true; } void Update() { char s[300]; if (Param) sprintf_s(s, sizeof(s), "%s '%s' @ %g %s", sReminderType[Type], Param.Get(), Value, sReminderUnits[Units]); else sprintf_s(s, sizeof(s), "%s @ %g %s", sReminderType[Type], Value, sReminderUnits[Units]); SetText(s); } void OnPaintColumn(LItem::ItemPaintCtx &Ctx, int i, LItemColumn *c) { LColour old = Ctx.Fore; if (i == 1) Ctx.Fore.Rgb(255, 0, 0); LListItem::OnPaintColumn(Ctx, i, c); Ctx.Fore = old; } void OnMouseClick(LMouse &m) { if (m.Down() && m.Left()) { if (GetList()->ColumnAtX(m.x) == 1) { delete this; return; } } LListItem::OnMouseClick(m); } }; //////////////////////////////////////////////////////////////////////////////////// static int DefaultCalenderFields[] = { FIELD_CAL_SUBJECT, FIELD_CAL_START_UTC, FIELD_CAL_END_UTC, 0, }; #define MINUTE 1 // we're in minutes... #define HOUR (60 * MINUTE) #define DAY (24 * HOUR) int ReminderOffsets[] = { 0, 15 * MINUTE, 30 * MINUTE, 1 * HOUR, 2 * HOUR, 3 * HOUR, 4 * HOUR, 5 * HOUR, 6 * HOUR, 7 * HOUR, 8 * HOUR, 9 * HOUR, 10 * HOUR, 11 * HOUR, 12 * HOUR, 1 * DAY, 2 * DAY }; ////////////////////////////////////////////////////////////////////////////// List Calendar::Reminders; int Calendar::DayStart = -1; int Calendar::DayEnd = -1; int Calendar::WorkDayStart = -1; int Calendar::WorkDayEnd = -1; int Calendar::WorkWeekStart = -1; int Calendar::WorkWeekEnd = -1; void InitCalendarView() { Calendar::DayStart = 6; Calendar::DayEnd = 23; Calendar::WorkDayStart = -1; Calendar::WorkDayEnd = -1; Calendar::WorkWeekStart = -1; Calendar::WorkWeekEnd = -1; auto s = LAppInst->GetConfig("Scribe.Calendar.WorkDayStart"); if (s) Calendar::WorkDayStart = (int)s.Int(); s = LAppInst->GetConfig("Scribe.Calendar.WorkDayEnd"); if (s) Calendar::WorkDayEnd = (int)s.Int(); s = LAppInst->GetConfig("Scribe.Calendar.WorkWeekStart"); if (s) Calendar::WorkWeekStart = (int)s.Int() + 1; s = LAppInst->GetConfig("Scribe.Calendar.WorkWeekEnd"); if (s) Calendar::WorkWeekEnd = (int)s.Int() + 1; if (Calendar::WorkDayStart < 0) Calendar::WorkDayStart = 9; if (Calendar::WorkDayEnd < 0) Calendar::WorkDayEnd = 18; if (Calendar::WorkWeekStart < 0) Calendar::WorkWeekStart = 1; if (Calendar::WorkWeekEnd < 0) Calendar::WorkWeekEnd = 5; } void Calendar::CheckReminders() { LDateTime Now; Now.SetNow(); #if DEBUG_REMINDER // Get all the times for today, including recent ones Now.SetTime("0:0:0"); #endif LDateTime Then = Now; Then.AddDays(1); #if DEBUG_REMINDER LgiTrace("%s:%i - Reminders.Len=%i, Now=%s, Then=%s\n", _FL, Reminders.Length(), Now.Get().Get(), Then.Get().Get()); #endif for (auto c: Reminders) { auto App = c->App; LHashTbl,bool> Added; Thing *ReminderThing = NULL; Mail *ReminderEmail = NULL; // Is a reminder on the entry? if (!c->GetObject()) continue; LString Rem = c->GetObject()->GetStr(FIELD_CAL_REMINDERS); if (!Rem) continue; const char *Subj = NULL, *Notes = NULL; c->GetField(FIELD_CAL_SUBJECT, Subj); c->GetField(FIELD_CAL_NOTES, Notes); LArray Times; if (!c->GetTimes(Now, Then, Times)) { #if DEBUG_REMINDER // LgiTrace(" No times for '%s', now=%s, then=%s\n", Subj, Now.Get().Get(), Then.Get().Get()); #endif continue; } auto Obj = c->GetObject(); if (!Obj) continue; LDateTime LastCheck = *Obj->GetDate(FIELD_CAL_LAST_CHECK); if (LastCheck.IsValid()) { LastCheck.ToLocal(); #if DEBUG_REMINDER // Helps with debugging... LastCheck.AddDays(-1); #endif } LString::Array r = Rem.SplitDelimit("\n"); for (unsigned i=0; i LastCheck; bool b = ts <= Now; LgiTrace(" last check = %s\n", LastCheck.Get().Get()); LgiTrace(" now = %s\n", Now.Get().Get()); LgiTrace(" %i %i\n", a, b); #endif if ( ( !LastCheck.IsValid() || ts > LastCheck ) && ts <= Now ) { // Save the last check field now auto NowUtc = Now.Utc(); Obj->SetDate(FIELD_CAL_LAST_CHECK, &NowUtc); c->SetDirty(); // Fire the event switch (ri.GetType()) { case CalEmail: { ScribeAccount *Acc = App->GetCurrentAccount(); if (!Acc || !Acc->Identity.IsValid()) { LView *Ui = c->GetUI(); LgiMsg(Ui ? Ui : c->App, "No from account to use for sending event notifications.", AppName, MB_OK); break; } auto Email = Acc->Identity.Email(); auto Name = Acc->Identity.Name(); LgiTrace("%s:%i - Using account ('%s' '%s') for the event reminder 'from' address.\n", _FL, Email.Str(), Name.Str()); if (!ReminderThing) ReminderThing = App->CreateThingOfType(MAGIC_MAIL); if (!ReminderEmail) { ReminderEmail = ReminderThing ? ReminderThing->IsMail() : NULL; if (ReminderEmail) { LString s; s.Printf("Calendar Notification: %s", Subj); ReminderEmail->SetSubject(s); s.Printf("The event '%s' is due at: %s\n" "\n" "%s\n" "\n" "(This email was generated by Scribe)", Subj, t.s.Get().Get(), Notes ? Notes : ""); ReminderEmail->SetBody(s); auto Frm = ReminderEmail->GetFrom(); Frm->SetStr(FIELD_EMAIL, Email.Str()); Frm->SetStr(FIELD_NAME, Name.Str()); } } if (ReminderEmail) { auto To = ReminderEmail->GetTo(); // Add any custom parameter email address: LString Param = ri.GetParam(); if (LIsValidEmail(Param)) { auto Recip = To->Create(c->GetObject()->GetStore()); if (Recip) { Added.Add(Param, true); Recip->SetStr(FIELD_EMAIL, Param); To->Insert(Recip); } } // Add all the guests LString sGuests = c->GetObject()->GetStr(FIELD_TO); LString::Array Guests = sGuests.SplitDelimit(","); for (auto Guest: Guests) { LAutoString e, n; DecodeAddrName(Guest, n, e, NULL); #if DEBUG_REMINDER LgiTrace("Attendee=%s,%s\n", n.Get(), e.Get()); #endif if (LIsValidEmail(e.Get()) && !Added.Find(e)) { auto Recip = To->Create(c->GetObject()->GetStore()); if (Recip) { if (n) Recip->SetStr(FIELD_NAME, n); Recip->SetStr(FIELD_EMAIL, e); To->Insert(Recip); Added.Add(e, true); } } else { #if DEBUG_REMINDER LgiTrace("Attendee not valid or added already: %s\n", e.Get()); #endif } } } break; } case CalPopup: { if (LgiMsg( 0, // this causes the dialog to be ontop of everything else LLoadString(IDS_EVENT_DUE), AppName, MB_YESNO | MB_SYSTEMMODAL, Subj) == IDYES) { // Open the calendar entry c->DoUI(); } break; } case CalScriptCallback: { break; } default: { LgiTrace("%s:%i - Unknown reminder type.\n", _FL); break; } } } } } } if (ReminderEmail) { ReminderEmail->CreateMailHeaders(); ReminderEmail->Update(); ReminderEmail->Send(true); } } } #define MIN_1 ((int64)LDateTime::Second64Bit * 60) #define HOUR_1 (MIN_1 * 60) #define DAY_1 (HOUR_1 * 24) #define YEAR_1 (DAY_1 * 365) const char *RelativeTime(LDateTime &Then) { static char s[256]; static const int Id[] = { IDS_CAL_LDAY_SUN, IDS_CAL_LDAY_MON, IDS_CAL_LDAY_TUE, IDS_CAL_LDAY_WED, IDS_CAL_LDAY_THU, IDS_CAL_LDAY_FRI, IDS_CAL_LDAY_SAT, }; LDateTime Now; Now.SetNow(); char Val[64]; LTimeStamp n, t; Now.Get(n); Then.Get(t); auto Diff = t - n; int Yrs = 0; int Months = 0; int Days = 0; int Hrs = 0; int Mins = 0; LDateTime i = Now; int Inc = Then > Now ? 1 : -1; char DirIndcator = Then > Now ? '+' : '-'; while (ABS(Diff) > YEAR_1) { Yrs++; i.Year(i.Year()+Inc); if (!i.IsValid()) break; i.Get(n); Diff = t - n; } int TotalDays = 0; if (ABS(Diff) > DAY_1) { TotalDays = Days = (int) (Diff / DAY_1); while (true) { LDateTime first = i; first.AddMonths(Inc); if ( (Inc < 0 && first > Then) // Tracking back in time.. || (Inc > 0 && Then > first) // Forward in time.. ) { Months += Inc; i = first; } else break; } if (Months) { LTimeStamp remaining; i.Get(remaining); Diff = t - remaining; Days = (int) (Diff / DAY_1); } Diff -= (int64) Days * DAY_1; } if (ABS(Diff) > HOUR_1) { Hrs = (int) (Diff / HOUR_1); Diff -= (int64) Hrs * HOUR_1; } if (ABS(Diff) > MIN_1) { Mins = (int) (Diff / MIN_1); Diff -= (int64) Mins * MIN_1; } if (Yrs) { // Years + months sprintf_s(Val, sizeof(Val), "%c%iy %im", DirIndcator, abs(Yrs), abs(Months)); } else if (Months) { // Months + days sprintf_s(Val, sizeof(Val), "%c%im %id", DirIndcator, abs(Months), abs(Days)); } else if (Days) { if (abs(Days) >= 7) { // Weeks + days... sprintf_s(Val, sizeof(Val), "%c%iw %id", DirIndcator, abs(Days)/7, abs(Days)%7); } else { // Days + hours... sprintf_s(Val, sizeof(Val), "%c%id %ih", DirIndcator, abs(Days), abs(Hrs)); } } else if (Hrs) { // Hours + min sprintf_s(Val, sizeof(Val), "%c%ih %im", DirIndcator, abs(Hrs), abs(Mins)); } else { // Mins sprintf_s(Val, sizeof(Val), "%c%im", DirIndcator, abs(Mins)); } if (Yrs != 0 || Months != 0) { sprintf_s(s, sizeof(s), "%s", Val); return s; } auto NowDay = n.Get() / DAY_1; auto ThenDay = t.Get() / DAY_1; auto DaysDiff = (int64_t)ThenDay - (int64_t)NowDay; int Ch = 0; if (NowDay == ThenDay) Ch = sprintf_s(s, sizeof(s), "%s", LLoadString(IDS_TODAY)); else if (DaysDiff == -1) Ch = sprintf_s(s, sizeof(s), "%s", LLoadString(IDS_YESTERDAY)); else if (DaysDiff == 1) Ch = sprintf_s(s, sizeof(s), "%s", LLoadString(IDS_TOMORROW)); else if (DaysDiff > 1 && DaysDiff < 7) Ch = sprintf_s(s, sizeof(s), LLoadString(IDS_THIS_WEEK), LLoadString(Id[Then.DayOfWeek()])); else if (DaysDiff >= 7 && DaysDiff < 14) Ch = sprintf_s(s, sizeof(s), LLoadString(IDS_NEXT_WEEK), LLoadString(Id[Then.DayOfWeek()])); else { sprintf_s(s, sizeof(s), "%s", Val); return s; } sprintf_s(s+Ch, sizeof(s)-Ch, ", %s", Val); return s; } int CalSorter(TimePeriod *a, TimePeriod *b) { return a->s.Compare(&b->s); } CalendarSourceGetEvents *Calendar::GetEvents = NULL; void Calendar::SummaryOfToday(ScribeWnd *App, std::function Callback) { LDateTime Now; Now.SetNow(); LDateTime Next = Now; Next.AddMonths(1); LArray Sources; if (!App || !Callback || !App->GetCalendarSources(Sources)) return; if (GetEvents) return; new CalendarSourceGetEvents( App, &GetEvents, Now, Next, Sources, [Callback](auto e) { if (!e.Length()) { char s[256]; sprintf_s(s, sizeof(s), "%s", LLoadString(IDS_NO_EVENTS)); Callback(s); } else { e.Sort(CalSorter); LStringPipe p; p.Print("\n"); for (unsigned n=0; nGetField(FIELD_CAL_SUBJECT, Subject); LColour Base32 = c->GetColour(); auto Edge = Base32.Mix(LColour(L_WORKSPACE), 0.85f); sprintf_s(Back, sizeof(Back), "#%2.2x%2.2x%2.2x", Edge.r(), Edge.g(), Edge.b()); sprintf_s(Fore, sizeof(Fore), "#%2.2x%2.2x%2.2x", Base32.r(), Base32.g(), Base32.b()); p.Print("\t
\n" "\t\t%s\n", Fore, Back, Fore, Fore, (Thing*)c, Subject); char Str[256]; const char *Rel = RelativeTime(tp.s); if (Rel) { p.Print("\t\t
%s\n", Rel); } tp.s.Get(Str, sizeof(Str)); p.Print("\t\t
%s\n", Str); tp.e.Get(Str, sizeof(Str)); p.Print("\t\t-
%s\n", Str); } p.Print("
\n"); Callback(p.NewLStr()); } }); } void Calendar::OnSerialize(bool Write) { // Update reminder list.. Reminders.Delete(this); const char *Rem = NULL; if (GetField(FIELD_CAL_REMINDERS, Rem) && ValidStr(Rem)) { Reminders.Insert(this); } SetImage(GetCalType() == CalTodo ? ICON_TODO : ICON_CALENDAR); if (Write && TodoView) { TodoView->Update(); TodoView->Resort(); } } LArray Calendar::GetAttachments() { LArray attachments; if (GetObject()) { auto iter = GetObject()->GetList(FIELD_CAL_ATTACHMENTS); for (auto i = iter->First(); i; i = iter->Next()) { auto data = dynamic_cast(i); if (data) attachments.Add(data); else LAssert(!"Wrong object type."); } } return attachments; } LDataI *Calendar::ImportAttachment(LString Path) { if (!GetObject()) { LgiTrace("%s:%i - No object.\n", _FL); return NULL; } auto lst = GetObject()->GetList(FIELD_CAL_ATTACHMENTS); if (!lst) { LgiTrace("%s:%i - No FIELD_CAL_ATTACHMENTS.\n", _FL); return NULL; } LFile f(Path, O_READ); if (!f) { LgiTrace("%s:%i - Failed to open '%s' for reading.\n", _FL, Path.Get()); return NULL; } auto store = GetObject()->GetStore(); auto attachment = store->Create(MAGIC_CALENDAR_FILE); if (!attachment) { LgiTrace("%s:%i - Store didn't create a MAGIC_CALENDAR_FILE.\n", _FL); return NULL; } attachment->SetStr(FIELD_NAME, LGetLeaf(Path)); attachment->SetStr(FIELD_MIME_TYPE, LGetFileMimeType(Path)); auto now = LDateTime::Now(); attachment->SetDate(FIELD_DATE_MODIFIED, &now); attachment->SetStr(FIELD_ATTACHMENTS_DATA, f.Read()); auto status = attachment->Save(GetObject()); if (status > Store3Error) - lst->Insert(attachment); - else - DeleteObj(attachment); + { + // Check attachment is in list... + if (lst->IndexOf(attachment) < 0) + { + LAssert(!"Save needs to add object to list..."); + LgiTrace("%s:%i - save didn't add file to list.\n", _FL); + } + } + else DeleteObj(attachment); return attachment; } bool Calendar::DeleteAttachment(LDataI *attachment) { if (!attachment) { LgiTrace("%s:%i - No object.\n", _FL); return false; } auto result = attachment->Delete(false); return result > Store3Error; } ////////////////////////////////////////////////////////////////////////////// void TimePeriod::Set(CalendarSource *source, Calendar *cal, LDateTime start, LDateTime end) { src = source; c = cal; s = start; e = end; ToLocal(); } LString TimePeriod::ToString() { LString str; auto subj = c->GetObject()->GetStr(FIELD_CAL_SUBJECT); str.Printf("TimePeriod(%p, %s, %s, %s, %s)", c, subj, src ? src->ToString().Get() : NULL, s.Get().Get(), e.Get().Get()); return str; } ////////////////////////////////////////////////////////////////////////////// Calendar::Calendar(ScribeWnd *app, LDataI *object) : Thing(app, object) { DefaultObject(object); SetImage(ICON_CALENDAR); } Calendar::~Calendar() { CalendarView::OnDelete(this); DeleteObj(TodoView); Reminders.Delete(this); } bool Calendar::GetTimes(LDateTime StartLocal, LDateTime EndLocal, LArray &Times) { if (Calendar::DayStart < 0) InitCalendarView(); ssize_t StartLen = Times.Length(); LDateTime StartUtc = StartLocal; LDateTime EndUtc = EndLocal; StartUtc.ToUtc(); EndUtc.ToUtc(); TimePeriod w; w.s = StartUtc; w.e = EndUtc; const char *Subj = NULL; GetField(FIELD_CAL_SUBJECT, Subj); TimePeriod BaseUtc; LArray Periods; if (!GetField(FIELD_CAL_START_UTC, BaseUtc.s)) return false; if (!GetField(FIELD_CAL_END_UTC, BaseUtc.e)) { BaseUtc.e = BaseUtc.s; BaseUtc.e.AddHours(1); } if (BaseUtc.s > EndUtc) return true; LArray Dst; LDateTime::GetDaylightSavingsInfo(Dst, BaseUtc.s, &EndUtc); if (Dst.Length() < 2) { LgiTrace("%s:%i - GetDaylightSavingsInfo(%s, %s)\n", _FL, BaseUtc.s.Get().Get(), EndUtc.Get().Get()); LAssert(!"Need 2 dst points."); } Periods.Add(BaseUtc); LDateTime BaseS = BaseUtc.s; LDateTime::DstToLocal(Dst, BaseS); auto BaseTz = BaseS.GetTimeZone(); int AllDay = false; GetField(FIELD_CAL_ALL_DAY, AllDay); // Process recur rules int Recur = 0; if (GetField(FIELD_CAL_RECUR, Recur) && Recur) { LDateTime Diff = BaseUtc.e - BaseUtc.s; int FilterFreq = -1; int FilterInterval = 0; int FilterDay = 0; int FilterMonth = 0; int EndType = 0; int EndCount = 0; const char *FilterYear = NULL; const char *FilterPos = NULL; LDateTime EndDate; GetField(FIELD_CAL_RECUR_FREQ, FilterFreq); GetField(FIELD_CAL_RECUR_INTERVAL, FilterInterval); GetField(FIELD_CAL_RECUR_FILTER_DAYS, FilterDay); GetField(FIELD_CAL_RECUR_FILTER_MONTHS, FilterMonth); GetField(FIELD_CAL_RECUR_FILTER_YEARS, FilterYear); GetField(FIELD_CAL_RECUR_FILTER_POS, FilterPos); GetField(FIELD_CAL_RECUR_END_TYPE, EndType); GetField(FIELD_CAL_RECUR_END_DATE, EndDate); GetField(FIELD_CAL_RECUR_END_COUNT, EndCount); LDateTime CurUtc = BaseUtc.s; const char *Error = NULL; int Count = 0; while (!Error) { LAssert(CurUtc.GetTimeZone() == 0); // Advance the current date by interval * freq switch (FilterFreq) { case CalFreqDays: CurUtc.AddDays(FilterInterval); break; case CalFreqWeeks: CurUtc.AddDays(FilterInterval * 7); break; case CalFreqMonths: CurUtc.AddMonths(FilterInterval); break; case CalFreqYears: CurUtc.Year(CurUtc.Year() + FilterInterval); break; default: Error = "Invalid freq."; break; } LAssert(CurUtc.GetTimeZone() == 0); if (Error || CurUtc > EndUtc) break; // Check against end conditions bool IsEnded = CurUtc > EndUtc; switch (EndType) { case CalEndNever: break; case CalEndOnCount: // count IsEnded = Count >= EndCount - 1; break; case CalEndOnDate: // date IsEnded = CurUtc > EndDate; break; default: // error IsEnded = true; break; } if (IsEnded) break; // Check against filters LAssert(CurUtc.GetTimeZone() == 0); LDateTime CurLocal = CurUtc; LDateTime::DstToLocal(Dst, CurLocal); // This fixes the current time when it's in a different daylight saves zone. // Otherwise you get events one hour or whatever out of position after DST starts // or ends during the recurring set. int DiffMins = BaseTz - CurLocal.GetTimeZone(); CurLocal.AddMinutes(DiffMins); bool Show = true; if (FilterDay) { int Day = CurLocal.DayOfWeek(); for (int i=0; i<7; i++) { int Bit = 1 << i; if (Day == i && (FilterDay & Bit) == 0) { Show = false; break; } } } if (Show && FilterMonth) { for (int i=0; i<12; i++) { int Bit = 1 << i; if ((CurLocal.Month() == i + 1) && (FilterMonth & Bit) == 0) { Show = false; break; } } } if (Show && ValidStr(FilterYear)) { auto t = LString(FilterYear).SplitDelimit(" ,;:"); Show = false; for (unsigned i=0; i= MAX_RECUR) break; TimePeriod &p = Periods.New(); p.s = CurLocal; p.e = CurLocal; p.e.AddHours(Diff.Hours()); p.e.AddMinutes(Diff.Minutes()); } Count++; } } // Now process periods into 1 per day segments if needed for (unsigned k=0; k Calendar::WorkDayEnd) { t.e = t.e.EndOfDay(); } else { t.e.Hours(Calendar::WorkDayEnd); t.e.Minutes(0); t.e.Seconds(0); } if (t.Overlap(w)) { t.c = this; Times.Add(t); } } else if (i.IsSameDay(n.e)) { // End day TimePeriod t; t.s = n.e; t.e = n.e; int h = t.s.Hours(); if (h < Calendar::WorkDayStart) t.s.Hours(0); else t.s.Hours(Calendar::WorkDayStart); t.s.Minutes(0); t.s.Seconds(0); if (t.Overlap(w)) { t.c = this; Times.Add(t); } break; } else { // Middle day TimePeriod t; t.s = i.StartOfDay(); t.s.Hours(Calendar::WorkDayStart); t.e = i.StartOfDay(); t.e.Hours(Calendar::WorkDayEnd); if (t.Overlap(w)) { t.c = this; Times.Add(t); } } } } else if (n.Overlap(w)) { n.c = this; Times.Add(n); } } return (int)Times.Length() > StartLen; } CalendarType Calendar::GetCalType() { CalendarType Type = CalEvent; GetField(FIELD_CAL_TYPE, (int&)Type); return Type; } void Calendar::SetCalType(CalendarType Type) { SetField(FIELD_CAL_TYPE, (int)Type); SetImage(Type == CalTodo ? ICON_TODO : ICON_CALENDAR); } LString Calendar::ToString() { LString s; auto obj = GetObject(); s.Printf("Calendar(%s,%s,%s)", obj ? obj->GetStr(FIELD_CAL_SUBJECT) : "#NoObject", obj ? obj->GetDate(FIELD_CAL_START_UTC)->Get().Get() : NULL, obj ? obj->GetDate(FIELD_CAL_END_UTC)->Get().Get() : NULL); return s; } LColour Calendar::GetColour() { if (GetObject()) { int64 c = GetObject()->GetInt(FIELD_COLOUR); if (c >= 0) return LColour((uint32_t)c, 32); } if (Source) return Source->GetColour(); return LColour(L_LOW); } void Calendar::OnPaintView(LSurface *pDC, LFont *Font, LRect *Pos, TimePeriod *Period) { LRect p = *Pos; const char *Title = "..."; LDateTime Now; float Sx = 1.0; float Sy = 1.0; if (pDC->IsPrint()) { auto DcDpi = pDC->GetDpi(); auto SrcDpi = LScreenDpi(); Sx = (float)DcDpi.x / SrcDpi.x; Sy = (float)DcDpi.y / SrcDpi.y; } float Scale = Sx < Sy ? Sx : Sy; Now.SetNow(); GetField(FIELD_CAL_SUBJECT, Title); bool Delayed = GetObject() ? GetObject()->GetInt(FIELD_STATUS) == Store3Delayed : false; LColour Grey(192, 192, 192); auto View = GetView(); if (View && View->Selection.HasItem(this)) { // selected LColour f = L_FOCUS_SEL_FORE; LColour b = L_FOCUS_SEL_BACK; if (Delayed) { f = f.Mix(Grey); b = b.Mix(Grey); } pDC->Colour(f); pDC->Box(&p); p.Inset(1, 1); pDC->Colour(b); Font->Colour(f, b); } else { LColour Text(0x80, 0x80, 0x80); LColour Ws(L_WORKSPACE); auto Base = GetColour(); auto Qtr = Ws.Mix(Base, 0.1f); auto Half = Ws.Mix(Base, 0.4f); // not selected LColour f, b; if (Period && Now < Period->s) { // future entry (full colour) f = Base; b = Half; } else { // historical entry (half strength colour) f = Half; b = Qtr; } if (Delayed) { f = f.Mix(Grey); b = b.Mix(Grey); } pDC->Colour(f); pDC->Box(&p); p.Inset(1, 1); pDC->Colour(b); Font->Colour(Text, b); } pDC->Rectangle(&p); p.Inset((int)SX(1), (int)SY(1)); Font->Transparent(false); LDisplayString ds(Font, Title); float Ht = p.Y() > 0 ? (float)ds.Y() / p.Y() : 1.0f; if (Ht < 0.75f) p.Inset((int)SX(3), (int)SY(3)); else if (Ht < 0.95f) p.Inset((int)SX(1), (int)SY(1)); ds.Draw(pDC, p.x1, p.y1, &p); ViewPos.Union(Pos); } Thing &Calendar::operator =(Thing &Obj) { if (Obj.GetObject() && GetObject()) { GetObject()->CopyProps(*Obj.GetObject()); } return *this; } bool Calendar::operator ==(Thing &t) { Calendar *c = t.IsCalendar(); if (!c) return false; { LDateTime a, b; if (GetField(FIELD_CAL_START_UTC, a) && c->GetField(FIELD_CAL_START_UTC, b) && a != b) return false; if (GetField(FIELD_CAL_END_UTC, a) && c->GetField(FIELD_CAL_END_UTC, b) && a != b) return false; } { const char *a, *b; if (GetField(FIELD_CAL_SUBJECT, a) && c->GetField(FIELD_CAL_SUBJECT, b) && Stricmp(a, b)) return false; if (GetField(FIELD_CAL_LOCATION, a) && c->GetField(FIELD_CAL_LOCATION, b) && Stricmp(a, b)) return false; } return true; } int Calendar::Compare(LListItem *Arg, ssize_t FieldId) { Calendar *c1 = this; Calendar *c2 = dynamic_cast(Arg); if (c1 && c2) { switch (FieldId) { case FIELD_CAL_START_UTC: case FIELD_CAL_END_UTC: { LDateTime d1, d2; if (!c1->GetField((int)FieldId, d1)) d1.SetNow(); if (!c2->GetField((int)FieldId, d2)) d2.SetNow(); return d1.Compare(&d2); break; } case FIELD_CAL_SUBJECT: case FIELD_CAL_LOCATION: { const char *s1 = "", *s2 = ""; if (c1->GetField((int)FieldId, s1) && c2->GetField((int)FieldId, s2)) { return _stricmp(s1, s2); } break; } } } return 0; } uint32_t Calendar::GetFlags() { return 0; } ThingUi *Calendar::DoUI(MailContainer *c) { if (!Ui) { Ui = new CalendarUi(this); } return Ui; } ThingUi *Calendar::GetUI() { return Ui; } bool Calendar::SetUI(ThingUi *ui) { if (Ui) Ui->Item = NULL; Ui = dynamic_cast(ui); if (Ui) Ui->Item = this; return true; } void Calendar::OnMouseClick(LMouse &m) { if (m.IsContextMenu()) { auto View = GetView(); DoContextMenu(m, View ? (LView*)View : (LView*)App); } else if (m.Down() && m.Left() && m.Double()) { DoUI(); } } void Calendar::OnCreate() { for (auto v: CalendarView::CalendarViews) v->OnContentsChanged(); } bool Calendar::OnDelete() { bool IsTodo = GetCalType() == CalTodo; bool Status = Thing::OnDelete(); if (IsTodo) DeleteObj(TodoView); for (auto v: CalendarView::CalendarViews) v->OnContentsChanged(); return Status; } bool Calendar::GetParentSelection(LList *Lst, List &s) { if (Lst) { return Lst->GetSelection(s); } if (auto View = GetView()) { LArray &a = View->GetSelection(); for (auto cal: a) { auto str = cal->ToString(); s.Insert(cal); } return true; } return false; } #define IDM_MOVE_TO 4000 void Calendar::DoContextMenu(LMouse &m, LView *Parent) { LSubMenu Sub; LScriptUi s(&Sub); s.Sub->AppendItem(LLoadString(IDS_OPEN), IDM_OPEN); s.Sub->AppendItem(LLoadString(IDS_DELETE), IDM_DELETE); s.Sub->AppendItem(LLoadString(IDS_EXPORT), IDM_EXPORT); s.Sub->AppendSeparator(); s.Sub->AppendItem(LLoadString(IDS_INSPECT), IDM_INSPECT); auto Cv = dynamic_cast(Parent); if (Cv) { s.Sub->AppendSeparator(); for (unsigned n = 0; nGetName() && Cs->IsWritable()) { auto msg = LString::Fmt("Move to '%s'\n", Cs->GetName()); s.Sub->AppendItem(msg, IDM_MOVE_TO + n, Source != Cs); } } } LArray Callbacks; if (App->GetScriptCallbacks(LThingContextMenu, Callbacks)) { LScriptArguments Args(NULL); Args[0] = new LVariant(App); Args[1] = new LVariant(this); Args[2] = new LVariant(&s); for (auto cb: Callbacks) App->ExecuteScriptCallback(*cb, Args); Args.DeleteObjects(); } int Result = s.Sub->Float(Parent, m); switch (Result) { default: { auto Idx = Result - IDM_MOVE_TO; if (Cv && Idx >= 0 && Idx < (int)CalendarSource::GetSources().Length()) { auto Dst = CalendarSource::GetSources().ItemAt(Idx); if (!Dst) { LAssert(!"No dst?"); return; } auto Fsrc = dynamic_cast(Dst); if (!Fsrc) { LAssert(!"No cal src?"); break; } auto Path = Fsrc->GetPath(); auto DstFolder = App->GetFolder(Path); if (!DstFolder) { LAssert(!"Path doesn't exist?"); return; } LArray Items{ this }; DstFolder->MoveTo( Items, false, [this, Cv, Dst](auto result, auto itemStatus) { if (result) { Source = Dst; Cv->Invalidate(); } } ); return; } // Handle any installed callbacks for menu items for (auto &cb: s.Callbacks) { if (cb.Param == Result) { LScriptArguments Args(NULL); Args[0] = new LVariant(App); Args[1] = new LVariant(this); Args[2] = new LVariant(cb.Param); App->ExecuteScriptCallback(cb, Args); Args.DeleteObjects(); } } break; } case IDM_OPEN: { DoUI(); break; } case IDM_DELETE: { LVariant ConfirmDelete; App->GetOptions()->GetValue(OPT_ConfirmDelete, ConfirmDelete); if (!ConfirmDelete.CastInt32() || LgiMsg(Parent, LLoadString(IDS_DELETE_ASK), AppName, MB_YESNO) == IDYES) { List Del; auto PList = dynamic_cast(Parent); if (GetParentSelection(PList ? PList : GetList(), Del)) { for (auto i: Del) { if (auto t = dynamic_cast(i)) { t->OnDelete(); } else if (auto Todo = dynamic_cast(i)) { if (auto c = Todo->GetTodo()) c->OnDelete(); } } } else { OnDelete(); } } break; } case IDM_EXPORT: { ExportAll(GetList(), sMimeVCalendar, NULL); break; } case IDM_INSPECT: { new ObjectInspector(App, this); break; } } } const char *Calendar::GetText(int i) { static char s[64]; if (!GetObject()) { LAssert(!"No storage object"); return 0; } int Field = 0; if (FieldArray.Length()) { if (i >= 0 && i < (int) FieldArray.Length()) Field = FieldArray[i]; } else if (i >= 0 && i < CountOf(DefaultCalenderFields)) { Field = DefaultCalenderFields[i]; } if (!Field) return 0; ItemFieldDef *Def = GetFieldDefById(Field); if (!Def) { LAssert(!"Where is the field def?"); return 0; } switch (Def->Type) { case GV_STRING: { return GetObject()->GetStr(Field); break; } case GV_INT32: { sprintf_s(s, sizeof(s), LPrintfInt64, GetObject()->GetInt(Field)); return s; break; } case GV_DATETIME: { // Get any effective timezone for this event. LString Tz = GetObject()->GetStr(FIELD_CAL_TIMEZONE); auto dt = GetObject()->GetDate(Field); if (dt && dt->IsValid()) { LDateTime tmp = *dt; LOG_DEBUG("%s:%i - GetText.UTC %i = %s\n", _FL, i, tmp.Get().Get()); bool UseLocal = true; tmp.SetTimeZone(0, false); if (Tz.Get()) { bool HasPt = false, HasDigit = false; char *e = Tz.Get(); while (strchr(" \t\r\n-.+", *e) || IsDigit(*e)) { if (*e == '.') HasPt = true; if (IsDigit(*e)) HasDigit = true; e++; } if (HasDigit) { if (HasPt) { double TzHrs = Tz.Float(); double i, f = modf(TzHrs, &i); int Mins = (int) ((i * 60) + (f * 60)); tmp.AddMinutes(Mins); } else { int64 i = Tz.Int(); int a = (int)ABS(i); int Mins = (int) (((a / 100) * 60) + (a % 100)); tmp.AddMinutes(i < 0 ? -Mins : Mins); } UseLocal = false; } } if (UseLocal) tmp.ToLocal(); LOG_DEBUG("%s:%i - GetText.Local %i = %s\n", _FL, i, tmp.Get().Get()); tmp.Get(s, sizeof(s)); return s; } break; } default: { LAssert(0); break; } } return 0; } int *Calendar::GetDefaultFields() { return DefaultCalenderFields; } const char *Calendar::GetFieldText(int Field) { return 0; } bool Calendar::Overlap(Calendar *c) { if (c) { LDateTime Ts, Te, Cs, Ce; if (GetField(FIELD_CAL_START_UTC, Ts) && c->GetField(FIELD_CAL_START_UTC, Cs)) { if (!GetField(FIELD_CAL_END_UTC, Te)) { Te = Ts; Te.AddHours(1); } if (!c->GetField(FIELD_CAL_END_UTC, Ce)) { Ce = Cs; Ce.AddHours(1); } if ((Ce <= Ts) || (Cs >= Te)) { return false; } return true; } } return false; } bool Calendar::Save(ScribeFolder *Folder) { bool Status = false; // Check the dates are the right way around LDateTime Start, End; if (GetField(FIELD_CAL_START_UTC, Start) && GetField(FIELD_CAL_END_UTC, End)) { if (End < Start) { SetField(FIELD_CAL_START_UTC, End); SetField(FIELD_CAL_END_UTC, Start); } } auto ChangeEvent = [this](bool Status) { auto View = GetView(); if (View && Status) { View->OnContentsChanged(Source); OnSerialize(true); } }; if (!Folder) Folder = GetFolder(); if (!Folder && App) Folder = App->GetFolder(FOLDER_CALENDAR); if (GetObject() && GetObject()->GetInt(FIELD_STORE_TYPE) == Store3Webdav) { auto ParentObj = Folder ? Folder->GetObject() : NULL; Store3Status s = GetObject()->Save(ParentObj); Status = s > Store3Error; if (Status) SetDirty(false); ChangeEvent(Status); } else { // FIXME: This can't wait for WriteThing to finish it's call back... Status = true; if (Folder) { LDateTime Now; GetObject()->SetDate(FIELD_DATE_MODIFIED, &Now.SetNow().ToUtc()); Folder->WriteThing(this, [this, ChangeEvent](auto Status) { if (Status > Store3Error) SetDirty(false); ChangeEvent(Status); }); } else ChangeEvent(Status); } return Status; } // Import/Export bool Calendar::GetFormats(bool Export, LString::Array &MimeTypes) { MimeTypes.Add(sMimeVCalendar); return MimeTypes.Length() > 0; } Thing::IoProgress Calendar::Import(IoProgressImplArgs) { if (Stricmp(mimeType, sMimeVCalendar) && Stricmp(mimeType, sMimeICalendar)) { ErrMsg.Printf("Unknown mimetype '%s'", mimeType); IoProgressNotImpl(); } VCal vCal; if (!vCal.Import(GetObject(), stream)) { ErrMsg.Printf("vCal import failed"); IoProgressError(ErrMsg); } IoProgressSuccess(); } Thing::IoProgress Calendar::Export(IoProgressImplArgs) { if (Stricmp(mimeType, sMimeVCalendar)) IoProgressNotImpl(); VCal vCal; if (!vCal.Export(GetObject(), stream)) IoProgressError("vCal export failed."); IoProgressSuccess(); } char *Calendar::GetDropFileName() { if (!DropFileName) { const char *Name = 0; GetField(FIELD_CAL_SUBJECT, Name); DropFileName.Reset(MakeFileName(Name ? Name : "Cal", "ics")); } return DropFileName; } bool Calendar::GetDropFiles(LString::Array &Files) { bool Status = false; if (GetDropFileName()) { if (!LFileExists(DropFileName)) { LAutoPtr F(new LFile); if (F->Open(DropFileName, O_WRITE)) { F->SetSize(0); Export(AutoCast(F), sMimeVCalendar); } } if (LFileExists(DropFileName)) { Files.Add(DropFileName.Get()); Status = true; } } return Status; } void Calendar::OnPrintHeaders(ScribePrintContext &Context) { LDisplayString *ds = Context.Text(LLoadString(IDS_CAL_EVENT)); LRect &r = Context.MarginPx; int Line = ds->Y(); LDrawListSurface *Page = Context.Pages.Last(); Page->Rectangle(r.x1, Context.CurrentY + (Line * 5 / 10), r.x2, Context.CurrentY + (Line * 6 / 10)); Context.CurrentY += Line; } void Calendar::OnPrintText(ScribePrintContext &Context, LPrintPageRanges &Pages) { // Print document for (ItemFieldDef *Fld = CalendarFields; Fld->FieldId; Fld++) { LString Value; switch (Fld->Type) { case GV_STRING: { Value = GetObject()->GetStr(Fld->FieldId); break; } case GV_DATETIME: { auto Dt = GetObject()->GetDate(Fld->FieldId); if (Dt) { char s[64] = ""; Dt->Get(s, sizeof(s)); Value = s; } break; } default: break; } if (Value) { LString f; const char *Name = LLoadString(Fld->FieldId); f.Printf("%s: %s", Name ? Name : Fld->DisplayText, Value.Get()); // LDisplayString *ds = Context.Text(f); } } } bool Calendar::GetVariant(const char *Name, LVariant &Value, const char *Array) { ScribeDomType Fld = StrToDom(Name); switch (Fld) { // String variables case SdSubject: // Type: String Value = GetObject()->GetStr(FIELD_CAL_SUBJECT); break; case SdTo: // Type: String Value = GetObject()->GetStr(FIELD_TO); break; case SdLocation: // Type: String Value = GetObject()->GetStr(FIELD_CAL_LOCATION); break; case SdUid: // Type: String Value = GetObject()->GetStr(FIELD_UID); break; case SdReminders: // Type: String Value = GetObject()->GetStr(FIELD_CAL_REMINDERS); break; case SdNotes: // Type: String Value = GetObject()->GetStr(FIELD_CAL_NOTES); break; case SdStatus: // Type: String Value = GetObject()->GetStr(FIELD_CAL_STATUS); break; // Int variables case SdType: // Type: Int32 Value = GetObject()->GetInt(FIELD_CAL_TYPE); break; case SdCompleted: // Type: Int32 Value = GetObject()->GetInt(FIELD_CAL_COMPLETED); break; case SdShowTimeAs: // Type: Int32 Value = GetObject()->GetInt(FIELD_CAL_SHOW_TIME_AS); break; case SdRecur: // Type: Int32 Value = GetObject()->GetInt(FIELD_CAL_RECUR); break; case SdRecurFreq: // Type: Int32 Value = GetObject()->GetInt(FIELD_CAL_RECUR_FREQ); break; case SdRecurInterval: // Type: Int32 Value = GetObject()->GetInt(FIELD_CAL_RECUR_INTERVAL); break; case SdRecurEndCount: // Type: Int32 Value = GetObject()->GetInt(FIELD_CAL_RECUR_END_COUNT); break; case SdRecurEndType: // Type: Int32 Value = GetObject()->GetInt(FIELD_CAL_RECUR_END_TYPE); break; case SdRecurFilterDays: // Type: Int32 Value = GetObject()->GetInt(FIELD_CAL_RECUR_FILTER_DAYS); break; case SdRecurFilterMonths: // Type: Int32 Value = GetObject()->GetInt(FIELD_CAL_RECUR_FILTER_MONTHS); break; case SdPrivacy: // Type: Int32 Value = GetObject()->GetInt(FIELD_CAL_PRIVACY); break; case SdAllDay: // Type: Int32 Value = GetObject()->GetInt(FIELD_CAL_ALL_DAY); break; case SdColour: // Type: Int64 Value = GetObject()->GetInt(FIELD_COLOUR); break; // Date time fields case SdStart: // Type: DateTime return GetDateField(FIELD_CAL_START_UTC, Value); case SdEnd: // Type: DateTime return GetDateField(FIELD_CAL_END_UTC, Value); case SdDateModified: // Type: DateTime return GetDateField(FIELD_DATE_MODIFIED, Value); default: return false; } return true; } bool Calendar::SetVariant(const char *Name, LVariant &Value, const char *Array) { ScribeDomType Fld = StrToDom(Name); switch (Fld) { // String variables case SdSubject: Value = GetObject()->SetStr(FIELD_CAL_SUBJECT, Value.Str()); break; case SdTo: Value = GetObject()->SetStr(FIELD_TO, Value.Str()); break; case SdLocation: Value = GetObject()->SetStr(FIELD_CAL_LOCATION, Value.Str()); break; case SdUid: Value = GetObject()->SetStr(FIELD_UID, Value.Str()); break; case SdReminders: Value = GetObject()->SetStr(FIELD_CAL_REMINDERS, Value.Str()); break; case SdNotes: Value = GetObject()->SetStr(FIELD_CAL_NOTES, Value.Str()); break; case SdStatus: Value = GetObject()->SetStr(FIELD_CAL_STATUS, Value.Str()); break; // Int variables case SdType: Value = GetObject()->SetInt(FIELD_CAL_TYPE, Value.CastInt32()); break; case SdCompleted: Value = GetObject()->SetInt(FIELD_CAL_COMPLETED, Value.CastInt32()); break; case SdShowTimeAs: Value = GetObject()->SetInt(FIELD_CAL_SHOW_TIME_AS, Value.CastInt32()); break; case SdRecur: Value = GetObject()->SetInt(FIELD_CAL_RECUR, Value.CastInt32()); break; case SdRecurFreq: Value = GetObject()->SetInt(FIELD_CAL_RECUR_FREQ, Value.CastInt32()); break; case SdRecurInterval: Value = GetObject()->SetInt(FIELD_CAL_RECUR_INTERVAL, Value.CastInt32()); break; case SdRecurEndCount: Value = GetObject()->SetInt(FIELD_CAL_RECUR_END_COUNT, Value.CastInt32()); break; case SdRecurEndType: Value = GetObject()->SetInt(FIELD_CAL_RECUR_END_TYPE, Value.CastInt32()); break; case SdRecurFilterDays: Value = GetObject()->SetInt(FIELD_CAL_RECUR_FILTER_DAYS, Value.CastInt32()); break; case SdRecurFilterMonths: Value = GetObject()->SetInt(FIELD_CAL_RECUR_FILTER_MONTHS, Value.CastInt32()); break; case SdPrivacy: Value = GetObject()->SetInt(FIELD_CAL_PRIVACY, Value.CastInt32()); break; case SdColour: Value = GetObject()->SetInt(FIELD_COLOUR, Value.CastInt32()); break; case SdAllDay: Value = GetObject()->SetInt(FIELD_CAL_ALL_DAY, Value.CastInt32()); break; // Date time fields case SdStart: return SetDateField(FIELD_CAL_START_UTC, Value); case SdEnd: return SetDateField(FIELD_CAL_END_UTC, Value); case SdDateModified: return SetDateField(FIELD_DATE_MODIFIED, Value); default: return false; } return true; } bool Calendar::CallMethod(const char *MethodName, LScriptArguments &Args) { return Thing::CallMethod(MethodName, Args); } ////////////////////////////////////////////////////////////////////////////// bool SerializeUi(ItemFieldDef *Defs, LDataI *Object, LViewI *View, bool ToUi) { if (!Object || !Defs || !View) return false; if (ToUi) { for (ItemFieldDef *d = Defs; d->FieldId; d++) { if (d->CtrlId <= 0) continue; switch (d->Type) { case GV_STRING: { auto s = Object->GetStr(d->FieldId); View->SetCtrlName(d->CtrlId, s); break; } case GV_INT32: { int64 i = Object->GetInt(d->FieldId); View->SetCtrlValue(d->CtrlId, i); break; } case GV_DATETIME: { char s[64] = ""; auto dt = Object->GetDate(d->FieldId); if (dt && dt->Year()) dt->Get(s, sizeof(s)); View->SetCtrlName(d->CtrlId, s); break; } default: { LAssert(0); break; } } } } else { for (ItemFieldDef *d = Defs; d->FieldId; d++) { if (d->CtrlId <= 0) continue; switch (d->Type) { case GV_STRING: { const char *s = View->GetCtrlName(d->CtrlId); Object->SetStr(d->FieldId, s); break; } case GV_INT32: { int i = (int)View->GetCtrlValue(d->CtrlId); Object->SetInt(d->FieldId, i); break; } case GV_DATETIME: { const char *s = View->GetCtrlName(d->CtrlId); LDateTime dt; dt.Set(s); Object->SetDate(d->FieldId, &dt); break; } default: { LAssert(0); break; } } } } return true; } ////////////////////////////////////////////////////////////////////////////// class LEditDropDown : public LEdit { public: enum DropType { DropNone, DropDate, DropTime, }; protected: DropType Type; LAutoPtr Popup; public: LEditDropDown(int id) : LEdit(id, 0, 0, 60, 20, NULL) { Type = DropNone; SetObjectName(Res_Custom); } void SetType(DropType type) { Type = type; } const char *GetClass() { return "LEditDropDown"; } void OnMouseClick(LMouse &m) { LEdit::OnMouseClick(m); if (Focus() && Popup && !Popup->Visible()) { Popup->Visible(true); } } void OnFocus(bool f) { if (f) { if (!Popup) { if (Type == DropDate) Popup.Reset(new LDatePopup(this)); else if (Type == DropTime) Popup.Reset(new LTimePopup(this)); if (Popup) { Popup->TakeFocus(false); LPoint p(0, Y()); PointToScreen(p); LRect r = Popup->GetPos(); r.Offset(p.x - r.x1, p.y - r.y1); Popup->SetPos(r); } } if (Popup) Popup->Visible(true); } } int OnNotify(LViewI *Wnd, LNotification n) { /* LgiTrace("OnNotify %s, %i\n", Wnd->GetClass(), Flags); if (Wnd == (LViewI*)Popup) { GDatePopup *Date; if ((Date = dynamic_cast(Popup.Get()))) { LDateTime Ts = Date->Get(); char s[256]; Ts.GetDate(s, sizeof(s)); Name(s); } else LgiTrace("%s:%i - Incorrect pop up type.\n", _FL); } */ return 0; } void OnChildrenChanged(LViewI *Wnd, bool Attaching) { if (Wnd == (LViewI*)Popup.Get() && !Attaching) { // This gets called in the destructor of the Popup, so we should // lose the pointer to it. Popup.Release(); } } }; struct LEditDropDownFactory : public LViewFactory { LView *NewView(const char *Class, LRect *Pos, const char *Text) { if (!_stricmp(Class, "LEditDropDown")) { return new LEditDropDown(-1); } return NULL; } } EditDropDownFactory; ////////////////////////////////////////////////////////////////////////////// class LRecurDlg : public LDialog { CalendarUi *Ui = NULL; LEditDropDown *EndOnDate = NULL; LCombo *Repeats = NULL; bool AcceptNotify = true; public: LRecurDlg(CalendarUi *ui) { Ui = ui; SetParent(ui); if (LoadFromResource(IDD_CAL_RECUR)) { if (GetViewById(IDC_ON_DATE, EndOnDate)) { EndOnDate->SetType(LEditDropDown::DropDate); LDateTime dt; dt.SetNow(); char s[64]; dt.GetDate(s, sizeof(s)); EndOnDate->Name(s); } if (GetViewById(IDC_REPEATS, Repeats)) { Repeats->Insert(LLoadString(IDS_DAY)); Repeats->Insert(LLoadString(IDS_WEEK)); Repeats->Insert(LLoadString(IDS_MONTH)); Repeats->Insert(LLoadString(IDS_YEAR)); Repeats->Value(1); } Radio(IDC_NEVER); SetCtrlValue(IDC_EVERY, 1); Serialize(false); } } ~LRecurDlg() { } void Serialize(bool Write) { int DayCtrls[] = { IDC_SUNDAY, IDC_MONDAY, IDC_TUESDAY, IDC_WEDNESDAY, IDC_THURSDAY, IDC_FRIDAY, IDC_SATURDAY }; Calendar *c = Ui->GetCal(); LDataI *o = c->GetObject(); if (Write) { // Dlg -> Object int64 v = Repeats->Value(); o->SetInt(FIELD_CAL_RECUR_FREQ, v); v = GetCtrlValue(IDC_EVERY); o->SetInt(FIELD_CAL_RECUR_INTERVAL, v); int DayFilter = 0; for (int i=0; i<7; i++) { if (GetCtrlValue(DayCtrls[i])) DayFilter |= 1 << i; } o->SetInt(FIELD_CAL_RECUR_FILTER_DAYS, DayFilter); if (GetCtrlValue(IDC_NEVER)) { o->SetInt(FIELD_CAL_RECUR_END_TYPE, CalEndNever); } else if (GetCtrlValue(IDC_AFTER)) { o->SetInt(FIELD_CAL_RECUR_END_TYPE, CalEndOnCount); o->SetInt(FIELD_CAL_RECUR_END_COUNT, GetCtrlValue(IDC_AFTER_COUNT)); } else if (GetCtrlValue(IDC_ON)) { o->SetInt(FIELD_CAL_RECUR_END_TYPE, CalEndOnDate); LDateTime e; e.SetDate(GetCtrlName(IDC_ON_DATE)); e.SetTime("11:59:59"); o->SetDate(FIELD_CAL_RECUR_END_DATE, &e); } else LAssert(0); } else { // Object -> Dlg int64 v = o->GetInt(FIELD_CAL_RECUR_FREQ); Repeats->Value(v); OnRepeat(v); v = o->GetInt(FIELD_CAL_RECUR_INTERVAL); SetCtrlValue(IDC_EVERY, MAX(v, 1)); int DayFilter = (int)o->GetInt(FIELD_CAL_RECUR_FILTER_DAYS); for (int i=0; i<7; i++) { SetCtrlValue(DayCtrls[i], (DayFilter & (1 << i)) ? 1 : 0); } CalRecurEndType EndType = (CalRecurEndType) o->GetInt(FIELD_CAL_RECUR_END_TYPE); if (EndType == CalEndOnCount) { Radio(IDC_AFTER); SetCtrlValue(IDC_AFTER_COUNT, o->GetInt(FIELD_CAL_RECUR_END_COUNT)); } else if (EndType == CalEndOnDate) { Radio(IDC_ON); auto e = o->GetDate(FIELD_CAL_RECUR_END_DATE); if (e) SetCtrlName(IDC_ON_DATE, e->GetDate()); } else // Default to never... { Radio(IDC_NEVER); } } } void Radio(int Id) { AcceptNotify = false; SetCtrlValue(IDC_NEVER, Id == IDC_NEVER); SetCtrlValue(IDC_AFTER, Id == IDC_AFTER); SetCtrlEnabled(IDC_AFTER_COUNT, Id == IDC_AFTER); SetCtrlEnabled(IDC_OCCURRENCES, Id == IDC_AFTER); SetCtrlValue(IDC_ON, Id == IDC_ON); SetCtrlEnabled(IDC_ON_DATE, Id == IDC_ON); AcceptNotify = true; } void OnRepeat(int64 Val) { LViewI *v; if (!GetViewById(IDC_TIME_TYPE, v)) return; switch (Val) { case 0: v->Name(LLoadString(IDS_DAYS)); break; case 1: v->Name(LLoadString(IDS_WEEKS)); break; case 2: v->Name(LLoadString(IDS_MONTHS)); break; case 3: v->Name(LLoadString(IDS_YEARS)); break; } v->SendNotify(LNotifyTableLayoutRefresh); } int OnNotify(LViewI *Ctrl, LNotification n) { if (!AcceptNotify) return 0; switch (Ctrl->GetId()) { case IDC_NEVER: case IDC_AFTER: case IDC_ON: Radio(Ctrl->GetId()); break; case IDC_REPEATS: OnRepeat(Ctrl->Value()); break; case IDOK: Serialize(true); // Fall through case IDCANCEL: EndModal(Ctrl->GetId() == IDOK); break; } return 0; } }; /////////////////////////////////////////////////////////////////////////////// struct CalendarUiPriv { CalendarUi *Ui; LEditDropDown *StartDate, *StartTime; LEditDropDown *EndDate, *EndTime; LEdit *Entry; AddressBrowse *Browse; LList *Guests; LList *Reminders; LCombo *ReminderType; LCombo *ReminderUnit; LCombo *CalSelect; LColourSelect *Colour; CalendarUiPriv(CalendarUi *ui) { Ui = ui; StartDate = StartTime = NULL; EndDate = EndTime = NULL; CalSelect = NULL; Entry = NULL; Browse = NULL; Guests = NULL; Colour = NULL; Reminders = NULL; ReminderType = NULL; ReminderUnit = NULL; } void OnGuest() { const char *g = Ui->GetCtrlName(IDC_GUEST_ENTRY); if (!g) return; Mailto mt(Ui->App, g); for (auto a: mt.To) { ListAddr *la = new ListAddr(Ui->App, a); if (la) { Guests->Insert(la); } } Ui->SetCtrlName(IDC_GUEST_ENTRY, ""); } }; ////////////////////////////////////////////////////////////////////////////// class CalendarAttachmentItem : public LListItem { CalendarUi *Ui = NULL; LDataI *Obj = NULL; LString Cache; public: enum Columns { CFileName, CMimeType, CDateMod, CSize }; CalendarAttachmentItem(CalendarUi *ui, LDataI *obj) : Ui(ui), Obj(obj) { } LDataI *GetObject() { return Obj; } bool IsRemote() { return Obj->GetStr(FIELD_URI) != NULL; } void SaveAs() { auto sel = new LFileSelect(GetList()); sel->Name(Obj->GetStr(FIELD_NAME)); sel->Save([this, sel](auto dlg, auto ok) { if (ok) Save(sel->Name()); delete dlg; }); } bool Save(LString path) { auto in = Obj->GetStream(_FL); if (!in) return false; auto inSize = in->GetSize(); LFile out(path, O_WRITE); if (!out) return false; LCopyStreamer cp; ssize_t copied = cp.Copy(in, &out); return copied == inSize; } void Open() { if (IsRemote()) { auto uri = Obj->GetStr(FIELD_URI); LExecute(uri); } else // Local: save and open { auto file = Obj->GetStr(FIELD_NAME); LFile::Path p(ScribeTempPath(), LGetLeaf(file)); if (Save(p.GetFull())) { LExecute(p.GetFull()); } } } void Delete() { Ui->PostEvent(M_DELETE_ATTACHMENT, (LMessage::Param)this); } void OnMouseClick(LMouse &m) override { if (m.IsContextMenu()) { LSubMenu sub; sub.AppendItem("Save As", IDM_SAVEAS, !IsRemote()); sub.AppendItem("Open Uri", IDM_OPEN, IsRemote()); sub.AppendSeparator(); sub.AppendItem("Delete", IDM_DELETE); switch (sub.Float(GetList(), m)) { case IDM_SAVEAS: SaveAs(); break; case IDM_OPEN: Open(); break; case IDM_DELETE: Delete(); break; } } else if (m.Left()) { if (m.Down() && m.Double()) Open(); } } bool OnKey(LKey &k) override { switch (k.vkey) { case LK_DELETE: if (k.Down()) Delete(); return true; default: break; } return false; } const char *GetText(int Col = 0) { switch (Col) { case CFileName: return Obj->GetStr(FIELD_NAME); case CMimeType: return Obj->GetStr(FIELD_MIME_TYPE); case CDateMod: { auto mod = Obj->GetDate(FIELD_DATE_MODIFIED); if (mod) Cache = mod->Get(); return Cache; } case CSize: return Obj->GetStr(FIELD_SIZE); } return NULL; } }; ////////////////////////////////////////////////////////////////////////////// CalendarUi::CalendarUi(Calendar *item) : ThingUi(item, LLoadString(IDS_CAL_EVENT)) { Item = item; d = new CalendarUiPriv(this); #if WINNATIVE CreateClassW32("Event", LoadIcon(LProcessInst(), MAKEINTRESOURCE(IDI_EVENT))); #endif if (!Attach(NULL)) LAssert(0); else { LoadFromResource(IDD_CAL, this); AttachChildren(); if (!SerializeState(Item->App->GetOptions(), OPT_CalendarEventPos, true)) { LRect p(100, 100, 900, 700); SetPos(p); MoveToCenter(); } if (GetViewById(IDC_START_DATE, d->StartDate)) { d->StartDate->SetType(LEditDropDown::DropDate); } if (GetViewById(IDC_START_TIME, d->StartTime)) { d->StartTime->SetType(LEditDropDown::DropTime); } if (GetViewById(IDC_END_DATE, d->EndDate)) { d->EndDate->SetType(LEditDropDown::DropDate); } if (GetViewById(IDC_END_TIME, d->EndTime)) { d->EndTime->SetType(LEditDropDown::DropTime); } GetViewById(IDC_GUEST_ENTRY, d->Entry); if (GetViewById(IDC_GUESTS, d->Guests)) { d->Guests->AddColumn(LLoadString(IDS_ADDRESS), 120); d->Guests->AddColumn(LLoadString(IDS_NAME), 120); d->Guests->ColumnHeaders(false); } if (GetViewById(IDC_REMINDERS, d->Reminders)) { d->Reminders->AddColumn(LLoadString(FIELD_CAL_REMINDER_TIME), 200); d->Reminders->AddColumn(LLoadString(IDC_DELETE), 20); d->Reminders->ColumnHeaders(false); } if (GetViewById(IDC_REMINDER_TYPE, d->ReminderType)) { d->ReminderType->Insert(LLoadString(IDS_EMAIL)); d->ReminderType->Insert(LLoadString(IDS_POPUP)); d->ReminderType->Insert("Script"); d->ReminderType->Value(1); } SetCtrlValue(IDC_REMINDER_VALUE, 10); if (GetViewById(IDC_REMINDER_UNIT, d->ReminderUnit)) { d->ReminderUnit->Insert(LLoadString(IDS_MINUTES)); d->ReminderUnit->Insert(LLoadString(IDS_HOURS)); d->ReminderUnit->Insert(LLoadString(IDS_DAYS)); d->ReminderUnit->Insert(LLoadString(IDS_WEEKS)); d->ReminderUnit->Value(0); } SetCtrlValue(IDC_AVAILABLE_TYPE, 1); if (GetViewById(IDC_COLOUR, d->Colour)) { LArray Colours; Colours.Add(LColour(84, 132, 237, 255)); Colours.Add(LColour(164, 189, 252, 255)); Colours.Add(LColour(70, 214, 219, 255)); Colours.Add(LColour(122, 231, 191, 255)); Colours.Add(LColour(81, 183, 73, 255)); Colours.Add(LColour(251, 215, 91, 255)); Colours.Add(LColour(255, 184, 120, 255)); Colours.Add(LColour(255, 136, 124, 255)); Colours.Add(LColour(220, 33, 39, 255)); Colours.Add(LColour(219, 173, 255, 255)); Colours.Add(LColour(225, 225, 225, 255)); d->Colour->SetColourList(&Colours); d->Colour->Value(0); } if (GetViewById(IDC_CALENDAR, d->CalSelect)) { LArray Sources; if (Item->App->GetCalendarSources(Sources)) { for (unsigned i=0; iCalSelect->Insert(cs->GetName()); } } } LView *s; if (GetViewById(IDC_SUBJECT, s)) s->Focus(true); LButton *btn; if (GetViewById(IDC_SAVE, btn)) btn->Default(true); OnLoad(); Visible(true); NotifyOn = true; } LViewI *v; if (GetViewById(IDC_REMINDER_TYPE, v)) { LNotification note; OnNotify(v, note); } RegisterHook(this, LKeyEvents); } CalendarUi::~CalendarUi() { if (Item) { if (Item->App) SerializeState(Item->App->GetOptions(), OPT_CalendarEventPos, false); Item->SetUI(NULL); } } bool CalendarUi::OnViewKey(LView *v, LKey &k) { THREAD_UNSAFE(false); if (k.CtrlCmd()) { switch (k.c16) { case 's': case 'S': { if (k.Down()) OnSave(); return true; } case 'w': case 'W': { if (k.Down()) { OnSave(); Quit(); } return true; } } if (k.vkey == LK_RETURN) { if (!k.Down()) { OnSave(); Quit(); } return true; } } return false; } int CalendarUi::OnCommand(int Cmd, int Event, OsView Window) { THREAD_UNSAFE(0); return 0; } void CalendarUi::OnPosChange() { THREAD_UNSAFE(); LWindow::OnPosChange(); if (FirstLayout && NotifyOn) { FirstLayout = false; LList *attachLst; if (GetViewById(IDC_ATTACHMENTS, attachLst)) attachLst->ResizeColumnsToContent(); } } LMessage::Result CalendarUi::OnEvent(LMessage *Msg) { THREAD_UNSAFE(0); switch (Msg->Msg()) { case M_DELETE_ATTACHMENT: { auto item = (CalendarAttachmentItem*)Msg->A(); if (!item) break; auto lst = item->GetList(); lst->Remove(item); Item->DeleteAttachment(item->GetObject()); delete item; break; } } return ThingUi::OnEvent(Msg); } void CalendarUi::CheckConsistancy() { THREAD_UNSAFE(); auto St = CurrentStart(); auto En = CurrentEnd(); - if (En < St) + if (St.IsValid() && + En.IsValid() && + En < St) { En = St; En.AddMinutes(30); SetCtrlName(IDC_END_DATE, En.GetDate().Get()); SetCtrlName(IDC_END_TIME, En.GetTime().Get()); } } LDateTime CalendarUi::CurrentStart() { LDateTime dt; THREAD_UNSAFE(dt); dt.SetDate(GetCtrlName(IDC_START_DATE)); dt.SetTime(GetCtrlName(IDC_START_TIME)); return dt; } LDateTime CalendarUi::CurrentEnd() { LDateTime dt; THREAD_UNSAFE(dt); dt.SetDate(GetCtrlName(IDC_END_DATE)); dt.SetTime(GetCtrlName(IDC_END_TIME)); return dt; } void CalendarUi::UpdateStartRelative() { THREAD_UNSAFE(); LDateTime dt = CurrentStart(); if (dt.IsValid()) { const char *s = RelativeTime(dt); SetCtrlName(IDC_START_REL, s); } } void CalendarUi::UpdateEndRelative() { THREAD_UNSAFE(); LDateTime dt = CurrentEnd(); if (dt.IsValid()) { const char *s = RelativeTime(dt); SetCtrlName(IDC_END_REL, s); } } void CalendarUi::UpdateRelative() { THREAD_UNSAFE(); CheckConsistancy(); UpdateStartRelative(); UpdateEndRelative(); } int CalendarUi::OnNotify(LViewI *Ctrl, LNotification n) { THREAD_UNSAFE(0); if (!NotifyOn) return false; switch (Ctrl->GetId()) { case IDC_START_DATE: case IDC_START_TIME: { CheckConsistancy(); UpdateStartRelative(); break; } case IDC_END_DATE: case IDC_END_TIME: { CheckConsistancy(); UpdateEndRelative(); break; } case IDC_REMINDER_TYPE: { LViewI *Label, *Param; if (GetViewById(IDC_PARAM_LABEL, Label) && GetViewById(IDC_REMINDER_PARAM, Param)) { Param->Name(NULL); switch (Ctrl->Value()) { case CalEmail: Label->Name("optional email:"); Param->Enabled(true); break; default: case CalPopup: Label->Name(NULL); Param->Enabled(false); break; case CalScriptCallback: Label->Name("callback fn:"); Param->Enabled(true); break; } } break; } case IDC_ALL_DAY: { int64 AllDay = Ctrl->Value(); LDataI *o = Item->GetObject(); char s[256] = ""; auto dt = o->GetDate(FIELD_CAL_START_UTC); if (dt) { LDateTime tmp = *dt; tmp.ToLocal(true); tmp.GetTime(s, sizeof(s)); SetCtrlName(IDC_START_TIME, AllDay ? "" : s); SetCtrlEnabled(IDC_START_TIME, !AllDay); } dt = o->GetDate(FIELD_CAL_END_UTC); if (dt) { LDateTime tmp = *dt; tmp.ToLocal(true); tmp.GetTime(s, sizeof(s)); SetCtrlName(IDC_END_TIME, AllDay ? "" : s); SetCtrlEnabled(IDC_END_TIME, !AllDay); } break; } case IDC_SUBJECT: { SetCtrlEnabled(IDC_SAVE, ValidStr(Ctrl->Name())); break; } case IDC_SHOW_LOCATION: { LString Loc = GetCtrlName(IDC_LOCATION); if (ValidStr(Loc)) { for (char *c = Loc; *c; c++) { if (Strchr(LWhiteSpace, *c)) *c = '+'; } LString Uri; Uri.Printf("http://maps.google.com/?q=%s", Loc.Get()); LExecute(Uri); } break; } case IDC_GUESTS: { if (n.Type == LNotifyItemInsert) { d->Guests->ResizeColumnsToContent(); } else if (n.Type == LNotifyDeleteKey) { List la; d->Guests->GetSelection(la); la.DeleteObjects(); } break; } case IDC_GUEST_ENTRY: { if (d->Entry) { if (ValidStr(d->Entry->Name())) { if (!d->Browse) { d->Browse = new AddressBrowse(Item->App, d->Entry, d->Guests, NULL); } } if (d->Browse) { d->Browse->OnNotify(d->Entry, n); } if (n.Type == LNotifyReturnKey) { d->OnGuest(); } } break; } case IDC_REPEAT: { if (!Ctrl->Value()) break; auto Dlg = new LRecurDlg(this); Dlg->DoModal([this, Dlg](auto dlg, auto ctrlId) { if (!ctrlId) SetCtrlValue(IDC_REPEAT, false); delete dlg; }); break; } case IDC_TIMEZONE: { auto Tz = Item->GetObject()->GetStr(FIELD_CAL_TIMEZONE); auto Dlg = new LInput(this, Tz, "Time zone:", "Calendar Event Timezone"); Dlg->DoModal([this, Dlg](auto dlg, auto Result) { if (Result) { Item->GetObject()->SetStr(FIELD_CAL_TIMEZONE, Dlg->GetStr()); Item->SetDirty(); } delete dlg; }); break; } case IDC_REMINDER_ADD: { const char *v = GetCtrlName(IDC_REMINDER_VALUE); const char *param = GetCtrlName(IDC_REMINDER_PARAM); d->Reminders->Insert ( new ReminderItem ( (CalendarReminderType) d->ReminderType->Value(), v ? (float)atof(v) : 1.0f, (CalendarReminderUnits) d->ReminderUnit->Value(), param ) ); d->Reminders->ResizeColumnsToContent(); break; } case IDC_ADD_GUEST: { d->OnGuest(); break; } case IDC_ADD_FILE: { if (!Item) { LAssert(!"No item"); break; } auto sel = new LFileSelect(this); sel->MultiSelect(true); sel->Open([this, sel](auto dlg, auto ok) { if (ok) { LList *attachLst = NULL; if (!GetViewById(IDC_ATTACHMENTS, attachLst)) { LAssert(!"No list"); } else { for (size_t i=0; iLength(); i++) { auto fn = (*sel)[i]; if (!fn) { LAssert(!"No filename"); continue; } auto data = Item->ImportAttachment(fn); if (!data) { LAssert(!"ImportAttachment failed"); break; } attachLst->Insert(new CalendarAttachmentItem(this, data)); } attachLst->ResizeColumnsToContent(); } } delete dlg; }); break; } case IDC_SAVE: { LViewI *v; if (GetViewById(IDC_GUEST_ENTRY, v) && v->Focus()) { d->OnGuest(); break; } else { OnSave(); // Fall through } } case IDCANCEL: { if (Item) { // Is the user canceling an object that hasn't been saved yet? // If so delete the object. auto obj = Item->GetObject(); if (obj && obj->IsOrphan()) { Item->DecRef(); Item = NULL; } } Quit(); break; } } return 0; } void CalendarUi::OnLoad() { THREAD_UNSAFE(); // Sanity check if (!Item) { LAssert(!"No item."); return; } LDataI *o = Item->GetObject(); if (!o) { LAssert(!"No object."); return; } // Copy object values into UI int64 AllDay = false; auto CalSubject = o->GetStr(FIELD_CAL_SUBJECT); SetCtrlName(IDC_SUBJECT, CalSubject); SetCtrlName(IDC_LOCATION, o->GetStr(FIELD_CAL_LOCATION)); SetCtrlName(IDC_DESCRIPTION, o->GetStr(FIELD_CAL_NOTES)); SetCtrlValue(IDC_ALL_DAY, AllDay = o->GetInt(FIELD_CAL_ALL_DAY)); if (d->Guests) { LJson j(o->GetStr(FIELD_ATTENDEE_JSON)); for (auto g: j.GetArray(LString())) { LAutoPtr la(new ListAddr(App)); if (la) { la->sAddr = g.Get("email"); la->sName = g.Get("name"); d->Guests->Insert(la.Release()); } } d->Guests->ResizeColumnsToContent(); } if (d->Reminders) { d->Reminders->Empty(); LString Rem = o->GetStr(FIELD_CAL_REMINDERS); auto a = Rem.SplitDelimit("\n"); for (unsigned i=0; iSetString(a[i])) d->Reminders->Insert(ri); else delete ri; } } d->Reminders->ResizeColumnsToContent(); } auto Show = (CalendarShowTimeAs)o->GetInt(FIELD_CAL_SHOW_TIME_AS); switch (Show) { case CalFree: SetCtrlValue(IDC_AVAILABLE_TYPE, 0); break; case CalTentative: SetCtrlValue(IDC_AVAILABLE_TYPE, 1); break; default: case CalBusy: SetCtrlValue(IDC_AVAILABLE_TYPE, 2); break; } auto Priv = (CalendarPrivacyType)o->GetInt(FIELD_CAL_PRIVACY); switch (Priv) { default: SetCtrlValue(IDC_PRIVACY_TYPE, 0); break; case CalPublic: SetCtrlValue(IDC_PRIVACY_TYPE, 1); break; case CalPrivate: SetCtrlValue(IDC_PRIVACY_TYPE, 2); break; } auto Col = o->GetInt(FIELD_COLOUR); if (Col >= 0) d->Colour->Value(Col); for (unsigned i=0; iGetName(); LString i_path; if (Item->GetFolder()) i_path = Item->GetFolder()->GetPath(); if (s_path && i_path && _stricmp(s_path, i_path) == 0) { SetCtrlValue(IDC_CALENDAR, i); break; } } auto dt = o->GetDate(FIELD_CAL_START_UTC); - if (dt) + if (dt && dt->IsValid()) { LOG_DEBUG("%s:%i - Load.Start.UTC=%s\n", _FL, dt->Get().Get()); LDateTime tmp = *dt; tmp.SetTimeZone(0, false); tmp.ToLocal(true); LOG_DEBUG("%s:%i - Load.Start.Local=%s\n", _FL, tmp.Get().Get()); SetCtrlName(IDC_START_DATE, tmp.GetDate()); SetCtrlName(IDC_START_TIME, AllDay ? "" : tmp.GetTime().Get()); SetCtrlEnabled(IDC_START_TIME, !AllDay); } dt = o->GetDate(FIELD_CAL_END_UTC); - if (dt) + if (dt && dt->IsValid()) { LOG_DEBUG("%s:%i - Load.End.UTC=%s\n", _FL, dt->Get().Get()); LDateTime tmp = *dt; tmp.SetTimeZone(0, false); tmp.ToLocal(true); LOG_DEBUG("%s:%i - Load.End.Local=%s\n", _FL, tmp.Get().Get()); SetCtrlName(IDC_END_DATE, tmp.GetDate()); SetCtrlName(IDC_END_TIME, AllDay ? "" : tmp.GetTime().Get()); SetCtrlEnabled(IDC_END_TIME, !AllDay); } SetCtrlValue(IDC_REPEAT, o->GetInt(FIELD_CAL_RECUR)); LViewI *ctrl; if (GetViewById(IDC_SUBJECT, ctrl)) { LNotification note(LNotifyValueChanged); OnNotify(ctrl, note); } if (ValidStr(CalSubject)) Name(LString::Fmt("%s - %s", LLoadString(IDS_CAL_EVENT), CalSubject)); // Load the list of attached files: LList *attachLst; if (GetViewById(IDC_ATTACHMENTS, attachLst)) { auto fileLst = Item->GetObject()->GetList(FIELD_CAL_ATTACHMENTS); if (fileLst) { for (auto i=fileLst->First(); i; i=fileLst->Next()) { auto data = dynamic_cast(i); if (data) attachLst->Insert(new CalendarAttachmentItem(this, data)); else LAssert(!"Wrong object!"); } // No point resizing the columns here, as the table hasn't has it's first layout // And will most likely be too small attachLst->ResizeColumnsToContent(); } } UpdateRelative(); } void CalendarUi::OnSave() { THREAD_UNSAFE(); // Sanity check if (!Item) { LAssert(!"No item."); return; } LDataI *o = Item->GetObject(); if (!o) { LAssert(!"No object."); return; } // Save UI values into object bool AllDay = false; o->SetStr(FIELD_CAL_SUBJECT, GetCtrlName(IDC_SUBJECT)); o->SetStr(FIELD_CAL_LOCATION, GetCtrlName(IDC_LOCATION)); o->SetStr(FIELD_CAL_NOTES, GetCtrlName(IDC_DESCRIPTION)); o->SetInt(FIELD_CAL_ALL_DAY, AllDay = (GetCtrlValue(IDC_ALL_DAY) != 0)); if (d->Guests) { List All; if (d->Guests->GetAll(All)) { LString::Array a; LString Sep(", "); for (auto la: All) { LString e; if (la->Serialize(e, true)) a.Add(e); } LString Guests = Sep.Join(a); o->SetStr(FIELD_TO, Guests); } } if (d->Reminders) { List All; if (d->Reminders->GetAll(All)) { LString::Array a; LString Sep("\n"); for (auto ri: All) { a.Add(ri->GetString()); } LString Reminders = Sep.Join(a); o->SetStr(FIELD_CAL_REMINDERS, Reminders); } else { o->SetStr(FIELD_CAL_REMINDERS, ""); } } int64 Show = GetCtrlValue(IDC_AVAILABLE_TYPE); switch (Show) { case 0: o->SetInt(FIELD_CAL_SHOW_TIME_AS, CalFree); break; case 1: o->SetInt(FIELD_CAL_SHOW_TIME_AS, CalTentative); break; default: case 2: o->SetInt(FIELD_CAL_SHOW_TIME_AS, CalBusy); break; } int64 Priv = GetCtrlValue(IDC_PRIVACY_TYPE); switch (Priv) { default: case 0: o->SetInt(FIELD_CAL_PRIVACY, CalDefaultPriv); break; case 1: o->SetInt(FIELD_CAL_PRIVACY, CalPublic); break; case 2: o->SetInt(FIELD_CAL_PRIVACY, CalPrivate); break; } auto Col = d->Colour->Value(); o->SetInt(FIELD_COLOUR, Col > 0 ? Col : -1); /* FIXME: change calendar item location if the user selects a different cal for (unsigned i=0; iSources.Length(); i++) { CalendarSource *src = d->Sources[i]; LAutoString s_path(src->GetPath()); LAutoString i_path; if (Item->GetFolder()) i_path = Item->GetFolder()->GetPath(); if (s_path && i_path && _stricmp(s_path, i_path) == 0) { SetCtrlValue(IDC_CALENDAR, i); break; } } */ LDateTime dt; - dt.SetDate(GetCtrlName(IDC_START_DATE)); - dt.SetTime(AllDay ? "0:0:0" : GetCtrlName(IDC_START_TIME)); - + if (dt.SetDate(GetCtrlName(IDC_START_DATE))) + { + dt.SetTime(AllDay ? "0:0:0" : GetCtrlName(IDC_START_TIME)); LOG_DEBUG("%s:%i - Start.Local=%s\n", _FL, dt.Get().Get()); - - dt.ToUtc(true); - + dt.ToUtc(true); LOG_DEBUG("%s:%i - Start.UTC=%s\n", _FL, dt.Get().Get()); - - o->SetDate(FIELD_CAL_START_UTC, &dt); + o->SetDate(FIELD_CAL_START_UTC, &dt); + } dt.Empty(); - dt.SetDate(GetCtrlName(IDC_END_DATE)); - dt.SetTime(AllDay ? "11:59:59" : GetCtrlName(IDC_END_TIME)); - + if (dt.SetDate(GetCtrlName(IDC_END_DATE))) + { + dt.SetTime(AllDay ? "11:59:59" : GetCtrlName(IDC_END_TIME)); LOG_DEBUG("%s:%i - End.Local=%s\n", _FL, dt.Get().Get()); - - dt.ToUtc(true); - + dt.ToUtc(true); LOG_DEBUG("%s:%i - End.UTC=%s\n", _FL, dt.Get().Get()); - - o->SetDate(FIELD_CAL_END_UTC, &dt); + o->SetDate(FIELD_CAL_END_UTC, &dt); + } o->SetInt(FIELD_CAL_RECUR, GetCtrlValue(IDC_REPEAT)); Item->Update(); Item->Save(); } ////////////////////////////////////////////////////////////// int LDateTimeViewBase = 1000; class LDateTimeView : public LLayout, public ResObject { LEdit *Edit; LDateDropDown *Date; LTimeDropDown *Time; public: LDateTimeView() : ResObject(Res_Custom) { AddView(Edit = new LEdit(10, 0, 0, 60, 20, NULL)); AddView(Date = new LDateDropDown()); AddView(Time = new LTimeDropDown()); Edit->SetId(LDateTimeViewBase++); Date->SetNotify(Edit); Date->SetId(LDateTimeViewBase++); Time->SetNotify(Edit); Time->SetId(LDateTimeViewBase++); } bool OnLayout(LViewLayoutInfo &Inf) { if (!Inf.Width.Max) { Inf.Width.Max = 220; Inf.Width.Min = 150; } else if (!Inf.Height.Max) { Inf.Height.Max = Inf.Height.Min = LSysFont->GetHeight() + 6; } else return false; return true; } void OnCreate() { AttachChildren(); } void OnPosChange() { LRect c = GetClient(); // int cy = c.Y(); // int py = Y(); LRect tc = c; tc.x1 = tc.x2 - 20; LRect dc = c; dc.x2 = tc.x1 - 1; dc.x1 = dc.x2 - 20; Date->SetPos(dc); Time->SetPos(tc); c.x2 = dc.x1 - 1; Edit->SetPos(c); } const char *Name() { return Edit->Name(); } bool Name(const char *s) { return Edit->Name(s); } }; class LDateTimeViewFactory : public LViewFactory { LView *NewView(const char *Class, LRect *Pos, const char *Text) { if (!_stricmp(Class, "LDateTimeView")) return new LDateTimeView; return NULL; } public: } DateTimeViewFactory; diff --git a/src/ScribeUtils.cpp b/src/ScribeUtils.cpp --- a/src/ScribeUtils.cpp +++ b/src/ScribeUtils.cpp @@ -1,2113 +1,2111 @@ #include "Scribe.h" #include "lgi/common/Http.h" #include "lgi/common/DocView.h" #include "lgi/common/Store3.h" #include "lgi/common/Button.h" #include "lgi/common/TableLayout.h" #include "lgi/common/TabView.h" #include "lgi/common/OpenSSLSocket.h" #include "lgi/common/LgiRes.h" #include "ScribeUtils.h" #include "ScribeDefs.h" #include "ScribeListAddr.h" #include "resdefs.h" #include "../src/common/Coding/ScriptingPriv.h" #define COMP_FUNCTIONS 1 #include "lgi/common/ZlibWrapper.h" static char Ws[] = " \t\r\n"; #include "chardet.h" LString DetectCharset(LString s) { DetectObj *obj = detect_obj_init (); if (!obj) return LString(); LString cs; if (detect_r(s.Get(), s.Length(), &obj) == CHARDET_SUCCESS && obj->confidence >= 0.75) cs = obj->encoding; #if 0 LgiTrace("%s:%i - encoding=%s, obj->confidence=%f, obj->bom=%i, str='%s'\n", _FL, obj->encoding, obj->confidence, obj->bom, s.Get()); #endif detect_obj_free (&obj); return cs; } const char *ScribeResourcePath() { static char Res[MAX_PATH_LEN] = {0}; if (!Res[0]) { #if defined(LINUX) // Check for AppImage location LFile::Path app(LSP_APP_INSTALL); app += "../../usr/share/applications"; if (app.Exists()) { strcpy_s(Res, sizeof(Res), app.GetFull()); LgiTrace("%s:%i - Res: %s.\n", _FL, Res); return Res; } // else LgiTrace("%s:%i - Warning: app image resource '%s' doesn't exist.\n", _FL, app.GetFull().Get()); // else fall through to portable mode #elif defined(MAC) // Find resource folder in app bundle LMakePath(Res, sizeof(Res), LGetExeFile(), "Contents/Resources"); return Res; #endif #if !defined(MAC) const char *Paths[] = { "./resources", "../resources", "../../resources", }; bool Found = false; for (unsigned i=0; iGet(d, sizeof(d)); p.Push(d); break; } } } void PushArrayContent(LStringPipe &p, char *&s, LDom *Source) { // Process inside of array index while (s && *s) { // Skin ws while (*s && strchr(Ws, *s)) s++; // Is end of array brackets? if (*s == ']') { break; } else if (*s == '\'' || *s == '\"') { // String const char Delim = *s++; char *e = strchr(s, Delim); if (e) { p.Push(s, e-s); s = e + 1; } else { s += strlen(s); break; } } else { // Variable LStringPipe Var; char *e = s; int Depth = 0; while (*e && !strchr(Ws, *e)) { if (*e == '[') { e++; Var.Push(s, e-s); PushArrayContent(Var, e, Source); s = e; Depth++; } else if (*e == ']') { if (Depth > 0 || e[1] == '.') { // Continue the variable if (Depth) Depth--; e++; } else { // End the var break; } } else { e++; } } Var.Push(s, e-s); char *Tok = Var.NewStr(); if (Tok) { LVariant v; if (Source->GetValue(Tok, v)) { PushVariant(p, v); } else { p.Push(Tok); } DeleteArray(Tok); } s = e; } } } char *ScribeInsertFields(const char *Template, LDom *Source) { if (Template && Source) { LStringPipe p; char *n; for (const char *s=Template; s && *s; s = n) { n = strstr((char*)s, "') || strchr(Ws, *e) ) { break; } e++; } if (*e == '[') { e++; Var.Push(s, e-s); // Process inside of array index PushArrayContent(Var, e, Source); } else if (strchr(Ws, *e)) { // Skip whitespace Var.Push(s, e-s); while (*e && strchr(Ws, *e)) { e++; } } else if (e[0] == '?' && e[1] == '>') { // End of var Var.Push(s, e-s); GotEnd = true; n = e; break; } else { // error n = e;; break; } s = e; } if (GotEnd) { char *Name = Var.NewStr(); if (Name) { char *i = Name, *o = Name; while (*i) { if (*i == '=') { i++; char h[] = {i[0], i[1], 0}; *o++ = htoi(h); i += 2; } else { *o++ = *i++; } } *o++ = 0; LVariant v; if (Source->GetValue(Name, v)) { switch (v.Type) { default: break; case GV_STRING: { p.Push(v.Str()); break; } case GV_INT32: { char i[32]; sprintf_s(i, sizeof(i), "%i", v.Value.Int); p.Push(i); break; } case GV_DOUBLE: { char d[32]; sprintf_s(d, sizeof(d), "%f", v.Value.Dbl); p.Push(d); break; } case GV_DATETIME: { char d[64]; v.Value.Date->Get(d, sizeof(d)); p.Push(d); break; } } } DeleteArray(Name); } n += 2; } } else { p.Push(s); break; } } return p.NewStr(); } return 0; } ////////////////////////////////////////////////////////////////////////////////////// HttpImageThread::HttpImageThread(ScribeWnd *app, const char *proxy, LThreadTarget *First) : LThreadWorker(First, "HtmlImageLoader") { App = app; Proxy = proxy; Cache = ScribeTempPath(); if (!LDirExists(Cache)) FileDev->CreateFolder(Cache); } HttpImageThread::~HttpImageThread() { } void HttpImageThread::DoJob(LThreadJob *j) { LDocumentEnv::LoadJob *Job = dynamic_cast(j); if (!Job) return; char *d = strrchr(Job->Uri, '/'); if (!d) { Job->Status = LDocumentEnv::LoadJob::JobErr_Uri; Job->Error.Printf("No '/' in uri '%s'", Job->Uri.Get()); return; } LString CachedFile = UriMap.Find(Job->Uri); if (!CachedFile) { char *Ext = LGetExtension(++d); auto Qm = Ext ? strchr(Ext, '?') : NULL; auto Len = Qm ? Qm - Ext : Strlen(Ext); char p[MAX_PATH_LEN]; for (int i=0; i<1000; i++) { - char Hash[256]; - sprintf_s(Hash, sizeof(Hash), "%x_%i.%.*s", LHash((uchar*)d + 1, -1, true), i++, (int)Len, Ext); - + auto Hash = LString::Fmt("%x_%i.%.*s", LHash((uchar*)d + 1, -1, true), i++, (int)Len, Ext); if (!LMakePath(p, sizeof(p), Cache, Hash)) { Job->Status = LDocumentEnv::LoadJob::JobErr_Path; Job->Error.Printf("MakePath failed: '%s' + '%s'", Cache.Get(), Hash); return; } if (!LFileExists(p)) break; } UriMap.Add(Job->Uri, CachedFile = p); } if (!LFileExists(CachedFile)) { const char *InHeaders = "User-Agent: Memecode Scribe\r\n" "Accept: text/html,application/xhtml+xml,application/xml,image/png,image/*;q=0.9,*/*;q=0.8\r\n" "Accept-Encoding: gzip, deflate\r\n"; LFile f; if (f.Open(CachedFile, O_READWRITE)) { LUri Prox(Proxy); bool r = LgiGetUri(this, &f, &Job->Error, Job->Uri, InHeaders, Proxy ? &Prox : NULL); f.Close(); if (!r) { Job->Status = LDocumentEnv::LoadJob::JobErr_GetUri; FileDev->Delete(CachedFile, NULL, false); } } else { Job->Status = LDocumentEnv::LoadJob::JobErr_FileOpen; } } if (LFileExists(CachedFile)) { LString::Array Mime = LGetFileMimeType(CachedFile).Split("/"); if (Mime[0].Equals("image")) { int Promote = GdcD->SetOption(GDC_PROMOTE_ON_LOAD, 0); Job->pDC.Reset(GdcD->Load(CachedFile)); GdcD->SetOption(GDC_PROMOTE_ON_LOAD, Promote); if (Job->pDC) { Job->Status = LDocumentEnv::LoadJob::JobOk; } else { char *d = strrchr(CachedFile, DIR_CHAR); Job->Error.Printf("%s:%i - LoadDC(%s) failed [%s].", _FL, d?d+1:CachedFile.Get(), Job->Uri.Get()); FileDev->Delete(CachedFile, NULL, false); Job->Status = LDocumentEnv::LoadJob::JobErr_ImageFilter; } } else { // Css??? LFile *f = new LFile; if (f) { if (f->Open(CachedFile, O_READ)) { Job->Stream.Reset(f); Job->Status = LDocumentEnv::LoadJob::JobOk; } else { Job->Status = LDocumentEnv::LoadJob::JobErr_FileOpen; Job->Error.Printf("%s:%i - Cant read from '%s' (err=%i).", _FL, CachedFile.Get(), f->GetError()); delete f; } } else Job->Status = LDocumentEnv::LoadJob::JobErr_NoMem; } } else if (!Job->Error) { Job->Status = LDocumentEnv::LoadJob::JobErr_NoCachedFile; Job->Error = "No file in cache"; } if (Job->Error) { LgiTrace("Image load failed: %s\n", Job->Error.Get()); } } char *ScribeTempPath() { static char Tmp[MAX_PATH_LEN] = ""; if (Tmp[0] == 0) { if (LGetSystemPath(LSP_TEMP, Tmp, sizeof(Tmp))) { LMakePath(Tmp, sizeof(Tmp), Tmp, "Scribe"); } else { LgiTrace("%s:%i - LgiGetSystemPath(LSP_TEMP) failed.\n", _FL); return NULL; } } if (!LDirExists(Tmp)) { LError Err; if (!FileDev->CreateFolder(Tmp, true, &Err)) { LgiTrace("%s:%i - CreateFolder(%s) failed with %i\n", _FL, Tmp, Err.GetCode()); return NULL; } } return Tmp; } void ClearTempPath() { char *Tmp = ScribeTempPath(); if (Tmp) { if (!LDirExists(Tmp)) FileDev->CreateFolder(Tmp); LDirectory d; for (int b = d.First(Tmp); b; b = d.Next()) { if (!d.IsDir()) { char p[256]; d.Path(p, sizeof(p)); FileDev->Delete(p, NULL, false); } } } } //////////////////////////////////////////////////////////////////////////////////////////////// #define BufferLen_64ToBin(l) ( ((l)*3)/4 ) #define BufferLen_BinTo64(l) ( ((((l)+2)/3)*4) ) int DecodeUuencodedChar(const char *&s) { int Status = -1; if (*s == 0x60) { Status = 0; s++; } else if (*s >= (' ' + 64) || *s < ' ') { printf("%s:%i - Invalid uuencode char: %c (%i)\n", _FL, *s, (uchar)*s); } else { Status = *s - ' '; s++; } return Status; } bool DecodeUuencodedLine(LStreamI *Out, const char *Text, ssize_t Len) { bool Status = false; if (Text && Len > 1) { uchar *Buf = new uchar[Len]; if (Buf) { const char *End = Text + Len; const char *c = Text; uchar *d = Buf; int Count = DecodeUuencodedChar(c); int Processed = 0; if (Count < 0) return false; while (c < End && *c) { int t[4]; // De-text t[0] = DecodeUuencodedChar(c); if (t[0] < 0) break; t[1] = DecodeUuencodedChar(c); if (t[1] < 0) break; t[2] = DecodeUuencodedChar(c); if (t[2] < 0) break; t[3] = DecodeUuencodedChar(c); if (t[3] < 0) break; // Convert to binary uchar b[3] = { (uchar) ((t[0] << 2) | ((t[1] & 0x30) >> 4)), (uchar) (((t[1] & 0xF) << 4) | ((t[2] & 0x3C) >> 2)), (uchar) (((t[2] & 0x3) << 6) | (t[3])) }; // Push onto the output stream switch (Count - Processed) { case 1: { *d++ = b[0]; Processed++; break; } case 2: { *d++ = b[0]; *d++ = b[1]; Processed += 2; break; } default: { if (Count - Processed >= 3) { *d++ = b[0]; *d++ = b[1]; *d++ = b[2]; Processed += 3; } break; } } } if (Processed != Count) { printf("%s:%i - uuencode line error, processed %i of %i\n", _FL, Processed, Count); } Status = Out->Write(Buf, d-Buf) > 0; DeleteArray(Buf); } } return Status; } bool DecodeUuencodedAttachment(LDataStoreI *Store, LArray &Files, LStreamI *Out, const char *In) { if (Store && In) { // const char Ws[] = " \t\r\n"; LStringPipe FileName; LAutoPtr FileData; const char *e; const char *Last = In; int Line = 1; for (const char *s = In; s && *s; s = *e?e+1:e) { // Find the end of the line... e = s; while (*e && *e != '\n') e++; if (FileData) { if (_strnicmp(s, "end", 3) == 0) { // Write attachment LDataI *Attachment = Store->Create(MAGIC_ATTACHMENT); if (Attachment) { LAutoString Name(FileName.NewStr()); if (Name) { char *e = Name + strlen(Name); while (e > Name.Get() && strchr(" \t\r\n", e[-1])) *--e = 0; Attachment->SetStr(FIELD_NAME, Name); } LAutoStreamI fd(FileData.Release()); Attachment->SetStream(fd); Files.Add(Attachment); } FileData.Reset(); } else if (!DecodeUuencodedLine(FileData, s, e - s)) { /* printf("%s:%i - DecodeUuencodedLine failed on line %i:\n\t%s\n", _FL, Line, s); */ } } // Is it the start of a file else if (_strnicmp(s, "begin ", 6) == 0) { if (Last) { Out->Write(Last, s - Last); Last = 0; } auto Header = LString(s, e - s).SplitDelimit(" ", -1, true); if (Header.Length() >= 3) { LMemQueue File; for (int n=2; Header[n]; n++) { FileName.Print("%s%s", n==2?"":" ", Header[n].Get()); } } FileData.Reset(new LStringPipe(256)); } else if (!Last) { Last = s; } Line++; } if (Files.Length() && Last) { Out->Write(Last, strlen(Last)); } } return Files.Length() > 0; } char *MakeFileName(const char *ContentUtf, const char *Ext) { if (!ContentUtf) { LAssert(!"Invalid parameter."); return 0; } char *Content = 0; if (LIsUtf8(ContentUtf)) { // Valid UTF-8 Content = NewStr(ContentUtf); } else { // Garbage, so just ignore the input data. char n[256]; sprintf_s(n, sizeof(n), "_%i", LRand(1000000)); Content = NewStr(n); } if (!Content) { LAssert(!"No content to make filename from."); return 0; } char File[MAX_PATH_LEN]; char *e = Content; for (int i=0; i<64 && *e; i++) { char *before = e; e = LSeekUtf8(e, 1); if (e == before) { LAssert(!"LSeekUtf8 failed to more pointer forward."); break; } } *e = 0; if (strlen(Content) > 0) { if (Ext) sprintf_s(File, sizeof(File), "%s.%s", Content, Ext); else sprintf_s(File, sizeof(File), "%s", Content); } else { LAssert(!"No content for file name?"); strcpy_s(File, sizeof(File), "file"); } // Strip out invalid characters... char *Out = File; for (char *In = File; *In; In++) { if (!strchr("\\/?*:\"<>|\r\n", *In)) { *Out++ = *In; } } *Out++ = 0; LAssert(strlen(File) > 0); char Temp[MAX_PATH_LEN]; LMakePath(Temp, sizeof(Temp), ScribeTempPath(), File); if (LFileExists(Temp)) { char *Dot = strrchr(Temp, '.'); for (int i=2; LFileExists(Temp); i++) { ssize_t Len = Dot - Temp; sprintf_s(Dot, sizeof(Temp)-Len, "%i.%s", i, Ext); } } DeleteArray(Content); return NewStr(Temp); } /////////////////////////////////////////////////////////////////////////////////////////// Store3Progress::Store3Progress(LView *parent, bool interact) : LProgressDlg(parent) { Interact = interact; NewFormat = -1; } const char *Store3Progress::GetStr(int id) { switch (id) { case Store3UiError: return Err; case Store3UiStatus: return (Cache = ItemAt(0)->GetDescription()); } LAssert(0); return 0; } Store3Status Store3Progress::SetStr(int id, const char *str) { switch (id) { case Store3UiError: Err = str; // Fall through case Store3UiStatus: ItemAt(0)->SetDescription(str); return Store3Success; } LAssert(0); return Store3Error; } int64 Store3Progress::GetInt(int id) { switch (id) { case Store3UiCurrentPos: return ItemAt(0)->Value(); case Store3UiInteractive: return Interact; case Store3UiCancel: return IsCancelled(); case Store3UiNewFormat: return NewFormat; } LAssert(0); return -1; } Store3Status Store3Progress::SetInt(int id, int64 i) { switch (id) { case Store3UiCancel: return Store3Error; case Store3UiCurrentPos: ItemAt(0)->Value(i); break; case Store3UiMaxPos: ItemAt(0)->SetRange(i); break; case Store3UiNewFormat: NewFormat = (int)i; break; default: LAssert(0); return Store3Error; } return Store3Success; } /////////////////////////////////////////////////////////////////////// class BufferedTrace { List Traces; public: ~BufferedTrace() { for (auto s: Traces) { LgiTrace(s); DeleteArray(s); } } void Trace(char *s) { if (s) { Traces.Insert(NewStr(s)); } } } ; static BufferedTrace Bt; void TraceTime(char *s) { static int64 Last = 0; if (s) { int64 Now = LCurrentTime(); int64 Diff = 0; if (Last) { Diff = Now - Last; } else { Diff = 0; } Last = Now; char m[256]; sprintf_s(m, sizeof(m), "%s (+%i)", s, (int)Diff); Bt.Trace(m); } else { Last = 0; } } ///////////////////////////////////////////////////////////////////////////// Counter::~Counter() { for (auto c: *this) { DeleteObj(c); } } CountItem *Counter::FindType(int Type) { for (auto c: *this) { if (Type == c->Type) { return c; } } CountItem *c = new CountItem; if (c) { c->Type = Type; Insert(c); } return c; } void Counter::Inc(int Type) { CountItem *c = FindType(Type); if (c) { c->Count++; } } void Counter::Dec(int Type) { CountItem *c = FindType(Type); if (c) { c->Count--; } } void Counter::Add(int Type, int64 n) { CountItem *c = FindType(Type); if (c) { c->Count += n; } } void Counter::Sub(int Type, int64 n) { CountItem *c = FindType(Type); if (c) { c->Count -= n; } } int64 Counter::GetTypeCount(int Type) { CountItem *c = FindType(Type); if (c) { return c->Count; } return 0; } ////////////////////////////////////////////////////////// ItemFieldDef *ScribeGetFieldDefs(int Type) { switch ((uint32_t)Type) { case MAGIC_MAIL: { return MailFieldDefs; } case MAGIC_CONTACT: { return ContactFieldDefs; } case MAGIC_CALENDAR: { return CalendarFields; } } return 0; } Contact *IsContact(LListItem *Item) { return dynamic_cast(Item); } Mail *IsMail(LListItem *Item) { return dynamic_cast(Item); } ////////////////////////////////////////////////////////////////////////////// char sMimeVCard[] = "text/x-vcard"; char sMimeVCalendar[] = "text/calendar"; char sMimeICalendar[] = "application/ics"; char sMimeMbox[] = "text/mbox"; char sMimeLgiResource[] = "application/x-lgi-resource"; char sMimeMessage[] = "message/rfc822"; char sMimeXml[] = "text/xml"; LString ScribeGetFileMimeType(const char *File) { LString Ret; if (!File) return Ret; auto Ext = LGetExtension(File); if (Ext) { if (_stricmp(Ext, "lr8") == 0) { Ret = sMimeLgiResource; } else if (_stricmp(Ext, "ici") == 0) { Ret = "application/x-ici"; } else if (_stricmp(Ext, "vcf") == 0) { Ret = sMimeVCard; } else if (_stricmp(Ext, "vcs") == 0 || _stricmp(Ext, "ics") == 0) { Ret = sMimeVCalendar; } else if (_stricmp(Ext, "eml") == 0) { Ret = sMimeMessage; } #if defined WIN32 // Hard code extensions (because windows doesn't get it right) else if (_stricmp(Ext, "mbx") == 0 || _stricmp(Ext, "mbox") == 0) { Ret = sMimeMbox; } #endif } if (!Ret) { // Do normal lookup Ret = LGetFileMimeType(File); } return Ret; } ///////////////////////////////////////////////////////////////////// Mailto::Mailto(ScribeWnd *app, const char *s) { App = app; Subject = NULL; Body = NULL; if (!s) return; // Do some detection of what type of string this is... // // Could be in various formats: // 1. user@isp.com // 2. user@isp.com, user2@isp.com, user3@isp.com // 3. "First Last" // 4. "First Last" , "First2 Last2" // 5. mailto:user@isp.com // 6. mailto:user@isp.com?subject=xxxxxx&body=xxxxxxxx // Skip whitespace while (*s && strchr(" \t\r\n", *s)) s++; // Check for mailto prefix if (_strnicmp(s, "mailto:", 7) == 0) { // Parse mailto URI char *e = NewStr(s + 7); char *In, *Out = e; for (In = e; *In; ) { if (In[0] == '%' && In[1] && In[2]) { char h[3] = { In[1], In[2], 0 }; *Out++ = htoi(h); In += 3; } else { *Out++ = *In++; } } *Out++ = 0; // Process mailto syntax char *Question = strchr(e, '?'); if (Question) { *Question++ = 0; // Split all the headers up auto Headers = LString(Question).SplitDelimit("&"); for (unsigned h=0; hsAddr = e; To.Insert(la); } } DeleteArray(e); } else { // Not a mailto, apply normal email recipient parsing char White[] = " \t\r\n"; #define SkipWhite(s) while (*s && strchr(White, *s)) s++; const char *Addr = s; for (const char *c = s; true;) { SkipWhite(c); if (*c == '\'' || *c == '\"') { char Delim = *c++; char *e = strchr((char*)c, Delim); if (e) c = e + 1; else c += strlen(c); } else if (*c == '<') { char *e = strchr((char*)c, '>'); if (e) c = e + 1; else c++; } else if (*c == ',' || *c == 0) { char *a = NewStr(Addr, c - Addr); if (a) { LAutoString Name, Addr; DecodeAddrName(a, Name, Addr, 0); if (Name || Addr) { ListAddr *la = new ListAddr(App); if (la) { if (Name && Addr) { la->sName = Name.Get(); la->sAddr = Addr.Get(); } else { la->sAddr = Name ? Name.Get() : Addr.Get(); } To.Insert(la); } } DeleteArray(a); } if (!*c) break; else { c++; SkipWhite(c); Addr = c; } } else c++; } } } Mailto::~Mailto() { To.DeleteObjects(); DeleteArray(Subject); DeleteArray(Body); } void Mailto::Apply(Mail *m) { if (m) { bool Dirty = false; if (Subject) { m->SetSubject(Subject); Dirty = true; } if (Body) { LVariant HtmlEdit; m->App->GetOptions()->GetValue(OPT_EditControl, HtmlEdit); auto Email = m->GetFromStr(FIELD_EMAIL); ScribeAccount *Acc = m->App->GetAccountByEmail(Email); if (!Acc) Acc = m->App->GetCurrentAccount(); LVariant Sig; LString Content; if (Acc) { if (HtmlEdit.CastInt32()) { Sig = Acc->Identity.HtmlSig(); if (Sig.Str()) { char *s = Sig.Str(); char *e = stristr(s, ""); if (e) Content.Printf("%.*s\n%s\n%s", e - s, s, Body, e + 6); else Content.Printf("%s\n%s", Body, s); } else Content = Body; } else { Sig = Acc->Identity.TextSig(); Content.Printf("%s\n%s", Body, Sig.Str()); } } if (HtmlEdit.CastInt32()) m->SetHtml(Content); else m->SetBody(Content); Dirty = true; } for (auto t: To) { LDataIt To = m->GetObject()->GetList(FIELD_TO); if (To) { LDataPropI *Addr = To->Create(m->GetObject()->GetStore()); if (Addr) { LDataPropI *p = dynamic_cast(t); if (p) { Addr->CopyProps(*p); To->Insert(Addr); } else { LAssert(!"Not the right object."); DeleteObj(Addr); } } if (m->GetUI()) { m->GetUI()->AddRecipient(new ListAddr(App, t)); } Dirty = true; } } if (Dirty) m->SetDirty(); } } ScribeDom::ScribeDom(ScribeWnd *a) { App = a; Email = NULL; Con = NULL; Cal = NULL; Fil = NULL; Grp = NULL; } bool ScribeDom::GetVariant(const char *Name, LVariant &Value, const char *Array) { ScribeDomType Fld = StrToDom(Name); switch (Fld) { case SdScribe: // Type: ScribeWnd { Value = (LDom*)App; break; } case SdMail: // Type: Mail { Value = Email; break; } case SdContact: // Type: Contact { Value = Con; break; } case SdContactGroup: // Type: ContactGroup { Value = Grp; break; } case SdCalendar: // Type: Calendar { Value = Cal; break; } case SdFilter: // Type: Filter { Value = Fil; break; } case SdNow: // Type: String { char n[256]; LDateTime Now; Now.SetNow(); Now.Get(n, sizeof(n)); Value = n; break; } default: { return false; } } return true; } ////////////////////////////////////////////////////////////////////////////////////// #include "lgi/common/Html.h" #include "lgi/common/Button.h" class HtmlMsg : public LDialog, public LDefaultDocumentEnv { LTableLayout *Tbl = NULL; Html1::LHtml *Html2 = NULL; public: HtmlMsg(LViewI *Parent, const char *Html, const char *Title, int Type) { LPoint Size(300, 300); SetParent(Parent); Name(Title?Title:"Message"); AddView(Tbl = new LTableLayout(2222)); auto c = Tbl->GetCell(0, 0); if (c->Add(Html2 = new Html1::LHtml(100, 0, 0, (int)(GdcD->X() * 0.5), (int)(GdcD->Y() * 0.75), this))) { Html2->SetCharset("utf-8"); Html2->Name(Html); /* Size = Html2->Layout(); LRect r(0, 0, Size.x, Size.y); Html2->SetPos(r); */ } LArray Btns; switch (Type & 0xf) { case MB_OK: Btns.Add(new LButton(IDOK, 0, 0, -1, -1, "Ok")); break; case MB_OKCANCEL: Btns.Add(new LButton(IDOK, 0, 0, -1, -1, "Ok")); Btns.Add(new LButton(IDCANCEL, 0, 0, -1, -1, "Cancel")); break; case MB_YESNO: Btns.Add(new LButton(IDYES, 0, 0, -1, -1, "Yes")); Btns.Add(new LButton(IDNO, 0, 0, -1, -1, "No")); break; case MB_YESNOCANCEL: Btns.Add(new LButton(IDYES, 0, 0, -1, -1, "Yes")); Btns.Add(new LButton(IDNO, 0, 0, -1, -1, "No")); Btns.Add(new LButton(IDCANCEL, 0, 0, -1, -1, "Cancel")); break; } c = Tbl->GetCell(0, 1); c->TextAlign(LCss::AlignCenter); for (auto b: Btns) c->Add(b); LRect r(0, 0, Size.x + 20 + LAppInst->GetMetric(LGI_MET_DECOR_X), Size.y + 20 + LSysFont->GetHeight() + LAppInst->GetMetric(LGI_MET_DECOR_CAPTION) + LAppInst->GetMetric(LGI_MET_DECOR_Y)); SetPos(r); MoveSameScreen(Parent); } void OnPosChange() { if (Tbl) Tbl->SetPos(GetClient()); } int OnNotify(LViewI *Ctrl, LNotification n) { switch (Ctrl->GetId()) { case IDOK: case IDCANCEL: case IDYES: case IDNO: EndModal(Ctrl->GetId()); break; } return LDialog::OnNotify(Ctrl, n); } }; void LHtmlMsg(std::function Callback, LViewI *Parent, const char *Html, const char *Title, int Type, ...) { va_list Arg; va_start(Arg, Type); #undef vsnprintf int length = vsnprintf(NULL, 0, Html, Arg); LAutoString Msg(new char[++length]); vsprintf_s(Msg, length, Html, Arg); va_end(Arg); auto Dlg = new HtmlMsg(Parent, Msg, Title, Type); Dlg->DoModal([Callback](auto dlg, auto id) { if (Callback) Callback(id); delete dlg; }); } ///////////////////////////////////////////////////////////////////////////// void TabDialog::OnCreate() { LTabView *Tab; if (GetViewById(TabCtrlId, Tab)) { Tab->SetPourChildren(true); LRect r(0, 0, 100, 100); Tab->SetPos(r); } OnPosChange(); } void TabDialog::IdealSize(LButton *b) { LViewLayoutInfo Inf; if (b->OnLayout(Inf)) { b->OnLayout(Inf); } else if (b->GetWindow()) { auto s = b->GetWindow()->GetDpiScale(); LDisplayString ds(b->GetFont(), b->Name()); Inf.Width.Max = (int32)(ds.X() + (s.x * LButton::Overhead.x)); Inf.Height.Max = (int32)(ds.Y() + (s.y * LButton::Overhead.y)); } else { LAssert(!"No way to set ideal size."); return; } LRect p = b->GetPos(); p.SetSize(Inf.Width.Max, Inf.Height.Max); b->SetPos(p); } void TabDialog::OnPosChange() { LButton *Ok = 0, *Cancel = 0, *Help = 0; LViewI *Tab = 0; if (GetViewById(TabCtrlId, Tab) && GetViewById(IDOK, Ok) && GetViewById(IDCANCEL, Cancel)) { GetViewById(HelpBtnId, Help); LRect r = GetClient(); r.Inset(LTableLayout::CellSpacing, LTableLayout::CellSpacing); IdealSize(Ok); IdealSize(Cancel); LRect t = r; t.y2 -= LTableLayout::CellSpacing + Ok->Y(); Tab->SetPos(t); if (Help) { IdealSize(Help); LRect h = Help->GetPos(); h.Offset(r.x1 - h.x1, r.y2 - h.Y() + 1 - h.y1); Help->SetPos(h); } LRect c = Cancel->GetPos(); c.Offset(r.x2 - c.X() + 1 - c.x1, r.y2 - c.Y() + 1 - c.y1); Cancel->SetPos(c); LRect o = Ok->GetPos(); o.Offset(c.x1 - LTableLayout::CellSpacing - o.X() + 1 - o.x1, r.y2 - o.Y() + 1 - o.y1); Ok->SetPos(o); } } LAutoString ConvertThreadIndex(char *ThreadIndex, int TruncateChars) { LAutoString a; if (ThreadIndex) { uchar InBuf[256]; ssize_t In = ConvertBase64ToBinary(InBuf, sizeof(InBuf), ThreadIndex, strlen(ThreadIndex)); LAssert(In >= 22); LStringPipe OutBuf(256); for (int i=0; i, ScribeDomType> Scribe_StrToDom(0, SdNone); static LHashTbl, const char *> Scribe_DomToStr; void InitStrToDom() { if (Scribe_StrToDom.Length() == 0) { // If this asserts it's likely you have a duplicate value in DomTypeValues.h #undef _ #define _(name) LAssert(Scribe_StrToDom.Find(#name) == SdNone); \ Scribe_StrToDom.Add(#name, Sd##name); \ LAssert(Scribe_StrToDom.Find(#name) == Sd##name); \ \ LAssert(Scribe_DomToStr.Find(Sd##name) == NULL); \ Scribe_DomToStr.Add(Sd##name, #name); \ LAssert(Scribe_DomToStr.Find(Sd##name) != NULL); #include "DomTypeValues.h" #undef _ } } void FreeStrToDom() { Scribe_StrToDom.Empty(true); Scribe_DomToStr.Empty(true); } ScribeDomType StrToDom(const char *s) { ScribeDomType d = Scribe_StrToDom.Find(s); return d; } const char *DomToStr(ScribeDomType d) { const char *s = Scribe_DomToStr.Find(d); return s; } void PatternBox(LSurface *pDC, const LRect &r) { int All = r.X() + r.Y() - 1; int MinEdge = MIN(r.X(), r.Y()); bool Wider = r.X() > r.Y(); for (int i=0; i> 2) % 2; /* if (i <= 4) LgiTrace("pt=%i,%i draw=%i\n", pt.x, pt.y, Draw); */ if (!Draw) continue; if (i < MinEdge) { pDC->Line(r.x1, r.y1 + i, r.x1 + i, r.y1); } else if (Wider) { if (i < r.X()) { int yy = r.Y() - 1; pDC->Line(pt.x, pt.y, pt.x - yy, pt.y + yy); } else { int yy = r.Y() - (i - r.X() + 1) - 1; pDC->Line(r.x2, r.y2-yy, r.x2-yy, r.y2); } } else // Tall { if (i < r.Y()) { int xx = r.X() - 1; pDC->Line(r.x1, r.y1 + i, r.x1 + xx, r.y1 + i - xx); } else { int xx = r.X() - (i - r.Y() + 1) - 1; pDC->Line(r.x2-xx, r.y2, r.x2, r.y2-xx); } } } } /////////////////////////////////////////////////////////////////////////////////////// ContactGroup *LookupContactGroup(ScribeWnd *App, const char *Name) { auto Srcs = App->GetThingSources(MAGIC_GROUP); if (!Srcs.Length() || !Name) return NULL; for (auto s: Srcs) { s->LoadThings(); for (auto t: s->Items) { ContactGroup *g = t->IsGroup(); if (!g) continue; LVariant Nm; if (g->GetVariant("Name", Nm) && Nm.Str() && _stricmp(Nm.Str(), Name) == 0) { return g; } } } return NULL; } ////////////////////////////////////////////////////////////////////////////////////////////////// LOAuth2::Params GetOAuth2Params(const char *Host, Store3ItemTypes Context) { LOAuth2::Params p; // FYI: None of this works due to issues at the providers end. It did sometime in the // past. And is only here in case someone wants to try and get it working again. if (stristr(Host, "google.") || stristr(Host, "gmail.")) { if (Context == MAGIC_MAIL) { p.AuthUri = "https://accounts.google.com/o/oauth2/auth"; p.ApiUri = "https://www.googleapis.com/oauth2/v3/token"; #if 1 // Old scope: p.Scope = "https://mail.google.com/"; #else // New scope: (doesn't work) p.Scope = "https://www.googleapis.com/auth/gmail.modify"; #endif // p.RevokeUri = "https://accounts.google.com/o/oauth2/revoke"; } /* else if (Context == MAGIC_CALENDAR) { p.AuthUri = "https://accounts.google.com/o/oauth2/v2/auth"; p.ApiUri = "https://apidata.googleusercontent.com/caldav/v2/%s/user"; p.Scope = "https://www.googleapis.com/auth/calendar"; } */ else return p; p.Provider = LOAuth2::Params::OAuthGoogle; p.ClientID = ""; p.ClientSecret = ""; p.RedirURIs = "urn:ietf:wg:oauth:2.0:oob\nhttp://localhost"; } else if (stristr(Host, "outlook.") && !stristr(Host, "office365.")) { if (Context == MAGIC_MAIL) { p.RedirURIs = "urn:ietf:wg:oauth:2.0:oob\nhttp://localhost"; p.AuthUri = "https://login.microsoftonline.com/common/oauth2/v2.0/authorize"; p.ApiUri = "https://login.microsoftonline.com/common/oauth2/v2.0/token"; } else return p; p.Provider = LOAuth2::Params::OAuthMicrosoft; p.ClientID = ""; p.ClientSecret = ""; p.Scope = "https://outlook.office.com/mail.readwrite%20https://outlook.office.com/mail.send"; } return p; } ////////////////////////////////////////////////////////////////////////////////////////////////// class ScribeHtmLParser : public LHtmlParser { LScriptEngine Eng; public: struct HtmlElem : public LHtmlElement { LHashTbl, LString> Attr; HtmlElem(LHtmlElement *e) : LHtmlElement(e) { } bool GetVariant(const char *Name, LVariant &Value, const char *Array = NULL) { if (!Stricmp(Name, "element")) { Value = Tag.Get(); return true; } else if (!Stricmp(Name, "content")) { Value.OwnStr(WideToUtf8(Txt.Get())); return true; } else if (!Stricmp(Name, "attr")) { if (Array) { char *s = Attr.Find(Array); if (s) { Value = s; return true; } } } return false; } bool Get(const char *attr, const char *&val) { auto s = Attr.Find(attr); if (!s) return false; val = s.Get(); return true; } void Set(const char *attr, const char *val) { Attr.Add(attr, val); } }; ScribeHtmLParser() : LHtmlParser(NULL), Eng(NULL, NULL, NULL) { } LHtmlElement *CreateElement(LHtmlElement *Parent) { return new HtmlElem(Parent); } void Evaluate(LArray &Out, LString Search, HtmlElem *Elem) { LVariant Result; if (Eng.EvaluateExpression(&Result, Elem, Search)) { if (Result.CastInt32()) Out.Add(Elem); } for (auto e: Elem->Children) Evaluate(Out, Search, dynamic_cast(e)); } }; bool SearchHtml(LVariant *ReturnValue, const char *Html, const char *SearchExp, const char *ResultExp) { ScribeHtmLParser Parser; ScribeHtmLParser::HtmlElem Root(NULL); if (!Parser.Parse(&Root, Html)) { LgiTrace("%s:%i - HTML parsing failed.\n", _FL); *ReturnValue = false; return false; } LArray Matches; Parser.Evaluate(Matches, SearchExp, &Root); if (!ReturnValue->SetList()) return false; LScriptEngine Eng(NULL, NULL, NULL); for (auto e: Matches) { LVariant *Result = new LVariant; Eng.EvaluateExpression(Result, e, ResultExp); ReturnValue->Add(Result); } return true; } ////////////////////////////////////////////////////////////////////////////////////////////////////////// ScriptDownloadContentThread::ScriptDownloadContentThread(ScribeWnd *app, LString uri, LString callbackName, LVariant *userData) : LThread("ScriptDownloadContentThread", (App = app)->AddDispatch()) { Uri = uri; CallbackName = callbackName; if (userData) UserData = *userData; DeleteOnExit = true; Run(); } int ScriptDownloadContentThread::Main() { Result = LgiGetUri(this, &Out, &Err, Uri); return false; } void ScriptDownloadContentThread::OnComplete() { auto Cb = App->GetCallback(CallbackName); if (!Cb.Func) return; LVirtualMachine Vm; LScriptArguments Args(&Vm); LVariant vApp((LDom*)App); Args.Add(&vApp); LVariant vUri = Uri.Get(); Args.Add(&vUri); LVariant vResult = Result; Args.Add(&vResult); LVariant vData; if (Result) vData.OwnStr(Out.NewStr()); else vData = Err.Get(); Args.Add(&vData); Args.Add(&UserData); App->ExecuteScriptCallback(Cb, Args); } diff --git a/src/Store3CalendarObj.h b/src/Store3CalendarObj.h --- a/src/Store3CalendarObj.h +++ b/src/Store3CalendarObj.h @@ -1,383 +1,514 @@ // This is a generic implementation of the Store3 interface for a calendar object // It just doesn't do the storage part, which can be implemented by the subclass. #pragma once #include "lgi/common/Json.h" template +class Store3CalendarFile : public Parent +{ +protected: + LString FileName; + LString MimeType; + LDateTime DateModified; + LString Uri; // Either 'Uri' or 'Data' must be valid + LString Data; // Store the data inline + + LString SizeCache; + bool Dirty = false; + +public: + bool IsDirty() { return Dirty; } + uint32_t Type() override { return MAGIC_CALENDAR_FILE; } + + bool CopyProps(LDataPropI &p) override + { + SetStr(FIELD_NAME, p.GetStr(FIELD_NAME)); + SetStr(FIELD_MIME_TYPE, p.GetStr(FIELD_MIME_TYPE)); + SetDate(FIELD_DATE_MODIFIED, p.GetDate(FIELD_DATE_MODIFIED)); + SetStr(FIELD_URI, p.GetStr(FIELD_URI)); + SetStr(FIELD_ATTACHMENTS_DATA, p.GetStr(FIELD_ATTACHMENTS_DATA)); + return true; + } + + // Impl LDataPropI + const char *GetStr(int id) override + { + switch (id) + { + case FIELD_NAME: + return FileName; + case FIELD_MIME_TYPE: + return MimeType; + case FIELD_URI: + return Uri; + case FIELD_ATTACHMENTS_DATA: + return Data; + case FIELD_SIZE: + SizeCache = LFormatSize(Data.Length()); + return SizeCache; + } + + return NULL; + } + + Store3Status SetStr(int id, const char *str) override + { + switch (id) + { + case FIELD_NAME: + FileName = str; + break; + case FIELD_MIME_TYPE: + MimeType = str; + break; + case FIELD_URI: + Uri = str; + break; + case FIELD_ATTACHMENTS_DATA: + Data = str; + break; + default: + return Store3NotImpl; + } + + Dirty = true; + return Store3Success; + } + + const LDateTime *GetDate(int id) override + { + switch (id) + { + case FIELD_DATE_MODIFIED: + return &DateModified; + } + + return NULL; + } + + Store3Status SetDate(int id, const LDateTime *i) override + { + switch (id) + { + case FIELD_DATE_MODIFIED: + if (i) + DateModified = *i; + else + DateModified.Empty(); + break; + default: + return Store3NotImpl; + } + + Dirty = true; + return Store3Success; + } + + // Impl LDataI + LAutoStreamI GetStream(const char *file, int line) override + { + LAutoStreamI s; + if (Data) + s.Reset(new LMemStream(Data.Get(), Data.Length(), false)); + return s; + } + + uint64 Size() override + { + return sizeof(this) + + FileName.Length() + + MimeType.Length() + + sizeof(DateModified) + + Uri.Length() + + Data.Length(); + } + + // Stubs + int64 GetInt(int id) override { return 0; } + Store3Status SetInt(int id, int64 i) override { return Store3NotImpl; } + const LVariant *GetVar(int id) override { return NULL; } + Store3Status SetVar(int id, LVariant *i) override { return Store3NotImpl; } + LDataPropI *GetObj(int id) override { return NULL; } + Store3Status SetObj(int id, LDataPropI *i) override { return Store3NotImpl; } + LDataIt GetList(int id) override { EmptyVirtual(NULL); } +}; + + +template class Store3CalendarObj : public Parent { LString ToCache; protected: int CalType = 0; // FIELD_CAL_TYPE LString To; // FIELD_TO CalendarPrivacyType CalPriv = CalDefaultPriv; // FIELD_CAL_PRIVACY int Completed = 0; // 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 = false; // FIELD_CAL_ALL_DAY int64 StoreStatus = Store3Success; // FIELD_STATUS - ie the current Store3Status LString EventStatus; // FIELD_CAL_STATUS LDateTime Modified; // FIELD_DATE_MODIFIED LString Reminders; // FIELD_CAL_REMINDERS LDateTime LastCheck; // FIELD_CAL_LAST_CHECK int ShowTimeAs = 0; // FIELD_CAL_SHOW_TIME_AS int Recur = 0; // FIELD_CAL_RECUR int RecurFreq = 0; // FIELD_CAL_RECUR_FREQ int RecurInterval = 0; // FIELD_CAL_RECUR_INTERVAL LDateTime RecurEnd; // FIELD_CAL_RECUR_END_DATE int RecurCount = 0; // FIELD_CAL_RECUR_END_COUNT int RecurEndType = 0; // FIELD_CAL_RECUR_END_TYPE LString RecurPos; // FIELD_CAL_RECUR_FILTER_POS int FilterDays = 0; // FIELD_CAL_RECUR_FILTER_DAYS int FilterMonths = 0; // FIELD_CAL_RECUR_FILTER_MONTHS LString FilterYears; // FIELD_CAL_RECUR_FILTER_YEARS LString Notes; // FIELD_CAL_NOTES LColour Colour; // FIELD_COLOUR public: uint32_t Type() override { return MAGIC_CALENDAR; } const char *GetClass() override { return "Store3CalendarObj"; } bool CopyProps(LDataPropI &p) override { SetInt(FIELD_CAL_TYPE, p.GetInt(FIELD_CAL_TYPE)); SetInt(FIELD_CAL_COMPLETED, p.GetInt(FIELD_CAL_COMPLETED)); SetDate(FIELD_CAL_START_UTC, p.GetDate(FIELD_CAL_START_UTC)); SetDate(FIELD_CAL_END_UTC, p.GetDate(FIELD_CAL_END_UTC)); SetStr(FIELD_CAL_TIMEZONE, p.GetStr(FIELD_CAL_TIMEZONE)); SetStr(FIELD_CAL_SUBJECT, p.GetStr(FIELD_CAL_SUBJECT)); SetStr(FIELD_CAL_LOCATION, p.GetStr(FIELD_CAL_LOCATION)); SetStr(FIELD_UID, p.GetStr(FIELD_UID)); SetStr(FIELD_CAL_REMINDERS, p.GetStr(FIELD_CAL_REMINDERS)); SetInt(FIELD_CAL_SHOW_TIME_AS, p.GetInt(FIELD_CAL_SHOW_TIME_AS)); SetInt(FIELD_CAL_RECUR, p.GetInt(FIELD_CAL_RECUR)); SetInt(FIELD_CAL_RECUR_FREQ, p.GetInt(FIELD_CAL_RECUR_FREQ)); SetInt(FIELD_CAL_RECUR_INTERVAL, p.GetInt(FIELD_CAL_RECUR_INTERVAL)); SetDate(FIELD_CAL_RECUR_END_DATE, p.GetDate(FIELD_CAL_RECUR_END_DATE)); SetInt(FIELD_CAL_RECUR_END_COUNT, p.GetInt(FIELD_CAL_RECUR_END_COUNT)); SetInt(FIELD_CAL_RECUR_END_TYPE, p.GetInt(FIELD_CAL_RECUR_END_TYPE)); SetStr(FIELD_CAL_RECUR_FILTER_POS, p.GetStr(FIELD_CAL_RECUR_FILTER_POS)); SetInt(FIELD_CAL_RECUR_FILTER_DAYS, p.GetInt(FIELD_CAL_RECUR_FILTER_DAYS)); SetInt(FIELD_CAL_RECUR_FILTER_MONTHS, p.GetInt(FIELD_CAL_RECUR_FILTER_MONTHS)); SetStr(FIELD_CAL_RECUR_FILTER_YEARS, p.GetStr(FIELD_CAL_RECUR_FILTER_YEARS)); SetStr(FIELD_CAL_NOTES, p.GetStr(FIELD_CAL_NOTES)); SetDate(FIELD_CAL_LAST_CHECK, p.GetDate(FIELD_CAL_LAST_CHECK)); SetDate(FIELD_DATE_MODIFIED, p.GetDate(FIELD_DATE_MODIFIED)); return true; } const char *GetStr(int id) override { switch (id) { case FIELD_TO: { if (!ToCache) { LJson j(To); LString::Array Out; Out.SetFixedLength(false); for (auto i: j.GetArray(LString())) { auto Nm = i.Get("name"); auto Em = i.Get("email"); Out.New().Printf("\'%s\' <%s>", Nm.Get(), Em.Get()); } ToCache = LString(",").Join(Out); } return ToCache; } case FIELD_ATTENDEE_JSON: return To; case FIELD_CAL_TIMEZONE: return TimeZone; case FIELD_CAL_SUBJECT: return Subject; case FIELD_CAL_LOCATION: return Location; case FIELD_UID: return Uid; case FIELD_CAL_REMINDERS: return Reminders; case FIELD_CAL_RECUR_FILTER_POS: return RecurPos; case FIELD_CAL_RECUR_FILTER_YEARS: return FilterYears; case FIELD_CAL_NOTES: return Notes; case FIELD_CAL_STATUS: return EventStatus; } LAssert(0); return 0; } Store3Status SetStr(int id, const char *str) override { switch (id) { case FIELD_TO: { LJson j; auto In = LString(str).SplitDelimit(","); int i = 0; for (auto s: In) { LAutoString Name, Addr; DecodeAddrName(s, Name, Addr, NULL); if (Addr) { LString a; a.Printf("[%i].email", i); j.Set(a, Addr); a.Printf("[%i].name", i); if (Name) j.Set(a, Name); i++; } } To = j.GetJson(); ToCache = str; break; } case FIELD_ATTENDEE_JSON: To = str; ToCache.Empty(); break; case FIELD_CAL_TIMEZONE: TimeZone = str; break; case FIELD_CAL_SUBJECT: Subject = str; break; case FIELD_CAL_LOCATION: Location = str; break; case FIELD_UID: Uid = str; break; case FIELD_CAL_REMINDERS: Reminders = str; break; case FIELD_CAL_RECUR_FILTER_POS: RecurPos = str; break; case FIELD_CAL_RECUR_FILTER_YEARS: FilterYears = str; break; case FIELD_CAL_NOTES: Notes = str; break; case FIELD_CAL_STATUS: EventStatus = str; break; default: LAssert(0); return Store3Error; } return Store3Success; } int64 GetInt(int id) override { switch (id) { case FIELD_STORE_TYPE: return Store3Sqlite; case FIELD_CAL_TYPE: return CalType; case FIELD_CAL_COMPLETED: return Completed; case FIELD_CAL_SHOW_TIME_AS: return ShowTimeAs; case FIELD_CAL_RECUR: return Recur; case FIELD_CAL_RECUR_FREQ: return RecurFreq; case FIELD_CAL_RECUR_INTERVAL: return RecurInterval; case FIELD_CAL_RECUR_END_COUNT: return RecurCount; case FIELD_CAL_RECUR_END_TYPE: return RecurEndType; case FIELD_CAL_RECUR_FILTER_DAYS: return FilterDays; case FIELD_CAL_RECUR_FILTER_MONTHS: return FilterMonths; case FIELD_CAL_PRIVACY: return CalPriv; case FIELD_COLOUR: if (Colour.IsValid()) return Colour.c32(); return -1; case FIELD_CAL_ALL_DAY: return AllDay; case FIELD_STATUS: return StoreStatus; } LAssert(0); return -1; } Store3Status SetInt(int id, int64 val) override { int n = (int)val; switch (id) { case FIELD_CAL_TYPE: CalType = n; break; case FIELD_CAL_COMPLETED: Completed = n; break; case FIELD_CAL_SHOW_TIME_AS: ShowTimeAs = n; break; case FIELD_CAL_RECUR: Recur = n; break; case FIELD_CAL_RECUR_FREQ: RecurFreq = n; break; case FIELD_CAL_RECUR_INTERVAL: RecurInterval = n; break; case FIELD_CAL_RECUR_END_COUNT: RecurCount = n; break; case FIELD_CAL_RECUR_END_TYPE: RecurEndType = n; break; case FIELD_CAL_RECUR_FILTER_DAYS: FilterDays = n; break; case FIELD_CAL_RECUR_FILTER_MONTHS: FilterMonths = n; break; case FIELD_CAL_PRIVACY: CalPriv = (CalendarPrivacyType)n; break; case FIELD_COLOUR: if (val > 0) Colour.Set((uint32_t)val, 32); else Colour.Empty(); break; case FIELD_CAL_ALL_DAY: AllDay = val != 0; break; case FIELD_STATUS: StoreStatus = val; break; default: LAssert(0); return Store3Error; } return Store3Success; } const LDateTime *GetDate(int id) override { switch (id) { case FIELD_CAL_START_UTC: return &Start; case FIELD_CAL_END_UTC: return &End; case FIELD_CAL_RECUR_END_DATE: return &RecurEnd; case FIELD_CAL_LAST_CHECK: return &LastCheck; case FIELD_DATE_MODIFIED: return &Modified; } return NULL; } Store3Status SetDate(int id, const LDateTime *t) override { if (!t) return Store3Error; if (t->GetTimeZone()) { LStackTrace("LMail3Calendar::SetDate error: date has timezone.\n"); LAssert(t->GetTimeZone()==0); } switch (id) { case FIELD_CAL_START_UTC: if (t) Start = *t; else Start.Year(0); break; case FIELD_CAL_END_UTC: if (t) End = *t; else End.Year(0); break; case FIELD_CAL_RECUR_END_DATE: if (t) RecurEnd = *t; else RecurEnd.Year(0); break; case FIELD_CAL_LAST_CHECK: if (t) LastCheck = *t; else LastCheck.Empty(); break; case FIELD_DATE_MODIFIED: if (t) Modified = *t; else Modified.Empty(); break; default: return Store3NotImpl; } return Store3Success; } // LDataI impl: uint64 Size() override { return sizeof(*this) + ToCache.Length() + To.Length() + TimeZone.Length() + Subject.Length() + Location.Length() + Uid.Length() + Reminders.Length(); } /* Impl this is subclass: bool IsOnDisk() override { return false; } bool IsOrphan() override { return false; } Store3Status Save(LDataI *Obj = NULL) override { return Store3NotImpl; } Store3Status Delete(bool ToTrash = true) override { return Store3NotImpl; } LAutoStreamI GetStream(const char *file, int line) override { return LAutoStreamI(); } */ }; diff --git a/src/Store3Mail3/Mail3.h b/src/Store3Mail3/Mail3.h --- a/src/Store3Mail3/Mail3.h +++ b/src/Store3Mail3/Mail3.h @@ -1,809 +1,781 @@ #ifndef _MAIL3_H_ #define _MAIL3_H_ #include "lgi/common/Lgi.h" #include "Store3Common.h" #include "v3.41.2/sqlite3.h" #include "Store3CalendarObj.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" #define MAIL3_TBL_CALENDAR_FILES "CalendarFiles" class LMail3Store; class LMail3Mail; class LMail3Calendar; enum Mail3SubFormat { Mail3v1, // All the mail and segs in a single tables. Mail3v2, // All the mail and segs in per folders tables. }; struct LMail3Def { 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 LMail3Def TblFolder[]; extern LMail3Def TblFolderFlds[]; extern LMail3Def TblMail[]; extern LMail3Def TblMailSegs[]; extern LMail3Def TblContact[]; extern LMail3Def TblFilter[]; extern LMail3Def TblGroup[]; extern LMail3Def TblCalendar[]; extern LMail3Def TblCalendarFiles[]; #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_LSTR(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, LMail3Def*> 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, LMail3Def *Flds); bool UpdateTable(const char *Name, LMail3Def *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 = NULL); 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, const 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 = NULL); 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) override; Store3Status Delete(LArray &Items, bool ToTrash) override; Store3Status Change(LArray &Items, int PropId, LVariant &Value, LOperator Operator) override; void Compact(LViewI *Parent, LDataPropI *Props, std::function OnStatus) override; void Upgrade(LViewI *Parent, LDataPropI *Props, std::function OnStatus) override; void Repair(LViewI *Parent, LDataPropI *Props, std::function OnStatus) override; bool SetFormat(LViewI *Parent, LDataPropI *Props) override; void PostStore(LMail3StoreMsg *m) { Callback->Post(this, m); } void OnEvent(void *Param) override; bool Check(int Code, const char *Sql); LMail3Def *GetFields(const char *t) { return Fields.Find(t); } StoreTrans StartTransaction() override; // 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 bool SetId(int64_t id) { Id = id; return Id >= 0; } 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 = NULL) : LMail3Obj(store) { } ~LMail3Thing(); const char *GetClass() { return "LMail3Thing"; } bool IsOnDisk() { return Id > 0; } bool IsOrphan() { return Store == NULL || Parent == NULL; } 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 = NULL); 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; LString 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(); const char *GetClass() override { return "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 = NULL) override; void OnSave() override; }; class LMail3Mail : public LMail3Thing { LAutoString TextCache; LAutoString HtmlCache; LString IdCache; LAutoPtr SizeCache; LString InferredCharset; LString ErrMsg; void LoadSegs(); void OnSave() override; const char *GetTable() override { return MAIL3_TBL_MAIL; } const char *InferCharset(const char *ExampleTxt); bool Utf8Check(LString &v); 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 LString 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 = NULL); 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; LDataIt 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; LString Name; LString ConditionsXml; LString ActionsXml; LString Script; LDateTime Modified; 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; const LDateTime *GetDate(int id) override; Store3Status SetDate(int id, const LDateTime *i) override; }; -class LMail3CalendarFile : public LDataI +class LMail3CalendarFile : public Store3CalendarFile { friend class LMail3Calendar; LMail3Store *Store = NULL; LMail3Calendar *Calendar = NULL; int64 Id = -1; int64 ParentId = -1; - LString FileName; - LString MimeType; - LDateTime DateModified; - LString Uri; // Either 'Uri' or 'Data' must be valid - LString Data; // Store the data inline - - LString SizeCache; - bool Dirty = false; public: LMail3CalendarFile(LMail3Store *store); const char *GetClass() override { return "LMail3CalendarFile"; } bool IsDirty() { return Dirty || Id < 0 || ParentId < 0; } - bool CopyProps(LDataPropI &p) override; bool Serialize(LMail3Store::LStatement &s, bool Write); - // Impl LDataPropI - const char *GetStr(int id) override; - Store3Status SetStr(int id, const char *str) override; - const LDateTime *GetDate(int id) override; - Store3Status SetDate(int id, const LDateTime *i) override; - // Impl LDataI - uint32_t Type() override { return MAGIC_CALENDAR_FILE; } bool IsOnDisk() override { return Id >= 0; } bool IsOrphan() override { return Calendar == NULL; } LDataStoreI *GetStore() override { return Store; } - - LAutoStreamI GetStream(const char *file, int line) override; - uint64 Size() override; Store3Status Save(LDataI *Parent = NULL) override; Store3Status Delete(bool ToTrash = true) override; - - // Stubs - int64 GetInt(int id) override { return 0; } - Store3Status SetInt(int id, int64 i) override { return Store3NotImpl; } - const LVariant *GetVar(int id) override { return NULL; } - Store3Status SetVar(int id, LVariant *i) override { return Store3NotImpl; } - LDataPropI *GetObj(int id) override { return NULL; } - Store3Status SetObj(int id, LDataPropI *i) override { return Store3NotImpl; } - LDataIt GetList(int id) override { EmptyVirtual(NULL); } }; class LMail3Calendar : public Store3CalendarObj { friend class LMail3CalendarFile; private: const char *GetTable() override { return MAIL3_TBL_CALENDAR; } DIterator Attachments; bool SaveAttachments(); public: LMail3Calendar(LMail3Store *store); ~LMail3Calendar(); LDataStoreI *GetStore() override { return Store; } bool SetId(int64_t id) override; bool Serialize(LMail3Store::LStatement &s, bool Write) override; const char *GetClass() override { return "LMail3Filter"; } bool DbDelete() override; LDataIt GetList(int id) override; }; #endif diff --git a/src/Store3Mail3/Mail3Calendar.cpp b/src/Store3Mail3/Mail3Calendar.cpp --- a/src/Store3Mail3/Mail3Calendar.cpp +++ b/src/Store3Mail3/Mail3Calendar.cpp @@ -1,430 +1,328 @@ #include "Mail3.h" #include "lgi/common/Json.h" LMail3Def TblCalendar[] = { {"Id", "INTEGER PRIMARY KEY AUTOINCREMENT"}, {"ParentId", "INTEGER"}, {"CalType", "INTEGER"}, // FIELD_CAL_TYPE {"Completed", "INTEGER"}, // FIELD_CAL_COMPLETED {"Start", "TEXT"}, // FIELD_CAL_START_UTC {"End", "TEXT"}, // FIELD_CAL_END_UTC {"TimeZone", "TEXT"}, // FIELD_CAL_TIMEZONE {"Subject", "TEXT"}, // FIELD_CAL_SUBJECT {"Location", "TEXT"}, // FIELD_CAL_LOCATION {"Uid", "TEXT"}, // FIELD_UID {"ShowTimeAs", "INTEGER"}, // FIELD_CAL_SHOW_TIME_AS {"Recur", "INTEGER"}, // FIELD_CAL_RECUR {"RecurFreq", "INTEGER"}, // FIELD_CAL_RECUR_FREQ {"RecurInterval", "INTEGER"}, // FIELD_CAL_RECUR_INTERVAL {"RecurEnd", "TEXT"}, // FIELD_CAL_RECUR_END_DATE {"RecurCount", "INTEGER"}, // FIELD_CAL_RECUR_END_COUNT {"RecurEndType", "INTEGER"}, // FIELD_CAL_RECUR_END_TYPE {"RecurPos", "TEXT"}, // FIELD_CAL_RECUR_FILTER_POS {"FilterDays", "INTEGER"}, // FIELD_CAL_RECUR_FILTER_DAYS {"FilterMonths", "INTEGER"}, // FIELD_CAL_RECUR_FILTER_MONTHS {"FilterYears", "TEXT"}, // FIELD_CAL_RECUR_FILTER_YEARS {"Notes", "TEXT"}, // FIELD_CAL_NOTES {"Colour", "INTEGER"}, // FIELD_COLOUR {"Guests", "TEXT"}, // FIELD_TO {"Reminders", "TEXT"}, // FIELD_CAL_REMINDERS {"LastCheck", "TEXT"}, // FIELD_CAL_LAST_CHECK {"AllDay", "INTEGER"}, // FIELD_CAL_ALL_DAY {"Status", "STATUS"}, // FIELD_CAL_STATUS {"DateModified", "TEXT"}, // FIELD_DATE_MODIFIED {0, 0} }; LMail3Def TblCalendarFiles[] = { {"Id", "INTEGER PRIMARY KEY AUTOINCREMENT"}, {"ParentId", "INTEGER"}, // ID of the calendar event... {"FileName", "TEXT"}, // FIELD_NAME {"MimeType", "TEXT"}, // FIELD_MIME_TYPE {"DateModified", "TEXT"}, // FIELD_DATE_MODIFIED {"Uri", "TEXT"}, // FIELD_URI {"Data", "TEXT"}, // FIELD_ATTACHMENTS_DATA {0, 0} }; LMail3Calendar::LMail3Calendar(LMail3Store *store) { Store = store; } LMail3Calendar::~LMail3Calendar() { } bool LMail3Calendar::DbDelete() { Attachments.a.DeleteObjects(); Attachments.State = Store3Unloaded; // Delete any attachments. auto Sql = LString::Fmt("delete from " MAIL3_TBL_CALENDAR_FILES " where ParentId=" LPrintfInt64, Id); LMail3Store::LStatement FileDel(Store, Sql); if (!FileDel.Exec()) return false; // Delete the calendar itself Sql = LString::Fmt("delete from " MAIL3_TBL_CALENDAR " where Id=" LPrintfInt64, Id); LMail3Store::LStatement Del(Store, Sql); if (!Del.Exec()) return false; Id = -1; return true; } LDataIt LMail3Calendar::GetList(int id) { switch (id) { case FIELD_CAL_ATTACHMENTS: { if (Attachments.State == Store3Unloaded) { auto Sql = LString::Fmt("select * from %s where ParentId=" LPrintfInt64, MAIL3_TBL_CALENDAR_FILES, Id); LMail3Store::LStatement s(Store, Sql); if (s.IsOk()) { while (s.Row()) { if (auto a = new LMail3CalendarFile(Store)) { a->Calendar = this; if (a->Serialize(s, false)) Attachments.a.Add(a); else delete a; } } Attachments.State = Store3Loaded; } } return &Attachments; } } return NULL; } bool LMail3Calendar::SaveAttachments() { if (Id < 0) { LAssert(!"Needs an ID before you can call this."); return false; } printf("LMail3Calendar::Serialize attachments=%i\n", (int)Attachments.Length()); for (auto i: Attachments.a) { printf("LMail3Calendar::Serialize dirty=%i\n", i->IsDirty()); if (i->IsDirty()) { auto result = i->Save(this); printf("LMail3Calendar::Serialize result=%i\n", result); if (result != Store3Success) { LAssert(!"Attachment failed to save."); return false; } } } return true; } bool LMail3Calendar::SetId(int64_t id) { bool valid = Id >= 0; if (!LMail3Thing::SetId(id)) return false; if (!valid) SaveAttachments(); return true; } bool LMail3Calendar::Serialize(LMail3Store::LStatement &s, bool Write) { int i = 0; SERIALIZE_INT64(Id, i++); SERIALIZE_INT64(ParentId, i++); SERIALIZE_INT(CalType, i++); // FIELD_CAL_TYPE SERIALIZE_INT(Completed, i++); // FIELD_CAL_COMPLETED SERIALIZE_DATE(Start, i++); // FIELD_CAL_START_UTC SERIALIZE_DATE(End, i++); // FIELD_CAL_END_UTC SERIALIZE_LSTR(TimeZone, i++); // FIELD_CAL_TIMEZONE SERIALIZE_LSTR(Subject, i++); // FIELD_CAL_SUBJECT SERIALIZE_LSTR(Location, i++); // FIELD_CAL_LOCATION SERIALIZE_LSTR(Uid, i++); // FIELD_UID SERIALIZE_INT(ShowTimeAs, i++); // FIELD_CAL_SHOW_TIME_AS SERIALIZE_INT(Recur, i++); // FIELD_CAL_RECUR SERIALIZE_INT(RecurFreq, i++); // FIELD_CAL_RECUR_FREQ SERIALIZE_INT(RecurInterval, i++); // FIELD_CAL_RECUR_INTERVAL SERIALIZE_DATE(RecurEnd, i++); // FIELD_CAL_RECUR_END_DATE SERIALIZE_INT(RecurCount, i++); // FIELD_CAL_RECUR_END_COUNT SERIALIZE_INT(RecurEndType, i++); // FIELD_CAL_RECUR_END_TYPE SERIALIZE_LSTR(RecurPos, i++); // FIELD_CAL_RECUR_FILTER_POS SERIALIZE_INT(FilterDays, i++); // FIELD_CAL_RECUR_FILTER_DAYS SERIALIZE_INT(FilterMonths, i++); // FIELD_CAL_RECUR_FILTER_MONTHS SERIALIZE_LSTR(FilterYears, i++); // FIELD_CAL_RECUR_FILTER_YEARS SERIALIZE_LSTR(Notes, i++); // FIELD_CAL_NOTES SERIALIZE_COLOUR(Colour, i++); // FIELD_COLOUR SERIALIZE_LSTR(To, i++); // FIELD_TO SERIALIZE_LSTR(Reminders, i++); // FIELD_CAL_REMINDER_TIME SERIALIZE_DATE(LastCheck, i++); // FIELD_CAL_LAST_CHECK SERIALIZE_BOOL(AllDay, i++); // FIELD_CAL_ALL_DAY SERIALIZE_LSTR(EventStatus, i++); // FIELD_CAL_STATUS if (Write) { StoreStatus = Store3Success; if (Id >= 0 && !SaveAttachments()) { StoreStatus = Store3Error; return false; } } else if (To.Get()) { char c = To.Get()[0]; if (c != '[' && c != '{') { // Convert old style comma separated field to JSON LString s = To; To.Empty(); SetStr(FIELD_TO, s); } } return true; } /////////////////////////////////////////////////////////////////////////////////////////////// LMail3CalendarFile::LMail3CalendarFile(LMail3Store *store) : Store(store) { } -bool LMail3CalendarFile::CopyProps(LDataPropI &p) -{ - SetStr(FIELD_NAME, p.GetStr(FIELD_NAME)); - SetStr(FIELD_MIME_TYPE, p.GetStr(FIELD_MIME_TYPE)); - SetDate(FIELD_DATE_MODIFIED, p.GetDate(FIELD_DATE_MODIFIED)); - SetStr(FIELD_URI, p.GetStr(FIELD_URI)); - SetStr(FIELD_ATTACHMENTS_DATA, p.GetStr(FIELD_ATTACHMENTS_DATA)); - - return true; -} - bool LMail3CalendarFile::Serialize(LMail3Store::LStatement &s, bool Write) { int i = 0; if (Write) LAssert(ParentId >= 0); SERIALIZE_INT64(Id, i++); SERIALIZE_INT64(ParentId, i++); SERIALIZE_LSTR(FileName, i++); // FIELD_NAME SERIALIZE_LSTR(MimeType, i++); // FIELD_MIME_TYPE SERIALIZE_DATE(DateModified, i++); // FIELD_DATE_MODIFIED SERIALIZE_LSTR(Uri, i++); // FIELD_URI SERIALIZE_LSTR(Data, i++); // FIELD_ATTACHMENTS_DATA return true; } -const char *LMail3CalendarFile::GetStr(int id) -{ - switch (id) - { - case FIELD_NAME: - return FileName; - case FIELD_MIME_TYPE: - return MimeType; - case FIELD_URI: - return Uri; - case FIELD_ATTACHMENTS_DATA: - return Data; - case FIELD_SIZE: - SizeCache = LFormatSize(Data.Length()); - return SizeCache; - } - - return NULL; -} - -Store3Status LMail3CalendarFile::SetStr(int id, const char *str) -{ - switch (id) - { - case FIELD_NAME: - FileName = str; - break; - case FIELD_MIME_TYPE: - MimeType = str; - break; - case FIELD_URI: - Uri = str; - break; - case FIELD_ATTACHMENTS_DATA: - Data = str; - break; - default: - return Store3NotImpl; - } - - Dirty = true; - return Store3Success; -} - -const LDateTime *LMail3CalendarFile::GetDate(int id) -{ - switch (id) - { - case FIELD_DATE_MODIFIED: - return &DateModified; - } - - return NULL; -} - -Store3Status LMail3CalendarFile::SetDate(int id, const LDateTime *i) -{ - switch (id) - { - case FIELD_DATE_MODIFIED: - if (i) - DateModified = *i; - else - DateModified.Empty(); - break; - default: - return Store3NotImpl; - } - - Dirty = true; - return Store3Success; -} - -LAutoStreamI LMail3CalendarFile::GetStream(const char *file, int line) -{ - LAutoStreamI s; - if (Data) - s.Reset(new LMemStream(Data.Get(), Data.Length(), false)); - return s; -} - -uint64 LMail3CalendarFile::Size() -{ - return sizeof(this) + - FileName.Length() + - MimeType.Length() + - sizeof(DateModified) + - Uri.Length() + - Data.Length(); -} - Store3Status LMail3CalendarFile::Save(LDataI *Parent) { if (Parent) { Calendar = dynamic_cast(Parent); if (!Calendar) { LAssert(!"Wrong object type!"); return Store3Error; } - - if (Calendar->Attachments.IndexOf(this) < 0) - Calendar->Attachments.Insert(this); } if (!Calendar) { LAssert(!"Must have parent calendar event."); return Store3Error; } + + if (Calendar->Attachments.IndexOf(this) < 0) + Calendar->Attachments.Insert(this); ParentId = Calendar->Id; if (ParentId < 0) { // Parent event hasn't got an ID yet... assuming that when it's // saved, this object will get saved WITH it. return Store3Delayed; } LAutoPtr s; if (Id >= 0) s.Reset(new LMail3Store::LUpdate(Store, MAIL3_TBL_CALENDAR_FILES, Id)); else s.Reset(new LMail3Store::LInsert(Store, MAIL3_TBL_CALENDAR_FILES)); if (!s || !s->IsOk()) { LAssert(!"Query not valid."); return Store3Error; } if (!Serialize(*s, true)) { LAssert(!"Serialize failed"); return Store3Error; } if (!s->Exec()) { LAssert(!"Query failed"); return Store3Error; } if (Id < 0) Id = s->LastInsertId(); return Store3Success; } Store3Status LMail3CalendarFile::Delete(bool ToTrash) { if (!Calendar) { LAssert(!"No calendar object?"); return Store3Error; } auto idx = Calendar->Attachments.IndexOf(this); if (idx < 0) { LAssert(!"Calendar doesn't have this file?"); return Store3Error; } auto Sql = LString::Fmt("delete from " MAIL3_TBL_CALENDAR_FILES " where Id=" LPrintfInt64, Id); LMail3Store::LStatement s(Store, Sql); if (!s.Exec()) { LAssert(!"Delete query failed"); return Store3Error; } return Store3Success; } diff --git a/src/Store3Webdav/WebdavCalendar.cpp b/src/Store3Webdav/WebdavCalendar.cpp --- a/src/Store3Webdav/WebdavCalendar.cpp +++ b/src/Store3Webdav/WebdavCalendar.cpp @@ -1,229 +1,267 @@ #include "Scribe.h" #include "CalendarView.h" #include "lgi/common/Base64.h" #include "lgi/common/XmlTree.h" #include "lgi/common/vCard-vCal.h" #include "WebdavStore.h" #include "WebdavStorePriv.h" ///////////////////////////////////////////////////////////////////////////////////// WebdavCalendar::WebdavCalendar(WebdavStore *store, WebdavEvent *e) : WebdavObj(store) { if (e) { Href = e->Href; vCal = e->Data; } Colour = -1; LMemStream m(vCal.Get(), vCal.Length(), false); VCal convert; Converted = convert.Import(this, &m); } void WebdavCalendar::FireOnChange(int Fld) { LArray a; a.Add(this); Store->App->SetContext(_FL); Store->App->OnChange(a, Fld); } bool WebdavCalendar::ConvertToText() { LStringPipe p; VCal convert; Converted = convert.Export(this, &p); if (Converted) vCal = p.NewLStr(); else vCal.Empty(); return Converted; } Store3Status WebdavCalendar::Save(LDataI *parent) { Store3Status Ret = Store3Error; // Convert the object to text... if (!ConvertToText()) { LgiTrace("%s:%i - Failed to convert to text.\n", _FL); return Ret; } if (!Parent) { Parent = dynamic_cast(parent); if (Parent) { LAssert(Parent->Items.IndexOf(this) < 0); Parent->Items.Insert(this); } } if (!Parent) { LgiTrace("%s:%i - No Parent folder.\n", _FL); return Ret; } // Now save the vCal object to the WebDav server... if (!Parent->Thread) { LgiTrace("%s:%i - No Parent thread.\n", _FL); return Ret; } if (!Href) Href = Parent->AllocateAddress(); if (!Href) { LgiTrace("%s:%i - No Href.\n", _FL); return Ret; } Ret = Parent->Thread->Save(Href, vCal); if (StoreStatus != Ret) { StoreStatus = Ret; FireOnChange(FIELD_STATUS); } return Ret; } Store3Status WebdavCalendar::Delete(bool ToTrash) { Store3Status Status = Store3Error; // Now save the vCal object to the WebDav server... if (Parent && Parent->Thread) Status = Parent->Thread->Delete(Href); else LAssert(!"No thread?"); return Status; } LAutoStreamI WebdavCalendar::GetStream(const char *file, int line) { LAutoStreamI a; if (ConvertToText()) a.Reset(new LMemStream(vCal.Get(), vCal.Length(), true)); return a; } bool WebdavCalendar::CopyProps(LDataPropI &p) { #define _(f,v) SetDate(f, p.GetDate(f)); WebdavCalendarDates() #undef _ #define _(f,v) SetInt(f, p.GetInt(f)); WebdavCalendarInts() #undef _ #define _(f,v) SetStr(f, p.GetStr(f)); WebdavCalendarStrings() #undef _ return true; } const char *WebdavCalendar::GetStr(int id) { switch (id) { #define _(f,v) case f: return v; WebdavCalendarStrings() #undef _ } return NULL; } Store3Status WebdavCalendar::SetStr(int id, const char *str) { switch (id) { #define _(f,v) case f: v = str; break; WebdavCalendarStrings() #undef _ default: { LAssert(!"Impl me."); return Store3Error; } } return Store3Success; } int64 WebdavCalendar::GetInt(int id) { switch (id) { #define _(f,v) case f: return v; WebdavCalendarInts() #undef _ case FIELD_STORE_TYPE: return Store3Webdav; default: { LAssert(!"Impl me."); return Store3Error; } } return Store3Success; } Store3Status WebdavCalendar::SetInt(int id, int64 i) { switch (id) { #define _(f,v) case f: v = i; break; WebdavCalendarInts() #undef _ default: { LAssert(!"Impl me."); return Store3Error; } } return Store3Success; } const LDateTime *WebdavCalendar::GetDate(int id) { switch (id) { #define _(f,v) case f: return &v; WebdavCalendarDates() #undef _ } LAssert(!"Impl me."); return NULL; } Store3Status WebdavCalendar::SetDate(int id, const LDateTime *i) { if (!i) return Store3Error; switch (id) { #define _(f,v) case f: v = *i; break; WebdavCalendarDates() #undef _ default: { LAssert(!"Impl me."); return Store3Error; } } return Store3Success; } +LDataIt WebdavCalendar::GetList(int id) +{ + switch (id) + { + case FIELD_CAL_ATTACHMENTS: + Attachments.State = Store3Loaded; + return &Attachments; + } + + return NULL; +} + +//////////////////////////////////////////////////////////////////////////////// +Store3Status WebdavCalendarFile::Save(LDataI *Parent) +{ + if (Parent) + Cal = dynamic_cast(Parent); + + if (!Cal) + return Store3Error; + + if (Cal->Attachments.IndexOf(this) < 0) + Cal->Attachments.Insert(this); + + return Store3Success; +} + +Store3Status WebdavCalendarFile::Delete(bool ToTrash) +{ + if (!Cal) + return Store3Error; + + if (Cal->Attachments.IndexOf(this) < 0) + return Store3Error; + + Cal->Attachments.Delete(this); + return Store3Success; +} diff --git a/src/Store3Webdav/WebdavStore.cpp b/src/Store3Webdav/WebdavStore.cpp --- a/src/Store3Webdav/WebdavStore.cpp +++ b/src/Store3Webdav/WebdavStore.cpp @@ -1,322 +1,319 @@ #include "Scribe.h" #include "CalendarView.h" #include "lgi/common/Base64.h" #include "lgi/common/XmlTree.h" #include "lgi/common/vCard-vCal.h" #include "WebdavStore.h" #include "WebdavStorePriv.h" //////////////////////////////////////////////////////////////////////////////////////// WebdavStore::WebdavStore(ScribeWnd *a, LDataEventsI *cb, LString storeName) : LMutex("WebdavStore") { App = a; Callback = cb; Remote.Name = storeName; Root = new WebdavFolder(this); OnChanged(); } WebdavStore::~WebdavStore() { DeleteObj(CalFolder); DeleteObj(ContactFolder); DeleteObj(Root); Events.DeleteObjects(); } LXmlTag *WebdavStore::LockSettings(const char *File, int Line) { auto Opts = App->GetOptions(); auto Ms = Opts->LockTag(OPT_MailStores, File, Line); if (!Ms) return NULL; for (auto t: Ms->Children) { auto nm = t->GetAttr(OPT_MailStoreName); if (Remote.Name.Equals(nm)) return t; } Opts->Unlock(); return NULL; } void WebdavStore::UnlockSettings() { App->GetOptions()->Unlock(); } Store3Status WebdavStore::Delete(LArray &Items, bool ToTrash) { int Deleted = 0; for (auto it: Items) { WebdavObj *o = dynamic_cast(it); if (!o) continue; WebdavFolder *p = o->Parent; if (p) { LAssert(p->Items.IndexOf(it) >= 0); // Start delete... if (p->Thread) { if (p->Thread->Delete(o->GetHref()) > Store3Error) Deleted++; } else { return Store3Error; } } else { LAssert(!"Impl me."); } } return Deleted == Items.Length() ? Store3Success : Store3Error; } LDataI *WebdavStore::Create(int Type) { switch ((Store3ItemTypes)Type) { case MAGIC_CALENDAR: - { return new WebdavCalendar(this, NULL); - break; - } + case MAGIC_CALENDAR_FILE: + return new WebdavCalendarFile(this); case MAGIC_CONTACT: - { return new WebdavContact(this, NULL); - break; - } default: { + LAssert(!"Impl type here"); LgiTrace("%s:%i - Unhandled type %x\n", _FL, Type); break; } } return NULL; } void WebdavStore::OnChanged() { // Get the current settings auto t = LockSettings(_FL); if (t) { ContactUrl = t->GetAttr(OPT_MailStoreContactUrl); CalUrl = t->GetAttr(OPT_MailStoreCalendarUrl); Remote.User = t->GetAttr(OPT_MailStoreUserName); Remote.Pass = t->GetAttr(OPT_MailStorePassword); UnlockSettings(); } else return; #if 1 if (ContactUrl) { if (!ContactFolder) { ContactFolder = new WebdavFolder(this, Root); ContactFolder->Url = ContactUrl; ContactFolder->Extension = "vcf"; ContactFolder->SetInt(FIELD_FOLDER_TYPE, MAGIC_CONTACT); ContactFolder->SetStr(FIELD_FOLDER_NAME, "Contacts"); } if (ContactFolder && !ContactFolder->Thread) ContactFolder->Thread.Reset(new WebdavThread(this, ContactFolder, ContactUrl)); } else { DeleteObj(ContactFolder); } #endif if (CalUrl) { if (!CalFolder) { CalFolder = new WebdavFolder(this, Root); CalFolder->Url = CalUrl; CalFolder->Extension = "ics"; CalFolder->SetInt(FIELD_FOLDER_TYPE, MAGIC_CALENDAR); CalFolder->SetStr(FIELD_FOLDER_NAME, "Calendar"); } if (CalFolder && !CalFolder->Thread) CalFolder->Thread.Reset(new WebdavThread(this, CalFolder, CalUrl)); } else { DeleteObj(CalFolder); } } LDataPropI *WebdavStore::GetObj(int id) { return NULL; } int64 WebdavStore::GetInt(int id) { switch (id) { case FIELD_STATUS: return Store3Success; case FIELD_VERSION: return 1; case FIELD_READONLY: return false; case FIELD_STORE_TYPE: return Store3Webdav; default: LAssert(!"Not impl."); break; } return -1; } const char *WebdavStore::GetStr(int id) { switch (id) { case FIELD_STORE_PASSWORD: return NULL; default: LAssert(!"Not impl."); break; } return NULL; } LDataFolderI *WebdavStore::GetRoot(bool create) { return Root; } void WebdavStore::OnEvent(void *Param) { LAutoPtr e((WebdavEvent*) Param); if (!e) return; // Delete from our store of events, as the auto ptr now owns it. if (Lock(_FL)) { LAssert(Events.HasItem(e)); Events.Delete(e); Unlock(); } switch (e->Type) { case CmdFile: { WebdavObj *o = NULL; switch (e->Folder->ItemType) { case MAGIC_CONTACT: { o = new WebdavContact(this, e); break; } case MAGIC_CALENDAR: { o = new WebdavCalendar(this, e); break; } default: break; } if (o) { o->Parent = e->Folder; e->Folder->Items.State = Store3Loaded; e->Folder->Items.Insert(o); LArray a; a.Add(o); Callback->OnNew(e->Folder, a, -1, false); } break; } case CmdSave: { for (auto o: e->Folder->Items.a) { if (o->Href == e->Href) { o->SetInt(FIELD_STATUS, e->Status ? Store3Success : Store3Error); o->FireOnChange(FIELD_STATUS); } } break; } case CmdDelete: { for (auto o: e->Folder->Items.a) { if (o->Href == e->Href) { // Tell the app... LArray items; items.Add(o); Callback->OnDelete(e->Folder, items); // Remove locally... e->Folder->Items.Delete(o); o->Parent = NULL; delete o; } } break; } default: break; } } void WebdavStore::Post(LDataStoreI *store, void *Param) { if (Lock(_FL)) { Events.Add((WebdavEvent*)Param); // Own the event memory... Unlock(); } if (App && store) App->PostEvent(M_STORAGE_EVENT, store->Id, (LMessage::Param)Param); else LAssert(0); } void WebdavStore::OnNew(LDataFolderI *parent, LArray &new_items, int pos, bool is_new) { } bool WebdavStore::OnDelete(LDataFolderI *parent, LArray &items) { return true; } bool WebdavStore::OnMove(LDataFolderI *new_parent, LDataFolderI *old_parent, LArray &items) { return true; } bool WebdavStore::OnChange(LArray &items, int FieldHint) { return true; } diff --git a/src/Store3Webdav/WebdavStore.h b/src/Store3Webdav/WebdavStore.h --- a/src/Store3Webdav/WebdavStore.h +++ b/src/Store3Webdav/WebdavStore.h @@ -1,352 +1,380 @@ #ifndef _WEBDAV_STORE_H_ #define _WEBDAV_STORE_H_ #include "lgi/common/WebDav.h" #include "Store3CalendarObj.h" ////////////////////////////////////////////////////////////////////// class WebdavFolder; class WebdavCalendar; class WebdavThread; struct WebdavEvent; class WebdavStore : public LDataStoreI, public LMutex { friend class WebdavThread; friend class WebdavCalendar; friend class WebdavContact; public: struct LRemote { LString Name, Url, User, Pass; }; protected: ScribeWnd *App; LRemote Remote; LDataEventsI *Callback; LArray Events; LString ContactUrl, CalUrl; WebdavFolder *Root = NULL, *ContactFolder = NULL, *CalFolder = NULL; LXmlTag *LockSettings(const char *File, int Line); void UnlockSettings(); public: WebdavStore(ScribeWnd *a, LDataEventsI *cb, LString optsPath); ~WebdavStore(); const char *GetClass() override { return "WebdavStore"; } // Actions /* bool Read(); bool Write(); bool Delete(); Calendar *NewEvent(); bool Match(char *Email); bool GetEvents(LDateTime &Start, LDateTime &End, LArray &Events); */ // Events void OnChanged(); // LDataPropI int64 GetInt(int id) override; const char *GetStr(int id) override; LDataPropI *GetObj(int id) override; // LDataStoreI impl uint64 Size() override { return 0; } LDataI *Create(int Type) override; LDataFolderI *GetRoot(bool create = false) override; Store3Status Move(LDataFolderI *NewFolder, LArray &Items) override { return Store3Error; } Store3Status Delete(LArray &Items, bool ToTrash) override; Store3Status Change(LArray &Items, int PropId, LVariant &Value, LOperator Operator) override { return Store3Error; } void Compact(LViewI *Parent, LDataPropI *Props, std::function OnStatus) override { if (OnStatus) OnStatus(true); } void OnEvent(void *Param) override; bool OnIdle() override { return true; } LDataEventsI *GetEvents() override { return NULL; } // LDataEventsI impl void Post(LDataStoreI *store, void *Param); void OnNew(LDataFolderI *parent, LArray &new_items, int pos, bool is_new); bool OnDelete(LDataFolderI *parent, LArray &items); bool OnMove(LDataFolderI *new_parent, LDataFolderI *old_parent, LArray &items); bool OnChange(LArray &items, int FieldHint); }; ///////////////////////////////////////////////////////////////////////////////////// class WebdavObj : public LDataI { friend class WebdavStore; protected: WebdavStore *Store; LString Href; bool Converted; WebdavFolder *Parent; public: WebdavObj(WebdavStore *store) { Store = store; Parent = NULL; } const char *GetClass() override { return "WebdavObj"; } const char *GetHref() { return Href; } virtual void FireOnChange(int Fld) {} virtual bool ConvertToText() { return false; } // LDataI impl uint32_t Type() override { return MAGIC_NONE; } bool IsOnDisk() override { return false; } bool IsOrphan() override { return Store == NULL || Parent == NULL; } LDataStoreI *GetStore() override { return Store; } // Stubs uint64 Size() override { return 0; } Store3Status Save(LDataI *Parent = NULL) override { return Store3NotImpl; } Store3Status Delete(bool ToTrash = true) override { return Store3NotImpl; } LAutoStreamI GetStream(const char *file, int line) override { return LAutoStreamI(NULL); } bool SetStream(LAutoStreamI stream) override { return false; } bool ParseHeaders() override { return false; } LDataPropI *GetObj(int id) override { EmptyVirtual(NULL); } Store3Status SetObj(int id, LDataPropI *i) override { EmptyVirtual(Store3Error); } LDataIt GetList(int id) override { EmptyVirtual(NULL); } Store3Status SetRfc822(LStreamI *Rfc822Msg) override { return Store3Error; } }; class WebdavFld : public LDataPropI { LDataStoreI *Store; WebdavFolder *Parent; int Id, Width; public: WebdavFld(LDataStoreI *s, WebdavFolder *p = NULL, int id = 0, int width = 100); const char *GetClass() override { return "WebdavFld"; } const char *GetStr(int id) override; int64 GetInt(int id) override; Store3Status SetInt(int id, int64 i) override; }; class WebdavFolder : public LDataFolderI { friend class WebdavStore; WebdavFolder &operator =(const WebdavFolder &f) = delete; WebdavStore *Store; WebdavFolder *Parent; LString Name; Store3ItemTypes ItemType = MAGIC_ANY; int64 Sort = 0; Store3State State = Store3Unloaded; public: LString Url; const char *Extension = NULL; DIterator Items; DIterator Sub; DIterator Field; LAutoPtr Thread; WebdavFolder(WebdavStore *store, WebdavFolder *parent = NULL); const char *GetClass() override { return "WebdavFolder"; } LString AllocateAddress(); // LDataPropI impl bool CopyProps(LDataPropI &p) 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; // LDataI impl uint32_t Type() override { return MAGIC_FOLDER; } bool IsOnDisk() override { return false; } bool IsOrphan() override { return false; } uint64 Size() override { return 0; } Store3Status Save(LDataI *Parent = NULL) override; Store3Status Delete(bool ToTrash = true) override; LDataStoreI *GetStore() override { return Store; } LAutoStreamI GetStream(const char *file, int line) override; bool SetStream(LAutoStreamI stream) override { return false; } bool ParseHeaders() override { return false; } // LDataFolderI impl LDataIterator &SubFolders() override; LDataIterator &Children() override; LDataIterator &Fields() override; }; ///////////////////////////////////////////////////////////////////////////////////// #define WebdavCalendarDates() \ _(FIELD_CAL_START_UTC, Start) \ _(FIELD_CAL_END_UTC, End) \ _(FIELD_CAL_RECUR_END_DATE, RecurEnd) \ _(FIELD_CAL_LAST_CHECK, LastCheck) \ _(FIELD_DATE_MODIFIED, DateMod) #define WebdavCalendarInts() \ _(FIELD_CAL_TYPE, ObjType) \ _(FIELD_CAL_COMPLETED, Completed) \ _(FIELD_CAL_SHOW_TIME_AS, ShowTimeAs) \ _(FIELD_CAL_RECUR, Recur) \ _(FIELD_CAL_RECUR_FREQ, RecurFreq) \ _(FIELD_CAL_RECUR_INTERVAL, RecurInterval) \ _(FIELD_CAL_RECUR_END_COUNT, RecurEndCount) \ _(FIELD_CAL_RECUR_END_TYPE, RecurEndType) \ _(FIELD_CAL_RECUR_FILTER_DAYS, RecurFilterDays) \ _(FIELD_CAL_RECUR_FILTER_MONTHS, RecurFilterMonths) \ _(FIELD_CAL_ALL_DAY, AddDay) \ _(FIELD_CAL_PRIVACY, Privacy) \ _(FIELD_COLOUR, Colour) \ _(FIELD_STATUS, StoreStatus) #define WebdavCalendarStrings() \ _(FIELD_UID, Uid) \ _(FIELD_CAL_TIMEZONE, TimeZone) \ _(FIELD_CAL_SUBJECT, Subject) \ _(FIELD_CAL_LOCATION, Location) \ _(FIELD_CAL_REMINDERS, Reminders) \ _(FIELD_CAL_RECUR_FILTER_POS, FilterPos) \ _(FIELD_CAL_RECUR_FILTER_YEARS, FilterYears) \ _(FIELD_CAL_NOTES, Notes) \ _(FIELD_CAL_STATUS, CalStatus) +class WebdavCalendarFile : public Store3CalendarFile +{ + friend class WebdavCalendar; + + WebdavStore *Store = NULL; + WebdavCalendar *Cal = NULL; + +public: + WebdavCalendarFile(WebdavStore *store) : Store(store) + { + } + + const char *GetClass() override { return "WebdavCalendarFile"; } + bool IsDirty() { return Dirty; } + + // Impl LDataI + bool IsOnDisk() override { return true; } + bool IsOrphan() override { return Cal == NULL; } + LDataStoreI *GetStore() override { return Store; } + Store3Status Save(LDataI *Parent = NULL) override; + Store3Status Delete(bool ToTrash = true) override; +}; + class WebdavCalendar : public WebdavObj { + friend class WebdavCalendarFile; + LString vCal, Href; #define _(f,v) LDateTime v; WebdavCalendarDates() #undef _ #define _(f,v) uint64_t v = 0; WebdavCalendarInts() #undef _ #define _(f,v) LString v; WebdavCalendarStrings() #undef _ + DIterator Attachments; + public: WebdavCalendar(WebdavStore *store, WebdavEvent *e); const char *GetClass() override { return "WebdavCalendar"; } void FireOnChange(int Fld) override; bool ConvertToText() override; // Stubs uint32_t Type() override { return MAGIC_CALENDAR; } uint64 Size() override { return vCal.Length(); } // LDataI Impl Store3Status Save(LDataI *Parent = NULL) override; Store3Status Delete(bool ToTrash = true) override; LAutoStreamI GetStream(const char *file, int line) override; bool CopyProps(LDataPropI &p) override; const char *GetStr(int id) override; Store3Status SetStr(int id, const char *str) override; int64 GetInt(int id) override; Store3Status SetInt(int id, int64 i) override; const LDateTime *GetDate(int id) override; Store3Status SetDate(int id, const LDateTime *i) override; + LDataIt GetList(int id) override; }; ///////////////////////////////////////////////////////////////////////////////////// #define WebdavContactDates() \ _(FIELD_DATE_MODIFIED, DateMod) #define WebdavContactVariants() \ _(FIELD_CONTACT_IMAGE, Image) #define WebdavContactInts() \ _(FIELD_STATUS, Status) #define WebdavContactStrings() \ _(FIELD_UID, Uid) \ _(FIELD_TITLE, Title) \ _(FIELD_FIRST_NAME, First) \ _(FIELD_LAST_NAME, Last) \ _(FIELD_EMAIL, Email) \ _(FIELD_ALT_EMAIL, AltEmail) \ _(FIELD_NICK, Nick) \ _(FIELD_SPOUSE, Spouse) \ _(FIELD_NOTE, Notes) \ _(FIELD_TIMEZONE, TimeZone) \ \ _(FIELD_HOME_STREET, HomeStreet) \ _(FIELD_HOME_SUBURB, HomeSuburb) \ _(FIELD_HOME_POSTCODE, HomePostCode) \ _(FIELD_HOME_STATE, HomeState) \ _(FIELD_HOME_COUNTRY, HomeCountry) \ _(FIELD_HOME_PHONE, HomePhone) \ _(FIELD_HOME_MOBILE, HomeMobile) \ _(FIELD_HOME_IM, HomeIM) \ _(FIELD_HOME_FAX, HomeFax) \ _(FIELD_HOME_WEBPAGE, HomeWebpage) \ \ _(FIELD_WORK_STREET, WorkStreet) \ _(FIELD_WORK_SUBURB, WorkSuburb) \ _(FIELD_WORK_POSTCODE, WorkPostCode) \ _(FIELD_WORK_STATE, WorkState) \ _(FIELD_WORK_COUNTRY, WorkCountry) \ _(FIELD_WORK_PHONE, WorkPhone) \ _(FIELD_WORK_MOBILE, WorkMobile) \ _(FIELD_WORK_IM, WorkIM) \ _(FIELD_WORK_FAX, WorkFax) \ _(FIELD_WORK_WEBPAGE, WorkWebpage) \ _(FIELD_COMPANY, Company) \ \ _(FIELD_CONTACT_JSON, JSON) class WebdavContact : public WebdavObj { bool Converted; LString vCard; #define _(f,v) LDateTime v; WebdavContactDates() #undef _ #define _(f,v) LVariant v; WebdavContactVariants() #undef _ #define _(f,v) int64 v = 0; WebdavContactInts() #undef _ #define _(f,v) LString v; WebdavContactStrings() #undef _ public: WebdavContact(WebdavStore *store, WebdavEvent *e); const char *GetClass() override { return "WebdavContact"; } void FireOnChange(int Fld) override; bool ConvertToText() override; // Stubs uint32_t Type() override { return MAGIC_CONTACT; } uint64 Size() override { return vCard.Length(); } // LDataI Impl Store3Status Save(LDataI *Parent = NULL) override; Store3Status Delete(bool ToTrash = true) override; LAutoStreamI GetStream(const char *file, int line) override; bool CopyProps(LDataPropI &p) override; const char *GetStr(int id) override; Store3Status SetStr(int id, const char *str) override; int64 GetInt(int id) override; Store3Status SetInt(int id, int64 i) override; const LDateTime *GetDate(int id) override; Store3Status SetDate(int id, const LDateTime *i) override; const LVariant *GetVar(int id) override; Store3Status SetVar(int id, LVariant *i) override; }; #endif