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 | |
20 | namespace app { |
21 | |
22 | ClosedDocs::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 | |
40 | ClosedDocs::~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 | |
57 | bool 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 | |
69 | void 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 | |
87 | Doc* 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 | |
102 | std::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 | |
117 | void 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 | |