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