1// Aseprite
2// Copyright (C) 2019-2020 Igara Studio S.A.
3//
4// This program is distributed under the terms of
5// the End-User License Agreement for Aseprite.
6
7#ifdef HAVE_CONFIG_H
8#include "config.h"
9#endif
10
11#include "app/closed_docs.h"
12#include "app/doc.h"
13#include "app/pref/preferences.h"
14
15#include <algorithm>
16#include <limits>
17
18#define CLOSEDOC_TRACE(...) // TRACEARGS
19
20namespace app {
21
22ClosedDocs::ClosedDocs(const Preferences& pref)
23 : m_done(false)
24{
25 if (pref.general.dataRecovery())
26 m_dataRecoveryPeriodMSecs = int(1000.0*60.0*pref.general.dataRecoveryPeriod());
27 else
28 m_dataRecoveryPeriodMSecs = 0;
29
30 if (pref.general.keepClosedSpriteOnMemory())
31 m_keepClosedDocAliveForMSecs = int(1000.0*60.0*pref.general.keepClosedSpriteOnMemoryFor());
32 else
33 m_keepClosedDocAliveForMSecs = 0;
34
35 CLOSEDOC_TRACE("CLOSEDOC: Init",
36 "dataRecoveryPeriod", m_dataRecoveryPeriodMSecs,
37 "keepClosedDocs", m_keepClosedDocAliveForMSecs);
38}
39
40ClosedDocs::~ClosedDocs()
41{
42 CLOSEDOC_TRACE("CLOSEDOC: Exit");
43
44 if (m_thread.joinable()) {
45 CLOSEDOC_TRACE("CLOSEDOC: Join thread");
46
47 m_done = true;
48 m_cv.notify_one();
49 m_thread.join();
50
51 CLOSEDOC_TRACE("CLOSEDOC: Join done");
52 }
53
54 ASSERT(m_docs.empty());
55}
56
57bool ClosedDocs::hasClosedDocs()
58{
59 bool result;
60 {
61 std::unique_lock<std::mutex> lock(m_mutex);
62 result = !m_docs.empty();
63 }
64 CLOSEDOC_TRACE("CLOSEDOC: Has closed docs?",
65 (result ? "true": "false"));
66 return result;
67}
68
69void ClosedDocs::addClosedDoc(Doc* doc)
70{
71 CLOSEDOC_TRACE("CLOSEDOC: Add closed doc", doc);
72
73 ASSERT(doc != nullptr);
74 ASSERT(doc->context() == nullptr);
75
76 ClosedDoc closedDoc = { doc, base::current_tick() };
77
78 std::unique_lock<std::mutex> lock(m_mutex);
79 m_docs.insert(m_docs.begin(), std::move(closedDoc));
80
81 if (!m_thread.joinable())
82 m_thread = std::thread([this]{ backgroundThread(); });
83 else
84 m_cv.notify_one();
85}
86
87Doc* ClosedDocs::reopenLastClosedDoc()
88{
89 Doc* doc = nullptr;
90 {
91 std::unique_lock<std::mutex> lock(m_mutex);
92 if (!m_docs.empty()) {
93 doc = m_docs.front().doc;
94 m_docs.erase(m_docs.begin());
95 }
96 CLOSEDOC_TRACE(" -> ", doc);
97 }
98 CLOSEDOC_TRACE("CLOSEDOC: Reopen last closed doc", doc);
99 return doc;
100}
101
102std::vector<Doc*> ClosedDocs::getAndRemoveAllClosedDocs()
103{
104 std::vector<Doc*> docs;
105 {
106 std::unique_lock<std::mutex> lock(m_mutex);
107 CLOSEDOC_TRACE("CLOSEDOC: Get and remove all closed", m_docs.size(), "docs");
108 for (const ClosedDoc& closedDoc : m_docs)
109 docs.push_back(closedDoc.doc);
110 m_docs.clear();
111 m_done = true;
112 m_cv.notify_one();
113 }
114 return docs;
115}
116
117void ClosedDocs::backgroundThread()
118{
119 CLOSEDOC_TRACE("CLOSEDOC: [BG] Background thread start");
120
121 std::unique_lock<std::mutex> lock(m_mutex);
122 while (!m_done) {
123 base::tick_t now = base::current_tick();
124 base::tick_t waitForMSecs = std::numeric_limits<base::tick_t>::max();
125
126 for (auto it=m_docs.begin(); it != m_docs.end(); ) {
127 const ClosedDoc& closedDoc = *it;
128 auto doc = closedDoc.doc;
129
130 base::tick_t diff = now - closedDoc.timestamp;
131 if (diff >= m_keepClosedDocAliveForMSecs) {
132 if (// If we backup process is disabled
133 m_dataRecoveryPeriodMSecs == 0 ||
134 // Or this document doesn't need a backup (e.g. an unmodified document)
135 !doc->needsBackup() ||
136 // Or the document already has the backup done
137 doc->isFullyBackedUp()) {
138 // Finally delete the document (this is the place where we
139 // delete all documents created/loaded by the user)
140 CLOSEDOC_TRACE("CLOSEDOC: [BG] Delete doc", doc);
141 delete doc;
142 it = m_docs.erase(it);
143 }
144 else {
145 waitForMSecs = std::min(waitForMSecs, m_dataRecoveryPeriodMSecs);
146 ++it;
147 }
148 }
149 else {
150 waitForMSecs = std::min(waitForMSecs, m_keepClosedDocAliveForMSecs-diff);
151 ++it;
152 }
153 }
154
155 if (waitForMSecs < std::numeric_limits<base::tick_t>::max()) {
156 CLOSEDOC_TRACE("CLOSEDOC: [BG] Wait for", waitForMSecs, "milliseconds");
157
158 ASSERT(!m_docs.empty());
159 m_cv.wait_for(lock, std::chrono::milliseconds(waitForMSecs));
160 }
161 else {
162 CLOSEDOC_TRACE("CLOSEDOC: [BG] Wait for condition variable");
163
164 ASSERT(m_docs.empty());
165 m_cv.wait(lock);
166 }
167 }
168
169 CLOSEDOC_TRACE("CLOSEDOC: [BG] Background thread end");
170}
171
172} // namespace app
173