Merge lp:~mzanetti/reminders-app/jobqueue-tweaks into lp:reminders-app
- jobqueue-tweaks
- Merge into trunk
Proposed by
Michael Zanetti
Status: | Merged |
---|---|
Approved by: | Riccardo Padovani |
Approved revision: | 353 |
Merged at revision: | 355 |
Proposed branch: | lp:~mzanetti/reminders-app/jobqueue-tweaks |
Merge into: | lp:reminders-app |
Prerequisite: | lp:~mzanetti/reminders-app/update-thrift |
Diff against target: |
420 lines (+98/-35) 13 files modified
src/libqtevernote/evernoteconnection.cpp (+10/-4) src/libqtevernote/evernoteconnection.h (+1/-6) src/libqtevernote/jobs/evernotejob.cpp (+18/-2) src/libqtevernote/jobs/evernotejob.h (+12/-1) src/libqtevernote/jobs/fetchnotejob.cpp (+8/-0) src/libqtevernote/jobs/fetchnotejob.h (+1/-0) src/libqtevernote/jobs/fetchnotesjob.cpp (+10/-0) src/libqtevernote/jobs/fetchnotesjob.h (+1/-0) src/libqtevernote/jobs/notesstorejob.cpp (+9/-7) src/libqtevernote/note.cpp (+14/-5) src/libqtevernote/note.h (+3/-2) src/libqtevernote/notesstore.cpp (+10/-7) src/libqtevernote/notesstore.h (+1/-1) |
To merge this branch: | bzr merge lp:~mzanetti/reminders-app/jobqueue-tweaks |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Riccardo Padovani | Approve | ||
Ubuntu Phone Apps Jenkins Bot | continuous-integration | Approve | |
Review via email: mp+251188@code.launchpad.net |
Commit message
Tweak the jobqueue
better prioritze requests that are triggered by a user interaction.
Description of the change
To post a comment you must log in.
Revision history for this message
Ubuntu Phone Apps Jenkins Bot (ubuntu-phone-apps-jenkins-bot) wrote : | # |
review:
Approve
(continuous-integration)
- 353. By Michael Zanetti
-
more tuning the jobqueue
Revision history for this message
Ubuntu Phone Apps Jenkins Bot (ubuntu-phone-apps-jenkins-bot) wrote : | # |
PASSED: Continuous integration, rev:353
http://
Executed test runs:
SUCCESS: http://
deb: http://
SUCCESS: http://
SUCCESS: http://
Click here to trigger a rebuild:
http://
review:
Approve
(continuous-integration)
Revision history for this message
Riccardo Padovani (rpadovani) wrote : | # |
Tested, it compiles and seems to work
review:
Approve
Preview Diff
[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1 | === modified file 'src/libqtevernote/evernoteconnection.cpp' |
2 | --- src/libqtevernote/evernoteconnection.cpp 2015-02-26 00:59:08 +0000 |
3 | +++ src/libqtevernote/evernoteconnection.cpp 2015-02-27 00:43:31 +0000 |
4 | @@ -351,10 +351,10 @@ |
5 | return nullptr; |
6 | } |
7 | |
8 | -void EvernoteConnection::enqueue(EvernoteJob *job, JobPriority priority) |
9 | +void EvernoteConnection::enqueue(EvernoteJob *job) |
10 | { |
11 | if (!isConnected()) { |
12 | - qWarning() << "Not connected to evernote. Can't enqueue job."; |
13 | + qWarning() << "[JobQueue] Not connected to evernote. Can't enqueue job."; |
14 | job->emitJobDone(ErrorCodeConnectionLost, gettext("Disconnected from Evernote.")); |
15 | job->deleteLater(); |
16 | return; |
17 | @@ -364,15 +364,20 @@ |
18 | job->attachToDuplicate(duplicate); |
19 | connect(duplicate, &EvernoteJob::finished, job, &EvernoteJob::deleteLater); |
20 | // reprioritze the repeated request |
21 | - if (priority == JobPriorityHigh) { |
22 | + qDebug() << "[JobQueue] Duplicate job already queued:" << job->toString(); |
23 | + if (job->jobPriority() == EvernoteJob::JobPriorityHigh) { |
24 | + qDebug() << "[JobQueue] Reprioritising duplicate job:" << job->toString(); |
25 | + duplicate->setJobPriority(job->jobPriority()); |
26 | m_jobQueue.prepend(m_jobQueue.takeAt(m_jobQueue.indexOf(duplicate))); |
27 | } |
28 | } else { |
29 | connect(job, &EvernoteJob::finished, job, &EvernoteJob::deleteLater); |
30 | connect(job, &EvernoteJob::finished, this, &EvernoteConnection::startNextJob); |
31 | - if (priority == JobPriorityHigh) { |
32 | + if (job->jobPriority() == EvernoteJob::JobPriorityHigh) { |
33 | + qDebug() << "[JobQueue] Prepending high priority job request:" << job->toString(); |
34 | m_jobQueue.prepend(job); |
35 | } else { |
36 | + qDebug() << "[JobQueue] Appending low priority job request:" << job->toString(); |
37 | m_jobQueue.append(job); |
38 | } |
39 | startJobQueue(); |
40 | @@ -405,6 +410,7 @@ |
41 | } |
42 | |
43 | m_currentJob = m_jobQueue.takeFirst(); |
44 | + qDebug() << "[JobQueue] Starting job:" << m_currentJob->toString(); |
45 | m_currentJob->start(); |
46 | } |
47 | |
48 | |
49 | === modified file 'src/libqtevernote/evernoteconnection.h' |
50 | --- src/libqtevernote/evernoteconnection.h 2014-12-08 10:25:48 +0000 |
51 | +++ src/libqtevernote/evernoteconnection.h 2015-02-27 00:43:31 +0000 |
52 | @@ -64,11 +64,6 @@ |
53 | ErrorCodeQutaExceeded |
54 | }; |
55 | |
56 | - enum JobPriority { |
57 | - JobPriorityHigh, |
58 | - JobPriorityLow |
59 | - }; |
60 | - |
61 | static EvernoteConnection* instance(); |
62 | ~EvernoteConnection(); |
63 | |
64 | @@ -78,7 +73,7 @@ |
65 | QString token() const; |
66 | void setToken(const QString &token); |
67 | |
68 | - void enqueue(EvernoteJob *job, JobPriority priority = JobPriorityHigh); |
69 | + void enqueue(EvernoteJob *job); |
70 | |
71 | bool isConnected() const; |
72 | |
73 | |
74 | === modified file 'src/libqtevernote/jobs/evernotejob.cpp' |
75 | --- src/libqtevernote/jobs/evernotejob.cpp 2014-12-16 21:01:28 +0000 |
76 | +++ src/libqtevernote/jobs/evernotejob.cpp 2015-02-27 00:43:31 +0000 |
77 | @@ -39,9 +39,10 @@ |
78 | using namespace apache::thrift::protocol; |
79 | using namespace apache::thrift::transport; |
80 | |
81 | -EvernoteJob::EvernoteJob(QObject *parent) : |
82 | +EvernoteJob::EvernoteJob(QObject *parent, JobPriority jobPriority) : |
83 | QThread(parent), |
84 | - m_token(EvernoteConnection::instance()->token()) |
85 | + m_token(EvernoteConnection::instance()->token()), |
86 | + m_jobPriority(jobPriority) |
87 | { |
88 | } |
89 | |
90 | @@ -49,6 +50,16 @@ |
91 | { |
92 | } |
93 | |
94 | +EvernoteJob::JobPriority EvernoteJob::jobPriority() const |
95 | +{ |
96 | + return m_jobPriority; |
97 | +} |
98 | + |
99 | +void EvernoteJob::setJobPriority(EvernoteJob::JobPriority priority) |
100 | +{ |
101 | + m_jobPriority = priority; |
102 | +} |
103 | + |
104 | void EvernoteJob::run() |
105 | { |
106 | if (!EvernoteConnection::instance()->isConnected()) { |
107 | @@ -183,6 +194,11 @@ |
108 | } while (retry); |
109 | } |
110 | |
111 | +QString EvernoteJob::toString() const |
112 | +{ |
113 | + return metaObject()->className(); |
114 | +} |
115 | + |
116 | QString EvernoteJob::token() |
117 | { |
118 | return m_token; |
119 | |
120 | === modified file 'src/libqtevernote/jobs/evernotejob.h' |
121 | --- src/libqtevernote/jobs/evernotejob.h 2014-12-08 10:25:48 +0000 |
122 | +++ src/libqtevernote/jobs/evernotejob.h 2015-02-27 00:43:31 +0000 |
123 | @@ -44,15 +44,25 @@ |
124 | { |
125 | Q_OBJECT |
126 | public: |
127 | - explicit EvernoteJob(QObject *parent = 0); |
128 | + enum JobPriority { |
129 | + JobPriorityHigh, |
130 | + JobPriorityLow |
131 | + }; |
132 | + |
133 | + explicit EvernoteJob(QObject *parent = 0, JobPriority jobPriority = JobPriorityHigh); |
134 | virtual ~EvernoteJob(); |
135 | |
136 | + JobPriority jobPriority() const; |
137 | + void setJobPriority(JobPriority priority = JobPriorityHigh); |
138 | + |
139 | void run() final; |
140 | |
141 | virtual bool operator==(const EvernoteJob *other) const = 0; |
142 | |
143 | virtual void attachToDuplicate(const EvernoteJob *other) = 0; |
144 | |
145 | + virtual QString toString() const; |
146 | + |
147 | signals: |
148 | void connectionLost(const QString &errorMessage); |
149 | |
150 | @@ -65,6 +75,7 @@ |
151 | |
152 | private: |
153 | QString m_token; |
154 | + JobPriority m_jobPriority; |
155 | |
156 | friend class EvernoteConnection; |
157 | }; |
158 | |
159 | === modified file 'src/libqtevernote/jobs/fetchnotejob.cpp' |
160 | --- src/libqtevernote/jobs/fetchnotejob.cpp 2014-10-24 18:45:35 +0000 |
161 | +++ src/libqtevernote/jobs/fetchnotejob.cpp 2015-02-27 00:43:31 +0000 |
162 | @@ -43,6 +43,14 @@ |
163 | connect(otherJob, &FetchNoteJob::resultReady, this, &FetchNoteJob::resultReady); |
164 | } |
165 | |
166 | +QString FetchNoteJob::toString() const |
167 | +{ |
168 | + return QString("%1, NoteGuid: %2, What: %3") |
169 | + .arg(metaObject()->className()) |
170 | + .arg(m_guid) |
171 | + .arg(m_what == LoadContent ? "Content" : "Resources"); |
172 | +} |
173 | + |
174 | void FetchNoteJob::startJob() |
175 | { |
176 | client()->getNote(m_result, token().toStdString(), m_guid.toStdString(), m_what == LoadContent, m_what == LoadResources, false, false); |
177 | |
178 | === modified file 'src/libqtevernote/jobs/fetchnotejob.h' |
179 | --- src/libqtevernote/jobs/fetchnotejob.h 2014-10-24 18:45:35 +0000 |
180 | +++ src/libqtevernote/jobs/fetchnotejob.h 2015-02-27 00:43:31 +0000 |
181 | @@ -36,6 +36,7 @@ |
182 | |
183 | virtual bool operator==(const EvernoteJob *other) const override; |
184 | virtual void attachToDuplicate(const EvernoteJob *other) override; |
185 | + virtual QString toString() const override; |
186 | |
187 | signals: |
188 | void resultReady(EvernoteConnection::ErrorCode error, const QString &errorMessage, const evernote::edam::Note ¬e, LoadWhat what); |
189 | |
190 | === modified file 'src/libqtevernote/jobs/fetchnotesjob.cpp' |
191 | --- src/libqtevernote/jobs/fetchnotesjob.cpp 2014-11-07 19:37:09 +0000 |
192 | +++ src/libqtevernote/jobs/fetchnotesjob.cpp 2015-02-27 00:43:31 +0000 |
193 | @@ -52,6 +52,16 @@ |
194 | connect(otherJob, &FetchNotesJob::jobDone, this, &FetchNotesJob::jobDone); |
195 | } |
196 | |
197 | +QString FetchNotesJob::toString() const |
198 | +{ |
199 | + return QString("%1, NotebookFilter: %2, SearchWords: %3, StartIndex: %4, ChunkSize: %5") |
200 | + .arg(metaObject()->className()) |
201 | + .arg(m_filterNotebookGuid) |
202 | + .arg(m_searchWords) |
203 | + .arg(m_startIndex) |
204 | + .arg(m_chunkSize); |
205 | +} |
206 | + |
207 | void FetchNotesJob::startJob() |
208 | { |
209 | int32_t start = m_startIndex; |
210 | |
211 | === modified file 'src/libqtevernote/jobs/fetchnotesjob.h' |
212 | --- src/libqtevernote/jobs/fetchnotesjob.h 2014-11-06 15:57:43 +0000 |
213 | +++ src/libqtevernote/jobs/fetchnotesjob.h 2015-02-27 00:43:31 +0000 |
214 | @@ -34,6 +34,7 @@ |
215 | |
216 | virtual bool operator==(const EvernoteJob *other) const override; |
217 | virtual void attachToDuplicate(const EvernoteJob *other) override; |
218 | + virtual QString toString() const override; |
219 | |
220 | signals: |
221 | void jobDone(EvernoteConnection::ErrorCode errorCode, const QString &errorMessage, const evernote::edam::NotesMetadataList &results, const QString &filterNotebookGuid); |
222 | |
223 | === modified file 'src/libqtevernote/jobs/notesstorejob.cpp' |
224 | --- src/libqtevernote/jobs/notesstorejob.cpp 2014-04-30 21:53:07 +0000 |
225 | +++ src/libqtevernote/jobs/notesstorejob.cpp 2015-02-27 00:43:31 +0000 |
226 | @@ -29,15 +29,17 @@ |
227 | |
228 | void NotesStoreJob::resetConnection() |
229 | { |
230 | + try { |
231 | + EvernoteConnection::instance()->m_notesStoreHttpClient->readEnd(); |
232 | + } catch(...) {} |
233 | + try { |
234 | + EvernoteConnection::instance()->m_notesStoreHttpClient->flush(); |
235 | + } catch(...) {} |
236 | if (EvernoteConnection::instance()->m_notesStoreHttpClient->isOpen()) { |
237 | - EvernoteConnection::instance()->m_notesStoreHttpClient->close(); |
238 | + try { |
239 | + EvernoteConnection::instance()->m_notesStoreHttpClient->close(); |
240 | + } catch(...) {} |
241 | } |
242 | - try { |
243 | - EvernoteConnection::instance()->m_notesStoreHttpClient->readEnd(); |
244 | - } catch(...) {} |
245 | - try { |
246 | - EvernoteConnection::instance()->m_notesStoreHttpClient->flush(); |
247 | - } catch(...) {} |
248 | EvernoteConnection::instance()->m_notesStoreHttpClient->open(); |
249 | } |
250 | |
251 | |
252 | === modified file 'src/libqtevernote/note.cpp' |
253 | --- src/libqtevernote/note.cpp 2015-02-24 22:21:04 +0000 |
254 | +++ src/libqtevernote/note.cpp 2015-02-27 00:43:31 +0000 |
255 | @@ -38,6 +38,7 @@ |
256 | m_deleted(false), |
257 | m_updateSequenceNumber(updateSequenceNumber), |
258 | m_loading(false), |
259 | + m_loadingHighPriority(false), |
260 | m_loaded(false), |
261 | m_syncError(false), |
262 | m_conflicting(false) |
263 | @@ -287,7 +288,7 @@ |
264 | QString Note::tagline() const |
265 | { |
266 | if (m_tagline.isEmpty()) { |
267 | - load(); |
268 | + load(false); |
269 | } |
270 | return m_tagline; |
271 | } |
272 | @@ -595,11 +596,17 @@ |
273 | NotesStore::instance()->deleteNote(m_guid); |
274 | } |
275 | |
276 | -void Note::setLoading(bool loading) |
277 | +void Note::setLoading(bool loading, bool highPriority) |
278 | { |
279 | if (m_loading != loading) { |
280 | m_loading = loading; |
281 | emit loadingChanged(); |
282 | + |
283 | + if (!m_loading) { |
284 | + m_loadingHighPriority = false; |
285 | + } else { |
286 | + m_loadingHighPriority = highPriority; |
287 | + } |
288 | } |
289 | } |
290 | |
291 | @@ -638,12 +645,14 @@ |
292 | } |
293 | } |
294 | |
295 | -void Note::load() const |
296 | +void Note::load(bool priorityHigh) const |
297 | { |
298 | if (!m_loaded && isCached()) { |
299 | loadFromCacheFile(); |
300 | - } else if (!m_loaded && !m_loading) { |
301 | - NotesStore::instance()->refreshNoteContent(m_guid); |
302 | + } else if (!m_loaded) { |
303 | + if (!m_loading || (priorityHigh && !m_loadingHighPriority)) { |
304 | + NotesStore::instance()->refreshNoteContent(m_guid, FetchNoteJob::LoadContent, priorityHigh ? EvernoteJob::JobPriorityHigh : EvernoteJob::JobPriorityLow); |
305 | + } |
306 | } |
307 | } |
308 | |
309 | |
310 | === modified file 'src/libqtevernote/note.h' |
311 | --- src/libqtevernote/note.h 2015-02-16 19:28:02 +0000 |
312 | +++ src/libqtevernote/note.h 2015-02-27 00:43:31 +0000 |
313 | @@ -190,7 +190,7 @@ |
314 | |
315 | private: |
316 | // Those should only be called from NotesStore, which is a friend |
317 | - void setLoading(bool loading); |
318 | + void setLoading(bool loading, bool highPriority = false); |
319 | void setSyncError(bool syncError); |
320 | void setDeleted(bool deleted); |
321 | void syncToCacheFile(); |
322 | @@ -201,7 +201,7 @@ |
323 | void setConflicting(bool conflicting); |
324 | |
325 | // const because we want to load on demand in getters. Keep this private! |
326 | - void load() const; |
327 | + void load(bool priorityHigh = true) const; |
328 | void loadFromCacheFile() const; |
329 | |
330 | private: |
331 | @@ -225,6 +225,7 @@ |
332 | QString m_infoFile; |
333 | |
334 | bool m_loading; |
335 | + bool m_loadingHighPriority; |
336 | mutable bool m_loaded; |
337 | bool m_synced; |
338 | bool m_syncError; |
339 | |
340 | === modified file 'src/libqtevernote/notesstore.cpp' |
341 | --- src/libqtevernote/notesstore.cpp 2015-02-24 18:55:55 +0000 |
342 | +++ src/libqtevernote/notesstore.cpp 2015-02-27 00:43:31 +0000 |
343 | @@ -604,7 +604,7 @@ |
344 | if (note->updateSequenceNumber() < result.updateSequenceNum) { |
345 | qDebug() << "refreshing note from network. suequence number changed: " << note->updateSequenceNumber() << "->" << result.updateSequenceNum; |
346 | changedRoles = updateFromEDAM(result, note); |
347 | - refreshNoteContent(note->guid(), FetchNoteJob::LoadContent, EvernoteConnection::JobPriorityLow); |
348 | + refreshNoteContent(note->guid(), FetchNoteJob::LoadContent, EvernoteJob::JobPriorityLow); |
349 | syncToCacheFile(note); |
350 | } |
351 | } else { |
352 | @@ -705,7 +705,7 @@ |
353 | } |
354 | } |
355 | |
356 | -void NotesStore::refreshNoteContent(const QString &guid, FetchNoteJob::LoadWhat what, EvernoteConnection::JobPriority priority) |
357 | +void NotesStore::refreshNoteContent(const QString &guid, FetchNoteJob::LoadWhat what, EvernoteJob::JobPriority priority) |
358 | { |
359 | qDebug() << "fetching note content from network for note" << guid << (what == FetchNoteJob::LoadContent ? "content" : "image"); |
360 | Note *note = m_notesHash.value(guid); |
361 | @@ -715,10 +715,11 @@ |
362 | } |
363 | if (EvernoteConnection::instance()->isConnected()) { |
364 | FetchNoteJob *job = new FetchNoteJob(guid, what, this); |
365 | + job->setJobPriority(priority); |
366 | connect(job, &FetchNoteJob::resultReady, this, &NotesStore::fetchNoteJobDone); |
367 | - EvernoteConnection::instance()->enqueue(job, priority); |
368 | + EvernoteConnection::instance()->enqueue(job); |
369 | |
370 | - note->setLoading(true); |
371 | + note->setLoading(true, priority == EvernoteJob::JobPriorityHigh); |
372 | int idx = m_notes.indexOf(note); |
373 | emit dataChanged(index(idx), index(idx), QVector<int>() << RoleLoading); |
374 | } |
375 | @@ -726,6 +727,7 @@ |
376 | |
377 | void NotesStore::fetchNoteJobDone(EvernoteConnection::ErrorCode errorCode, const QString &errorMessage, const evernote::edam::Note &result, FetchNoteJob::LoadWhat what) |
378 | { |
379 | + FetchNoteJob *job = static_cast<FetchNoteJob*>(sender()); |
380 | Note *note = m_notesHash.value(QString::fromStdString(result.guid)); |
381 | if (!note) { |
382 | qWarning() << "can't find note for this update... ignoring..."; |
383 | @@ -790,13 +792,14 @@ |
384 | QString mime = QString::fromStdString(resource.mime); |
385 | |
386 | if (what == FetchNoteJob::LoadResources) { |
387 | + qDebug() << "[Sync] Resource fetched for note:" << note->guid() << "Filename:" << fileName << "Mimetype:" << mime << "Hash:" << hash; |
388 | QByteArray resourceData = QByteArray(resource.data.body.data(), resource.data.size); |
389 | note->addResource(resourceData, hash, fileName, mime); |
390 | } else if (Resource::isCached(hash)) { |
391 | - qDebug() << "have resource cached"; |
392 | + qDebug() << "[Sync] Resource already cached for note:" << note->guid() << "Filename:" << fileName << "Mimetype:" << mime << "Hash:" << hash; |
393 | note->addResource(QByteArray(), hash, fileName, mime); |
394 | } else { |
395 | - qDebug() << "refetching for image"; |
396 | + qDebug() << "[Sync] Resource not yet fetched for note:" << note->guid() << "Filename:" << fileName << "Mimetype:" << mime << "Hash:" << hash; |
397 | refreshWithResourceData = true; |
398 | } |
399 | roles << RoleHtmlContent << RoleEnmlContent << RoleResourceUrls; |
400 | @@ -833,7 +836,7 @@ |
401 | |
402 | if (refreshWithResourceData) { |
403 | qDebug() << "refreshWithResourceData"; |
404 | - refreshNoteContent(note->guid(), FetchNoteJob::LoadResources); |
405 | + refreshNoteContent(note->guid(), FetchNoteJob::LoadResources, job->jobPriority()); |
406 | } else { |
407 | syncToCacheFile(note); // Syncs into the list cache |
408 | note->syncToCacheFile(); // Syncs note's content into notes cache |
409 | |
410 | === modified file 'src/libqtevernote/notesstore.h' |
411 | --- src/libqtevernote/notesstore.h 2015-02-24 18:55:55 +0000 |
412 | +++ src/libqtevernote/notesstore.h 2015-02-27 00:43:31 +0000 |
413 | @@ -142,7 +142,7 @@ |
414 | void refreshNotes(const QString &filterNotebookGuid = QString(), int startIndex = 0); |
415 | |
416 | // Defaulting to High priority to provide fast feedback to the ui. Use low priority if you call this to prefetch things in the background |
417 | - void refreshNoteContent(const QString &guid, FetchNoteJob::LoadWhat what = FetchNoteJob::LoadContent, EvernoteConnection::JobPriority priority = EvernoteConnection::JobPriorityHigh); |
418 | + void refreshNoteContent(const QString &guid, FetchNoteJob::LoadWhat what = FetchNoteJob::LoadContent, EvernoteJob::JobPriority priority = EvernoteJob::JobPriorityHigh); |
419 | void refreshNotebooks(); |
420 | void refreshTags(); |
421 |
PASSED: Continuous integration, rev:352 91.189. 93.70:8080/ job/reminders- app-ci/ 675/ 91.189. 93.70:8080/ job/generic- mediumtests- utopic/ 2148 91.189. 93.70:8080/ job/generic- mediumtests- utopic/ 2148/artifact/ work/output/ *zip*/output. zip 91.189. 93.70:8080/ job/reminders- app-utopic- amd64-ci/ 277 91.189. 93.70:8080/ job/reminders- app-vivid- amd64-ci/ 96
http://
Executed test runs:
SUCCESS: http://
deb: http://
SUCCESS: http://
SUCCESS: http://
Click here to trigger a rebuild: 91.189. 93.70:8080/ job/reminders- app-ci/ 675/rebuild
http://