diff options
author | Christian Mollekopf <chrigi_1@fastmail.fm> | 2018-01-25 16:29:00 +0100 |
---|---|---|
committer | Christian Mollekopf <chrigi_1@fastmail.fm> | 2018-02-06 08:38:08 +0100 |
commit | 9b84aff4b68c3cef3328c85ac12418048b169cee (patch) | |
tree | 7014f685e6a8c850f0be7965e1e656d3de72a15d /examples | |
parent | 0a0c197ed487c343675b62dff8456932c8d5ff7f (diff) | |
download | sink-9b84aff4b68c3cef3328c85ac12418048b169cee.tar.gz sink-9b84aff4b68c3cef3328c85ac12418048b169cee.zip |
Store all BLOB properties inline.
BLOB properties had a couple of intended purposes:
* Allow large payloads to be streamed directly to disk, and then be
handled by reference.
* Allow zero-copy handling.
* Keep the database values compact so we can avoid traversing large
BLOBS.
However, they came at the cost of code-complexity, and we lost all the
benefits of our storage layer, such as transactions.
Measurements showed, that for email (the intended primary usecase),
the overhead is hardly measurable, with most parts performing
better, or at least not worse. We additionally also gain file-system
independence, which may help on other platforms.
The biggest drawback is probably that large payloads need to be written
to disk twice, because of the synchronizer queue (once for the queue,
once for the actual data).
Diffstat (limited to 'examples')
-rw-r--r-- | examples/maildirresource/facade.cpp | 7 | ||||
-rw-r--r-- | examples/maildirresource/maildirresource.cpp | 90 |
2 files changed, 65 insertions, 32 deletions
diff --git a/examples/maildirresource/facade.cpp b/examples/maildirresource/facade.cpp index 5ea3d98..ea02968 100644 --- a/examples/maildirresource/facade.cpp +++ b/examples/maildirresource/facade.cpp | |||
@@ -31,7 +31,7 @@ MaildirResourceMailFacade::MaildirResourceMailFacade(const Sink::ResourceContext | |||
31 | Sink::Log::Context ctx{"maildirfacade"}; | 31 | Sink::Log::Context ctx{"maildirfacade"}; |
32 | if (value.hasProperty(Sink::ApplicationDomain::Mail::MimeMessage::name)) { | 32 | if (value.hasProperty(Sink::ApplicationDomain::Mail::MimeMessage::name)) { |
33 | auto mail = Sink::ApplicationDomain::Mail{value}; | 33 | auto mail = Sink::ApplicationDomain::Mail{value}; |
34 | const auto mimeMessage = mail.getMimeMessagePath(); | 34 | const auto mimeMessage = mail.getMimeMessage(); |
35 | //Transform the mime message property into the actual path on disk. | 35 | //Transform the mime message property into the actual path on disk. |
36 | auto parts = mimeMessage.split('/'); | 36 | auto parts = mimeMessage.split('/'); |
37 | auto key = parts.takeLast(); | 37 | auto key = parts.takeLast(); |
@@ -45,7 +45,10 @@ MaildirResourceMailFacade::MaildirResourceMailFacade(const Sink::ResourceContext | |||
45 | SinkErrorCtx(ctx) << "Failed to find message. Directory: " << path << "Key: " << key << "Number of matching files: " << list.size(); | 45 | SinkErrorCtx(ctx) << "Failed to find message. Directory: " << path << "Key: " << key << "Number of matching files: " << list.size(); |
46 | mail.setProperty(Sink::ApplicationDomain::Mail::MimeMessage::name, QVariant()); | 46 | mail.setProperty(Sink::ApplicationDomain::Mail::MimeMessage::name, QVariant()); |
47 | } else { | 47 | } else { |
48 | mail.setMimeMessagePath(list.at(0).filePath()); | 48 | QFile file{list.at(0).filePath()}; |
49 | if (file.open(QIODevice::ReadOnly)) { | ||
50 | mail.setMimeMessage(file.readAll()); | ||
51 | } | ||
49 | } | 52 | } |
50 | } | 53 | } |
51 | value.setChangedProperties(QSet<QByteArray>()); | 54 | value.setChangedProperties(QSet<QByteArray>()); |
diff --git a/examples/maildirresource/maildirresource.cpp b/examples/maildirresource/maildirresource.cpp index 735665a..4c94d1d 100644 --- a/examples/maildirresource/maildirresource.cpp +++ b/examples/maildirresource/maildirresource.cpp | |||
@@ -54,8 +54,7 @@ static QString getFilePathFromMimeMessagePath(const QString &mimeMessagePath) | |||
54 | QDir dir(path); | 54 | QDir dir(path); |
55 | const QFileInfoList list = dir.entryInfoList(QStringList() << (key+"*"), QDir::Files); | 55 | const QFileInfoList list = dir.entryInfoList(QStringList() << (key+"*"), QDir::Files); |
56 | if (list.size() != 1) { | 56 | if (list.size() != 1) { |
57 | SinkWarning() << "Failed to find message " << mimeMessagePath; | 57 | SinkWarning() << "Failed to find message. Property value:" << mimeMessagePath << "Assembled path: " << path; |
58 | SinkWarning() << "Failed to find message " << path; | ||
59 | return QString(); | 58 | return QString(); |
60 | } | 59 | } |
61 | return list.first().filePath(); | 60 | return list.first().filePath(); |
@@ -63,10 +62,25 @@ static QString getFilePathFromMimeMessagePath(const QString &mimeMessagePath) | |||
63 | 62 | ||
64 | class MaildirMailPropertyExtractor : public MailPropertyExtractor | 63 | class MaildirMailPropertyExtractor : public MailPropertyExtractor |
65 | { | 64 | { |
65 | void update(Sink::ApplicationDomain::Mail &mail) | ||
66 | { | ||
67 | QFile file{::getFilePathFromMimeMessagePath(mail.getMimeMessage())}; | ||
68 | if (file.open(QIODevice::ReadOnly)) { | ||
69 | updatedIndexedProperties(mail, file.readAll()); | ||
70 | } else { | ||
71 | SinkWarning() << "Failed to open file message " << mail.getMimeMessage(); | ||
72 | } | ||
73 | } | ||
74 | |||
66 | protected: | 75 | protected: |
67 | virtual QString getFilePathFromMimeMessagePath(const QString &mimeMessagePath) const Q_DECL_OVERRIDE | 76 | void newEntity(Sink::ApplicationDomain::Mail &mail) Q_DECL_OVERRIDE |
68 | { | 77 | { |
69 | return ::getFilePathFromMimeMessagePath(mimeMessagePath); | 78 | update(mail); |
79 | } | ||
80 | |||
81 | void modifiedEntity(const Sink::ApplicationDomain::Mail &oldMail, Sink::ApplicationDomain::Mail &newMail) Q_DECL_OVERRIDE | ||
82 | { | ||
83 | update(newMail); | ||
70 | } | 84 | } |
71 | }; | 85 | }; |
72 | 86 | ||
@@ -92,6 +106,18 @@ public: | |||
92 | return folderPath; | 106 | return folderPath; |
93 | } | 107 | } |
94 | 108 | ||
109 | QString storeMessage(const QByteArray &data, const QByteArray &folder) | ||
110 | { | ||
111 | const auto path = getPath(folder); | ||
112 | KPIM::Maildir maildir(path, false); | ||
113 | if (!maildir.isValid(true)) { | ||
114 | SinkWarning() << "Maildir is not existing: " << path; | ||
115 | } | ||
116 | SinkWarning() << "Storing message: " << data; | ||
117 | auto identifier = maildir.addEntry(data); | ||
118 | return path + "/" + identifier; | ||
119 | } | ||
120 | |||
95 | QString moveMessage(const QString &oldPath, const QByteArray &folder) | 121 | QString moveMessage(const QString &oldPath, const QByteArray &folder) |
96 | { | 122 | { |
97 | if (oldPath.startsWith(Sink::temporaryFileLocation())) { | 123 | if (oldPath.startsWith(Sink::temporaryFileLocation())) { |
@@ -125,15 +151,21 @@ public: | |||
125 | } | 151 | } |
126 | } | 152 | } |
127 | 153 | ||
154 | bool isPath(const QByteArray &data) | ||
155 | { | ||
156 | return data.startsWith('/'); | ||
157 | } | ||
158 | |||
128 | void newEntity(Sink::ApplicationDomain::ApplicationDomainType &newEntity) Q_DECL_OVERRIDE | 159 | void newEntity(Sink::ApplicationDomain::ApplicationDomainType &newEntity) Q_DECL_OVERRIDE |
129 | { | 160 | { |
130 | auto mail = newEntity.cast<ApplicationDomain::Mail>(); | 161 | auto mail = newEntity.cast<ApplicationDomain::Mail>(); |
131 | const auto mimeMessage = mail.getMimeMessagePath(); | 162 | const auto mimeMessage = mail.getMimeMessage(); |
132 | if (!mimeMessage.isNull()) { | 163 | if (!mimeMessage.isNull()) { |
133 | const auto path = moveMessage(mimeMessage, mail.getFolder()); | 164 | if (isPath(mimeMessage)) { |
134 | auto blob = ApplicationDomain::BLOB{path}; | 165 | mail.setMimeMessage(moveMessage(mimeMessage, mail.getFolder()).toUtf8()); |
135 | blob.isExternal = false; | 166 | } else { |
136 | mail.setProperty(ApplicationDomain::Mail::MimeMessage::name, QVariant::fromValue(blob)); | 167 | mail.setMimeMessage(storeMessage(mimeMessage, mail.getFolder()).toUtf8()); |
168 | } | ||
137 | } | 169 | } |
138 | } | 170 | } |
139 | 171 | ||
@@ -141,28 +173,28 @@ public: | |||
141 | { | 173 | { |
142 | auto newMail = newEntity.cast<ApplicationDomain::Mail>(); | 174 | auto newMail = newEntity.cast<ApplicationDomain::Mail>(); |
143 | const ApplicationDomain::Mail oldMail{oldEntity}; | 175 | const ApplicationDomain::Mail oldMail{oldEntity}; |
144 | const auto mimeMessage = newMail.getMimeMessagePath(); | ||
145 | const auto newFolder = newMail.getFolder(); | 176 | const auto newFolder = newMail.getFolder(); |
146 | const bool mimeMessageChanged = !mimeMessage.isNull() && mimeMessage != oldMail.getMimeMessagePath(); | ||
147 | const bool folderChanged = !newFolder.isNull() && newFolder != oldMail.getFolder(); | 177 | const bool folderChanged = !newFolder.isNull() && newFolder != oldMail.getFolder(); |
148 | if (mimeMessageChanged || folderChanged) { | 178 | if (!newMail.getMimeMessage().isNull() || folderChanged) { |
149 | SinkTrace() << "Moving mime message: " << mimeMessageChanged << folderChanged; | 179 | const auto data = newMail.getMimeMessage(); |
150 | auto newPath = moveMessage(mimeMessage, newMail.getFolder()); | 180 | if (isPath(data)) { |
151 | if (newPath != oldMail.getMimeMessagePath()) { | 181 | auto newPath = moveMessage(data, newMail.getFolder()); |
152 | const auto oldPath = getFilePathFromMimeMessagePath(oldMail.getMimeMessagePath()); | 182 | if (newPath != oldMail.getMimeMessage()) { |
153 | auto blob = ApplicationDomain::BLOB{newPath}; | 183 | newMail.setMimeMessage(newPath.toUtf8()); |
154 | blob.isExternal = false; | 184 | //Remove the olde mime message if there is a new one |
155 | newMail.setProperty(ApplicationDomain::Mail::MimeMessage::name, QVariant::fromValue(blob)); | 185 | QFile::remove(getFilePathFromMimeMessagePath(oldMail.getMimeMessage())); |
186 | } | ||
187 | } else { | ||
188 | newMail.setMimeMessage(storeMessage(data, newMail.getFolder()).toUtf8()); | ||
156 | //Remove the olde mime message if there is a new one | 189 | //Remove the olde mime message if there is a new one |
157 | QFile::remove(oldPath); | 190 | QFile::remove(getFilePathFromMimeMessagePath(oldMail.getMimeMessage())); |
158 | } | 191 | } |
159 | } | 192 | } |
160 | 193 | ||
161 | auto mimeMessagePath = newMail.getMimeMessagePath(); | 194 | auto mimeMessagePath = newMail.getMimeMessage(); |
162 | const auto maildirPath = getPath(newMail.getFolder()); | 195 | const auto maildirPath = getPath(newMail.getFolder()); |
163 | KPIM::Maildir maildir(maildirPath, false); | 196 | KPIM::Maildir maildir(maildirPath, false); |
164 | const auto file = getFilePathFromMimeMessagePath(mimeMessagePath); | 197 | QString identifier = KPIM::Maildir::getKeyFromFile(getFilePathFromMimeMessagePath(mimeMessagePath)); |
165 | QString identifier = KPIM::Maildir::getKeyFromFile(file); | ||
166 | 198 | ||
167 | //get flags from | 199 | //get flags from |
168 | KPIM::Maildir::Flags flags; | 200 | KPIM::Maildir::Flags flags; |
@@ -179,7 +211,7 @@ public: | |||
179 | void deletedEntity(const Sink::ApplicationDomain::ApplicationDomainType &oldEntity) Q_DECL_OVERRIDE | 211 | void deletedEntity(const Sink::ApplicationDomain::ApplicationDomainType &oldEntity) Q_DECL_OVERRIDE |
180 | { | 212 | { |
181 | const ApplicationDomain::Mail oldMail{oldEntity}; | 213 | const ApplicationDomain::Mail oldMail{oldEntity}; |
182 | const auto filePath = getFilePathFromMimeMessagePath(oldMail.getMimeMessagePath()); | 214 | const auto filePath = getFilePathFromMimeMessagePath(oldMail.getMimeMessage()); |
183 | QFile::remove(filePath); | 215 | QFile::remove(filePath); |
184 | } | 216 | } |
185 | QByteArray mResourceInstanceIdentifier; | 217 | QByteArray mResourceInstanceIdentifier; |
@@ -327,9 +359,7 @@ public: | |||
327 | mail.setFolder(folderLocalId); | 359 | mail.setFolder(folderLocalId); |
328 | //We only store the directory path + key, so we facade can add the changing bits (flags) | 360 | //We only store the directory path + key, so we facade can add the changing bits (flags) |
329 | auto path = KPIM::Maildir::getDirectoryFromFile(filePath) + maildirKey; | 361 | auto path = KPIM::Maildir::getDirectoryFromFile(filePath) + maildirKey; |
330 | auto blob = ApplicationDomain::BLOB{path}; | 362 | mail.setMimeMessage(path.toUtf8()); |
331 | blob.isExternal = false; | ||
332 | mail.setProperty(ApplicationDomain::Mail::MimeMessage::name, QVariant::fromValue(blob)); | ||
333 | mail.setUnread(!flags.testFlag(KPIM::Maildir::Seen)); | 363 | mail.setUnread(!flags.testFlag(KPIM::Maildir::Seen)); |
334 | mail.setImportant(flags.testFlag(KPIM::Maildir::Flagged)); | 364 | mail.setImportant(flags.testFlag(KPIM::Maildir::Flagged)); |
335 | mail.setExtractedFullPayloadAvailable(true); | 365 | mail.setExtractedFullPayloadAvailable(true); |
@@ -396,7 +426,7 @@ public: | |||
396 | KAsync::Job<QByteArray> replay(const ApplicationDomain::Mail &mail, Sink::Operation operation, const QByteArray &oldRemoteId, const QList<QByteArray> &changedProperties) Q_DECL_OVERRIDE | 426 | KAsync::Job<QByteArray> replay(const ApplicationDomain::Mail &mail, Sink::Operation operation, const QByteArray &oldRemoteId, const QList<QByteArray> &changedProperties) Q_DECL_OVERRIDE |
397 | { | 427 | { |
398 | if (operation == Sink::Operation_Creation) { | 428 | if (operation == Sink::Operation_Creation) { |
399 | const auto remoteId = getFilePathFromMimeMessagePath(mail.getMimeMessagePath()); | 429 | const auto remoteId = getFilePathFromMimeMessagePath(mail.getMimeMessage()); |
400 | SinkTrace() << "Mail created: " << remoteId; | 430 | SinkTrace() << "Mail created: " << remoteId; |
401 | return KAsync::value(remoteId.toUtf8()); | 431 | return KAsync::value(remoteId.toUtf8()); |
402 | } else if (operation == Sink::Operation_Removal) { | 432 | } else if (operation == Sink::Operation_Removal) { |
@@ -404,7 +434,7 @@ public: | |||
404 | return KAsync::null<QByteArray>(); | 434 | return KAsync::null<QByteArray>(); |
405 | } else if (operation == Sink::Operation_Modification) { | 435 | } else if (operation == Sink::Operation_Modification) { |
406 | SinkTrace() << "Modifying a mail: " << oldRemoteId; | 436 | SinkTrace() << "Modifying a mail: " << oldRemoteId; |
407 | const auto remoteId = getFilePathFromMimeMessagePath(mail.getMimeMessagePath()); | 437 | const auto remoteId = getFilePathFromMimeMessagePath(mail.getMimeMessage()); |
408 | return KAsync::value(remoteId.toUtf8()); | 438 | return KAsync::value(remoteId.toUtf8()); |
409 | } | 439 | } |
410 | return KAsync::null<QByteArray>(); | 440 | return KAsync::null<QByteArray>(); |
@@ -460,7 +490,7 @@ protected: | |||
460 | 490 | ||
461 | if (domainType == ENTITY_TYPE_MAIL) { | 491 | if (domainType == ENTITY_TYPE_MAIL) { |
462 | auto mail = entityStore.readLatest<Sink::ApplicationDomain::Mail>(entityId); | 492 | auto mail = entityStore.readLatest<Sink::ApplicationDomain::Mail>(entityId); |
463 | const auto filePath = getFilePathFromMimeMessagePath(mail.getMimeMessagePath()); | 493 | const auto filePath = getFilePathFromMimeMessagePath(mail.getMimeMessage()); |
464 | 494 | ||
465 | if (inspectionType == Sink::ResourceControl::Inspection::PropertyInspectionType) { | 495 | if (inspectionType == Sink::ResourceControl::Inspection::PropertyInspectionType) { |
466 | if (property == "unread") { | 496 | if (property == "unread") { |