2626#include  < QtCore/QtDebug> 
2727
2828#include  < fmt/format.h> 
29+ #include  < ios> 
2930#include  < iterator> 
31+ #include  < type_traits> 
3032
3133namespace  Mayo  {
3234
3335namespace  {
3436
35- void  readRecentFile (QDataStream& stream, RecentFile* recentFile)
37+ template <typename  T>
38+ struct  Serializer  {
39+     static  auto  fnType ()
40+     {
41+         if  constexpr (std::is_same_v<T, int >)
42+             return  qint32{};
43+         else  if  constexpr (std::is_same_v<T, unsigned >)
44+             return  quint32{};
45+         else  if  constexpr (std::is_same_v<T, std::int64_t >)
46+             return  qint64{};
47+         else  if  constexpr (std::is_same_v<T, std::uint64_t >)
48+             return  quint64{};
49+         else  if  constexpr (std::is_same_v<T, std::time_t >)
50+             return  qint64{};
51+         else  if  constexpr (std::is_same_v<T, FilePath>)
52+             return  QString{};
53+         else 
54+             return  T{};
55+     }
56+ 
57+     using  StreamType = decltype (Serializer<T>::fnType());
58+ 
59+     static  void  read (QDataStream& stream, T& v)
60+     {
61+         if  constexpr (std::is_same_v<T, FilePath>) {
62+             QString strFilePath;
63+             stream >> strFilePath;
64+             v = filepathFrom (strFilePath);
65+         }
66+         else  {
67+             StreamType qv;
68+             stream >> qv;
69+             v = static_cast <T>(qv);
70+         }
71+     }
72+ 
73+     static  void  write (QDataStream& stream, const  T& v)
74+     {
75+         if  constexpr (std::is_same_v<T, FilePath>) {
76+             stream << filepathTo<QString>(v);
77+         }
78+         else  {
79+             stream << static_cast <StreamType>(v);
80+         }
81+     }
82+ };
83+ 
84+ bool  checkDataStreamStatus (QDataStream::Status status)
3685{
37-     QString strFilepath;
38-     stream >> strFilepath;
39-     if  (stream.status () != QDataStream::Ok)
40-         return ;
86+     if  (status != QDataStream::Ok) {
87+         qDebug () << fmt::format (
88+                         " QDataStream error\n     Function: {}\n     Status: {}" 
89+                         Q_FUNC_INFO, MetaEnum::name (status)
90+                     ).c_str ();
91+         return  false ;
92+     }
4193
42-     recentFile->filepath  = filepathFrom (strFilepath);
43-     stream >> recentFile->thumbnail .imageData ;
44-     if  (stream.status () != QDataStream::Ok)
45-         return ;
94+     return  true ;
95+ }
4696
47-     recentFile->thumbnail .imageCacheKey  = -1 ;
48-     //  Read thumbnail timestamp
49-     //  Warning: qint64 and int64_t may not be the exact same type(eg __int64 and longlong with Windows/MSVC)
50-     qint64 timestamp;
51-     stream >> timestamp;
52-     if  (stream.status () != QDataStream::Ok)
53-         return ;
97+ template <typename  T>
98+ void  dataStreamRead (QDataStream& stream, T& v)
99+ {
100+     Serializer<T>::read (stream, v);
101+     if  (!checkDataStreamStatus (stream.status ()))
102+         throw  std::ios_base::failure (" serialize() error with QDataStream" 
103+ }
104+ 
105+ template <typename  T>
106+ void  dataStreamWrite (QDataStream& stream, T v)
107+ {
108+     Serializer<T>::write (stream, v);
109+ }
110+ 
111+ template <typename  T> void  check_has_filepath_attribute () {
112+     static_assert (
113+         std::is_same_v<decltype (T::filepath), FilePath>,
114+         " Type 'T' must have 'filepath' member attribute of type 'std::filesystem::path'" 
115+     );
116+ }
117+ 
118+ template <typename  RecentItem>
119+ const  RecentItem* findRecentItem (const  FilePath& fp, const  std::vector<RecentItem>& listRecentItem)
120+ {
121+     check_has_filepath_attribute<RecentItem>();
122+     auto  itFound =
123+         std::find_if (
124+             listRecentItem.cbegin (),
125+             listRecentItem.cend (),
126+             [=](const  RecentItem& recentItem) { return  filepathEquivalent (fp, recentItem.filepath ); }
127+         );
128+     return  itFound != listRecentItem.cend () ? &(*itFound) : nullptr ;
129+ }
130+ 
131+ template <typename  RecentItem>
132+ void  prependRecentItem (
133+         const  FilePath& fp,
134+         GenericProperty<std::vector<RecentItem>>& propRecentItems,
135+         std::function<void (RecentItem&)> fnUpdateRecentItem = nullptr
136+     )
137+ {
138+     using  RecentItems = std::vector<RecentItem>;
139+     const  RecentItem* ptrRecentItem = findRecentItem (fp, propRecentItems.value ());
140+     RecentItems newRecentItems = propRecentItems.value ();
141+     if  (ptrRecentItem) {
142+         RecentItem& firstRecentItem = newRecentItems.front ();
143+         RecentItem& recentItem = newRecentItems.at (ptrRecentItem - &propRecentItems.value ().front ());
144+         if  (fnUpdateRecentItem)
145+             fnUpdateRecentItem (recentItem);
146+ 
147+         std::swap (firstRecentItem, recentItem);
148+     }
149+     else  {
150+         RecentItem recentItem;
151+         recentItem.filepath  = fp;
152+         if  (fnUpdateRecentItem)
153+             fnUpdateRecentItem (recentItem);
154+ 
155+         newRecentItems.insert (newRecentItems.begin (), std::move (recentItem));
156+         constexpr  unsigned  sizeLimit = 15 ;
157+         while  (newRecentItems.size () > sizeLimit)
158+             newRecentItems.pop_back ();
159+     }
160+ 
161+     propRecentItems.setValue (newRecentItems);
162+ }
163+ 
164+ template <typename  RecentItem>
165+ Settings::Variant recentItemsToVariant (const  std::vector<RecentItem>& recentItems)
166+ {
167+     QByteArray blob;
168+     QDataStream stream (&blob, QIODevice::WriteOnly);
169+     AppModule::write (stream, recentItems);
170+     Settings::Variant varBlob (blob.toStdString ());
171+     varBlob.setByteArray (true );
172+     return  varBlob;
173+ }
54174
55-     recentFile->thumbnailTimestamp  = timestamp;
175+ template <typename  RecentItem>
176+ std::vector<RecentItem> recentItemsFromVariant (const  Settings::Variant& variant, bool * ok = nullptr )
177+ {
178+     const  QByteArray blob = QtCoreUtils::QByteArray_frowRawData (variant.toConstRefString ());
179+     QDataStream stream (blob);
180+     std::vector<RecentItem> recentItems;
181+     AppModule::read (stream, &recentItems);
182+     if  (ok)
183+         *ok = stream.status () == QDataStream::Ok;
184+ 
185+     return  recentItems;
56186}
57187
58188QuantityLength shapeChordalDeflection (const  TopoDS_Shape& shape)
@@ -162,12 +292,11 @@ Settings::Variant AppModule::toVariant(const Property& prop) const
162292{
163293    if  (isType<PropertyRecentFiles>(prop)) {
164294        const  auto & filesProp = constRef<PropertyRecentFiles>(prop);
165-         QByteArray blob;
166-         QDataStream stream (&blob, QIODevice::WriteOnly);
167-         AppModule::writeRecentFiles (stream, filesProp.value ());
168-         Variant varBlob (blob.toStdString ());
169-         varBlob.setByteArray (true );
170-         return  varBlob;
295+         return  recentItemsToVariant (filesProp.value ());
296+     }
297+     else  if  (isType<PropertyRecentScripts>(prop)) {
298+         const  auto & scriptsProp = constRef<PropertyRecentScripts>(prop);
299+         return  recentItemsToVariant (scriptsProp.value ());
171300    }
172301    else  {
173302        return  PropertyValueConversion::toVariant (prop);
@@ -176,17 +305,20 @@ Settings::Variant AppModule::toVariant(const Property& prop) const
176305
177306bool  AppModule::fromVariant (Property* prop, const  Settings::Variant& variant) const 
178307{
308+     bool  ok = false ;
179309    if  (isType<PropertyRecentFiles>(prop)) {
180-         const  QByteArray blob = QtCoreUtils::QByteArray_frowRawData (variant.toConstRefString ());
181-         QDataStream stream (blob);
182-         RecentFiles recentFiles;
183-         AppModule::readRecentFiles (stream, &recentFiles);
310+         auto  recentFiles = recentItemsFromVariant<RecentFile>(variant, &ok);
184311        ptr<PropertyRecentFiles>(prop)->setValue (recentFiles);
185-         return  stream.status () == QDataStream::Ok;
312+     }
313+     else  if  (isType<PropertyRecentScripts>(prop)) {
314+         auto  recentScripts = recentItemsFromVariant<RecentScript>(variant, &ok);
315+         ptr<PropertyRecentScripts>(prop)->setValue (recentScripts);
186316    }
187317    else  {
188-         return  PropertyValueConversion::fromVariant (prop, variant);
318+         ok =  PropertyValueConversion::fromVariant (prop, variant);
189319    }
320+ 
321+     return  ok;
190322}
191323
192324void  AppModule::emitMessage (MessageType msgType, std::string_view text)
@@ -210,38 +342,17 @@ void AppModule::clearMessageLog()
210342    this ->signalMessageLogCleared .send ();
211343}
212344
213- void  AppModule::prependRecentFile (const  FilePath& fp)
345+ void  AppModule::prependRecentFile (const  FilePath& fp, GuiDocument* guiDoc )
214346{
215-     const  RecentFile* ptrRecentFile = this ->findRecentFile (fp);
216-     RecentFiles newRecentFiles = m_props.recentFiles .value ();
217-     if  (ptrRecentFile) {
218-         RecentFile& firstRecentFile = newRecentFiles.front ();
219-         RecentFile& recentFile = newRecentFiles.at (ptrRecentFile - &m_props.recentFiles .value ().front ());
220-         std::swap (firstRecentFile, recentFile);
221-     }
222-     else  {
223-         RecentFile recentFile;
224-         recentFile.filepath  = fp;
225-         newRecentFiles.insert (newRecentFiles.begin (), std::move (recentFile));
226-         constexpr  int  sizeLimit = 15 ;
227-         while  (newRecentFiles.size () > sizeLimit)
228-             newRecentFiles.pop_back ();
229-     }
230- 
231-     m_props.recentFiles .setValue (newRecentFiles);
347+     auto  fnUpdate = [=](RecentFile& recent) {
348+         this ->impl_recordRecentFile (&recent, guiDoc);
349+     };
350+     prependRecentItem<RecentFile>(fp, m_props.recentFiles , fnUpdate);
232351}
233352
234353const  RecentFile* AppModule::findRecentFile (const  FilePath& fp) const 
235354{
236-     const  RecentFiles& listRecentFile = m_props.recentFiles .value ();
237-     auto  itFound =
238-             std::find_if (
239-                 listRecentFile.cbegin (),
240-                 listRecentFile.cend (),
241-                 [=](const  RecentFile& recentFile) {
242-         return  filepathEquivalent (fp, recentFile.filepath );
243-     });
244-     return  itFound != listRecentFile.cend () ? &(*itFound) : nullptr ;
355+     return  findRecentItem (fp, m_props.recentFiles .value ());
245356}
246357
247358void  AppModule::recordRecentFile (GuiDocument* guiDoc)
@@ -303,45 +414,80 @@ void AppModule::setRecentFileThumbnailRecorder(std::function<Thumbnail(GuiDocume
303414    m_fnRecentFileThumbnailRecorder = std::move (fn);
304415}
305416
306- 
307- void  AppModule::readRecentFiles (QDataStream& stream, RecentFiles* recentFiles)
417+ void  AppModule::read (QDataStream& stream, RecentFiles* recentFiles)
308418{
309-     auto  fnCheckStreamStatus = [](QDataStream::Status status) {
310-         if  (status != QDataStream::Ok) {
311-             qDebug () << fmt::format (
312-                             " QDataStream error\n     Function: {}\n     Status: {}" 
313-                             Q_FUNC_INFO, MetaEnum::name (status)
314-                         ).c_str ();
315-             return  false ;
419+     try  {
420+         unsigned  count = 0 ;
421+         dataStreamRead (stream, count);
422+         recentFiles->clear ();
423+         for  (unsigned  i = 0 ; i < count; ++i) {
424+             RecentFile recent;
425+             dataStreamRead (stream, recent.filepath );
426+             dataStreamRead (stream, recent.thumbnail .imageData );
427+             recent.thumbnail .imageCacheKey  = -1 ;
428+             //  Read thumbnail timestamp
429+             //  Warning: qint64 and int64_t may not be the exact same type(eg __int64 and longlong with Windows/MSVC)
430+             dataStreamRead (stream, recent.thumbnailTimestamp );
431+             if  (!recent.filepath .empty () && recent.thumbnailTimestamp  != 0 )
432+                 recentFiles->push_back (std::move (recent));
316433        }
434+     }
435+     catch  (const  std::exception&) {
436+     }
437+ }
317438
318-         return  true ;
319-     };
439+ void  AppModule::write (QDataStream& stream, const  RecentFiles& recentFiles)
440+ {
441+     dataStreamWrite (stream, unsigned (recentFiles.size ()));
442+     for  (const  RecentFile& rf : recentFiles) {
443+         dataStreamWrite (stream, rf.filepath );
444+         dataStreamWrite (stream, rf.thumbnail .imageData );
445+         dataStreamWrite (stream, rf.thumbnailTimestamp );
446+     }
447+ }
320448
321-     uint32_t  count = 0 ;
322-     stream >> count;
323-     if  (!fnCheckStreamStatus (stream.status ()))
324-         return ; //  Stream extraction error, abort
449+ void  AppModule::prependRecentScript (const  FilePath& fp)
450+ {
451+     auto  fnUpdate = [](RecentScript& recent) {
452+         ++recent.executionCount ;
453+         std::timespec ts;
454+         std::timespec_get (&ts, TIME_UTC);
455+         recent.lastExecutionDateTime  = ts.tv_sec ;
456+     };
457+     prependRecentItem<RecentScript>(fp, m_props.recentScripts , fnUpdate);
458+ }
325459
326-     recentFiles->clear ();
327-     for  (uint32_t  i = 0 ; i < count; ++i) {
328-         RecentFile recent;
329-         readRecentFile (stream, &recent);
330-         if  (!fnCheckStreamStatus (stream.status ()))
331-             return ; //  Stream extraction error, abort
460+ const  RecentScript* AppModule::findRecentScript (const  FilePath& fp) const 
461+ {
462+     return  findRecentItem (fp, m_props.recentScripts .value ());
463+ }
332464
333-         if  (!recent.filepath .empty () && recent.thumbnailTimestamp  != 0 )
334-             recentFiles->push_back (std::move (recent));
465+ void  AppModule::read (QDataStream& stream, RecentScripts* recentScripts)
466+ {
467+     try  {
468+         unsigned  count = 0 ;
469+         dataStreamRead (stream, count);
470+         recentScripts->clear ();
471+         for  (unsigned  i = 0 ; i < count; ++i) {
472+             RecentScript recent;
473+             dataStreamRead (stream, recent.filepath );
474+             dataStreamRead (stream, recent.executionCount );
475+             dataStreamRead (stream, recent.lastExecutionDateTime );
476+             if  (!recent.filepath .empty ())
477+                 recentScripts->push_back (std::move (recent));
478+         }
479+     }
480+     catch  (const  std::exception&) {
335481    }
336482}
337483
338- void  AppModule::writeRecentFiles  (QDataStream& stream, const  RecentFiles& recentFiles )
484+ void  AppModule::write  (QDataStream& stream, const  RecentScripts& recentScripts )
339485{
340-     stream <<  uint32_t (recentFiles .size ());
341-     for  (const  RecentFile& rf  : recentFiles ) {
342-         stream << filepathTo<QString>(rf .filepath );
343-         stream << rf. thumbnail . imageData ;
344-         stream <<  qint64 (rf. thumbnailTimestamp );
486+     dataStreamWrite ( stream,  unsigned (recentScripts .size () ));
487+     for  (const  RecentScript& rs  : recentScripts ) {
488+         dataStreamWrite ( stream, rs .filepath );
489+         dataStreamWrite ( stream, rs. executionCount ) ;
490+         dataStreamWrite ( stream, rs. lastExecutionDateTime );
345491    }
346492}
347493
0 commit comments