1 // Aseprite
2 // Copyright (C) 2001-2018 David Capello
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/crash/session.h"
12
13 #include "app/console.h"
14 #include "app/context.h"
15 #include "app/crash/read_document.h"
16 #include "app/crash/write_document.h"
17 #include "app/doc.h"
18 #include "app/doc_access.h"
19 #include "app/file/file.h"
20 #include "app/ui_context.h"
21 #include "base/bind.h"
22 #include "base/convert_to.h"
23 #include "base/fs.h"
24 #include "base/fstream_path.h"
25 #include "base/process.h"
26 #include "base/split_string.h"
27 #include "base/string.h"
28 #include "base/unique_ptr.h"
29 #include "doc/cancel_io.h"
30
31 namespace app {
32 namespace crash {
33
Backup(const std::string & dir)34 Session::Backup::Backup(const std::string& dir)
35 : m_dir(dir)
36 {
37 DocumentInfo info;
38 read_document_info(dir, info);
39
40 std::vector<char> buf(1024);
41 sprintf(&buf[0], "%s Sprite %dx%d, %d %s: %s",
42 info.format == IMAGE_RGB ? "RGB":
43 info.format == IMAGE_GRAYSCALE ? "Grayscale":
44 info.format == IMAGE_INDEXED ? "Indexed":
45 info.format == IMAGE_BITMAP ? "Bitmap": "Unknown",
46 info.width, info.height, info.frames,
47 info.frames == 1 ? "frame": "frames",
48 info.filename.c_str());
49 m_desc = &buf[0];
50 }
51
Session(const std::string & path)52 Session::Session(const std::string& path)
53 : m_pid(0)
54 , m_path(path)
55 {
56 }
57
~Session()58 Session::~Session()
59 {
60 }
61
name() const62 std::string Session::name() const
63 {
64 std::string name = base::get_file_title(m_path);
65 std::vector<std::string> parts;
66 base::split_string(name, parts, "-");
67
68 if (parts.size() == 3) {
69 return "Session date: " + parts[0] + " time: " + parts[1] + " (PID " + parts[2] + ")";
70 }
71 else
72 return name;
73 }
74
version()75 std::string Session::version()
76 {
77 if (m_version.empty()) {
78 std::string verfile = verFilename();
79 if (base::is_file(verfile)) {
80 std::ifstream pf(FSTREAM_PATH(verfile));
81 if (pf)
82 pf >> m_version;
83 }
84 }
85 return m_version;
86 }
87
backups()88 const Session::Backups& Session::backups()
89 {
90 if (m_backups.empty()) {
91 for (auto& item : base::list_files(m_path)) {
92 std::string docDir = base::join_path(m_path, item);
93 if (base::is_directory(docDir)) {
94 m_backups.push_back(new Backup(docDir));
95 }
96 }
97 }
98 return m_backups;
99 }
100
isRunning()101 bool Session::isRunning()
102 {
103 loadPid();
104 return base::is_process_running(m_pid);
105 }
106
isEmpty()107 bool Session::isEmpty()
108 {
109 for (auto& item : base::list_files(m_path)) {
110 if (base::is_directory(base::join_path(m_path, item)))
111 return false;
112 }
113 return true;
114 }
115
create(base::pid pid)116 void Session::create(base::pid pid)
117 {
118 m_pid = pid;
119
120 std::ofstream pidf(FSTREAM_PATH(pidFilename()));
121 std::ofstream verf(FSTREAM_PATH(verFilename()));
122
123 pidf << m_pid;
124 verf << VERSION;
125 }
126
removeFromDisk()127 void Session::removeFromDisk()
128 {
129 try {
130 if (base::is_file(pidFilename()))
131 base::delete_file(pidFilename());
132
133 if (base::is_file(verFilename()))
134 base::delete_file(verFilename());
135
136 base::remove_directory(m_path);
137 }
138 catch (const std::exception& ex) {
139 (void)ex;
140 LOG(ERROR) << "RECO: Session directory cannot be removed, it's not empty.\n"
141 << " Error: " << ex.what() << "\n";
142 }
143 }
144
145 class CustomWeakDocReader : public WeakDocReader
146 , public doc::CancelIO {
147 public:
CustomWeakDocReader(Doc * doc)148 explicit CustomWeakDocReader(Doc* doc)
149 : WeakDocReader(doc) {
150 }
151
152 // CancelIO impl
isCanceled()153 bool isCanceled() override {
154 return !isLocked();
155 }
156 };
157
saveDocumentChanges(Doc * doc)158 bool Session::saveDocumentChanges(Doc* doc)
159 {
160 CustomWeakDocReader reader(doc);
161 if (!reader.isLocked())
162 return false;
163
164 app::Context ctx;
165 std::string dir = base::join_path(m_path,
166 base::convert_to<std::string>(doc->id()));
167 TRACE("RECO: Saving document '%s'...\n", dir.c_str());
168
169 if (!base::is_directory(dir))
170 base::make_directory(dir);
171
172 // Save document information
173 return write_document(dir, doc, &reader);
174 }
175
removeDocument(Doc * doc)176 void Session::removeDocument(Doc* doc)
177 {
178 try {
179 delete_document_internals(doc);
180
181 // Delete document backup directory
182 std::string dir = base::join_path(m_path,
183 base::convert_to<std::string>(doc->id()));
184 if (base::is_directory(dir))
185 deleteDirectory(dir);
186 }
187 catch (const std::exception&) {
188 // TODO Log this error
189 }
190 }
191
restoreBackupDoc(const std::string & backupDir)192 Doc* Session::restoreBackupDoc(const std::string& backupDir)
193 {
194 Console console;
195 try {
196 Doc* doc = read_document(backupDir);
197 if (doc) {
198 fixFilename(doc);
199 return doc;
200 }
201 }
202 catch (const std::exception& ex) {
203 Console::showException(ex);
204 }
205 return nullptr;
206 }
207
restoreBackup(Backup * backup)208 void Session::restoreBackup(Backup* backup)
209 {
210 Doc* doc = restoreBackupDoc(backup->dir());
211 if (doc)
212 UIContext::instance()->documents().add(doc);
213 }
214
restoreBackupById(const ObjectId id)215 void Session::restoreBackupById(const ObjectId id)
216 {
217 std::string docDir = base::join_path(m_path, base::convert_to<std::string>(int(id)));
218 if (!base::is_directory(docDir))
219 return;
220
221 Doc* doc = restoreBackupDoc(docDir);
222 if (doc)
223 UIContext::instance()->documents().add(doc);
224 }
225
restoreBackupDocById(const doc::ObjectId id)226 Doc* Session::restoreBackupDocById(const doc::ObjectId id)
227 {
228 std::string docDir = base::join_path(m_path, base::convert_to<std::string>(int(id)));
229 if (!base::is_directory(docDir))
230 return nullptr;
231
232 return restoreBackupDoc(docDir);
233 }
234
restoreRawImages(Backup * backup,RawImagesAs as)235 void Session::restoreRawImages(Backup* backup, RawImagesAs as)
236 {
237 Console console;
238 try {
239 Doc* doc = read_document_with_raw_images(backup->dir(), as);
240 if (doc) {
241 fixFilename(doc);
242 UIContext::instance()->documents().add(doc);
243 }
244 }
245 catch (const std::exception& ex) {
246 Console::showException(ex);
247 }
248 }
249
deleteBackup(Backup * backup)250 void Session::deleteBackup(Backup* backup)
251 {
252 try {
253 auto it = std::find(m_backups.begin(), m_backups.end(), backup);
254 ASSERT(it != m_backups.end());
255 if (it != m_backups.end())
256 m_backups.erase(it);
257
258 if (base::is_directory(backup->dir()))
259 deleteDirectory(backup->dir());
260 }
261 catch (const std::exception& ex) {
262 Console::showException(ex);
263 }
264 }
265
loadPid()266 void Session::loadPid()
267 {
268 if (m_pid)
269 return;
270
271 std::string pidfile = pidFilename();
272 if (base::is_file(pidfile)) {
273 std::ifstream pf(FSTREAM_PATH(pidfile));
274 if (pf)
275 pf >> m_pid;
276 }
277 }
278
pidFilename() const279 std::string Session::pidFilename() const
280 {
281 return base::join_path(m_path, "pid");
282 }
283
verFilename() const284 std::string Session::verFilename() const
285 {
286 return base::join_path(m_path, "ver");
287 }
288
deleteDirectory(const std::string & dir)289 void Session::deleteDirectory(const std::string& dir)
290 {
291 ASSERT(!dir.empty());
292 if (dir.empty())
293 return;
294
295 for (auto& item : base::list_files(dir)) {
296 std::string objfn = base::join_path(dir, item);
297 if (base::is_file(objfn)) {
298 TRACE("RECO: Deleting file '%s'\n", objfn.c_str());
299 base::delete_file(objfn);
300 }
301 }
302 base::remove_directory(dir);
303 }
304
fixFilename(Doc * doc)305 void Session::fixFilename(Doc* doc)
306 {
307 std::string fn = doc->filename();
308 if (fn.empty())
309 return;
310
311 std::string ext = base::get_file_extension(fn);
312 if (!ext.empty())
313 ext = "." + ext;
314
315 doc->setFilename(
316 base::join_path(
317 base::get_file_path(fn),
318 base::get_file_title(fn) + "-Recovered" + ext));
319 }
320
321 } // namespace crash
322 } // namespace app
323