1 /* === S Y N F I G ========================================================= */
2 /*! \file filesystemtemporary.cpp
3 ** \brief FileSystemTemporary Implementation
4 **
5 ** $Id$
6 **
7 ** \legal
8 ** ......... ... 2016 Ivan Mahonin
9 **
10 ** This package is free software; you can redistribute it and/or
11 ** modify it under the terms of the GNU General Public License as
12 ** published by the Free Software Foundation; either version 2 of
13 ** the License, or (at your option) any later version.
14 **
15 ** This package is distributed in the hope that it will be useful,
16 ** but WITHOUT ANY WARRANTY; without even the implied warranty of
17 ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
18 ** General Public License for more details.
19 ** \endlegal
20 */
21 /* ========================================================================= */
22
23 /* === H E A D E R S ======================================================= */
24
25 #ifdef USING_PCH
26 # include "pch.h"
27 #else
28 #ifdef HAVE_CONFIG_H
29 # include <config.h>
30 #endif
31
32 #include <libxml++/libxml++.h>
33
34 #include "general.h"
35 #include "localization.h"
36
37 #include "filesystemtemporary.h"
38
39 #include "guid.h"
40 #include "zstreambuf.h"
41
42 #endif
43
44 /* === U S I N G =========================================================== */
45
46 using namespace std;
47 using namespace etl;
48 using namespace synfig;
49
50 /* === M A C R O S ========================================================= */
51
52 /* === G L O B A L S ======================================================= */
53
54 /* === P R O C E D U R E S ================================================= */
55
56 /* === M E T H O D S ======================================================= */
57
FileSystemTemporary(const String & tag,const String & temporary_directory,const FileSystem::Handle & sub_file_system)58 FileSystemTemporary::FileSystemTemporary(const String &tag, const String &temporary_directory, const FileSystem::Handle &sub_file_system):
59 file_system(FileSystemNative::instance()),
60 tag(tag),
61 temporary_directory(temporary_directory.empty() ? get_system_temporary_directory() : temporary_directory),
62 temporary_filename_base(generate_temporary_filename_base(tag)),
63 autosave(true)
64 {
65 set_sub_file_system(sub_file_system);
66 }
67
~FileSystemTemporary()68 FileSystemTemporary::~FileSystemTemporary()
69 {
70 discard_changes();
71 }
72
73 String
get_system_temporary_directory()74 FileSystemTemporary::get_system_temporary_directory()
75 {
76 const char *tmpdir;
77 if ((tmpdir = getenv("TEMP")) == NULL)
78 if ((tmpdir = getenv("TMP")) == NULL)
79 if ((tmpdir = getenv("TMPDIR")) == NULL)
80 tmpdir = "/tmp";
81 return String(tmpdir);
82 }
83
84 String
generate_temporary_filename_base(const String & tag)85 FileSystemTemporary::generate_temporary_filename_base(const String &tag)
86 {
87 return "synfig_" + tag + "_" + GUID().get_string();
88 }
89
90 bool
scan_temporary_directory(const String & tag,FileList & out_files,const String & dirname)91 FileSystemTemporary::scan_temporary_directory(const String &tag, FileList &out_files, const String &dirname)
92 {
93 String tmpdir = dirname.empty() ? get_system_temporary_directory() : dirname;
94
95 FileList files;
96 if (!FileSystemNative::instance()->directory_scan(dirname, files))
97 return false;
98
99 String prefix = "synfig_" + tag + "_";
100 for(FileList::const_iterator i = files.begin(); i != files.end(); ++i)
101 if (i->substr(0, prefix.size()) == prefix)
102 if (FileSystemNative::instance()->is_file(tmpdir + ETL_DIRECTORY_SEPARATOR + *i))
103 out_files.push_back(*i);
104 return true;
105 }
106
107 String
generate_system_temporary_filename(const String & tag)108 FileSystemTemporary::generate_system_temporary_filename(const String &tag)
109 {
110 return get_system_temporary_directory() + ETL_DIRECTORY_SEPARATOR + generate_temporary_filename_base(tag);
111 }
112
113 bool
create_temporary_directory() const114 FileSystemTemporary::create_temporary_directory() const
115 {
116 return file_system->directory_create_recursive(get_temporary_directory());
117 }
118
119 bool
is_file(const String & filename)120 FileSystemTemporary::is_file(const String &filename)
121 {
122 FileMap::const_iterator i = files.find(fix_slashes(filename));
123 if (i != files.end())
124 return !i->second.is_removed && !i->second.is_directory;
125 return get_sub_file_system() && get_sub_file_system()->is_file(filename);
126 }
127
128 bool
is_directory(const String & filename)129 FileSystemTemporary::is_directory(const String &filename)
130 {
131 if (filename.empty()) return true;
132 FileMap::const_iterator i = files.find(fix_slashes(filename));
133 if (i != files.end())
134 return !i->second.is_removed && i->second.is_directory;
135 return get_sub_file_system() && get_sub_file_system()->is_directory(filename);
136 }
137
138 bool
directory_create(const String & dirname)139 FileSystemTemporary::directory_create(const String &dirname)
140 {
141 if (is_file(dirname)) return false;
142 if (is_directory(dirname)) return true;
143
144 FileInfo info;
145 info.name = fix_slashes(dirname);
146 info.is_directory = true;
147 files[info.name] = info;
148 autosave_temporary();
149 return true;
150 }
151
152 bool
directory_scan(const String & dirname,FileList & out_files)153 FileSystemTemporary::directory_scan(const String &dirname, FileList &out_files)
154 {
155 out_files.clear();
156 if (!is_directory(dirname)) return false;
157
158 String clean_dirname = fix_slashes(dirname);
159 std::set<String> files_set;
160
161 if (get_sub_file_system())
162 {
163 FileList list;
164 if (!get_sub_file_system()->directory_scan(clean_dirname, list))
165 return false;
166 for(FileList::const_iterator i = list.begin(); i != list.end(); ++i)
167 files_set.insert(*i);
168 }
169
170 for(FileMap::iterator i = files.begin(); i != files.end(); i++)
171 if (etl::dirname(i->second.name) == clean_dirname)
172 {
173 if (i->second.is_removed)
174 files_set.erase(etl::basename(i->second.name));
175 else
176 files_set.insert(etl::basename(i->second.name));
177 }
178
179 for(std::set<String>::const_iterator i = files_set.begin(); i != files_set.end(); ++i)
180 out_files.push_back(*i);
181 return true;
182 }
183
184 bool
file_remove(const String & filename)185 FileSystemTemporary::file_remove(const String &filename)
186 {
187 // remove directory
188 if (is_directory(filename))
189 {
190 // directory should be empty
191 // NB: This code can check temporary files only,
192 // but directory may contain other not tracked real files.
193 String prefix = fix_slashes(filename + "/");
194 for(FileMap::iterator i = files.begin(); i != files.end(); i++)
195 if ( !i->second.is_removed
196 && i->second.name.substr(0, prefix.size()) == prefix )
197 return false;
198
199 FileMap::iterator i = files.find(fix_slashes(filename));
200 if (i == files.end())
201 {
202 FileInfo &info = files[fix_slashes(filename)];
203 info.name = fix_slashes(filename);
204 info.is_directory = true;
205 info.is_removed = true;
206 autosave_temporary();
207 }
208 else
209 {
210 FileInfo &info = i->second;
211 info.is_removed = true;
212 autosave_temporary();
213 }
214 }
215 else
216 // remove file
217 if (is_file(filename))
218 {
219 FileMap::iterator i = files.find(fix_slashes(filename));
220 if (i == files.end())
221 {
222 FileInfo &info = files[fix_slashes(filename)];
223 info.name = fix_slashes(filename);
224 info.is_directory = false;
225 info.is_removed = true;
226 autosave_temporary();
227 }
228 else
229 {
230 FileInfo &info = i->second;
231 info.is_removed = true;
232 if (!info.tmp_filename.empty())
233 {
234 file_system->file_remove(info.tmp_filename);
235 info.tmp_filename.clear();
236 }
237 autosave_temporary();
238 }
239 }
240
241 return true;
242 }
243
244 FileSystem::ReadStream::Handle
get_read_stream(const String & filename)245 FileSystemTemporary::get_read_stream(const String &filename)
246 {
247 FileMap::const_iterator i = files.find(fix_slashes(filename));
248 if (i != files.end())
249 {
250 if (!i->second.is_removed && !i->second.is_directory && !i->second.tmp_filename.empty())
251 return file_system->get_read_stream(i->second.tmp_filename);
252 }
253 else
254 {
255 if (get_sub_file_system())
256 return get_sub_file_system()->get_read_stream(filename);
257 }
258 return FileSystem::ReadStream::Handle();
259 }
260
261 FileSystem::WriteStream::Handle
get_write_stream(const String & filename)262 FileSystemTemporary::get_write_stream(const String &filename)
263 {
264 FileSystem::WriteStream::Handle stream;
265
266 FileMap::iterator i = files.find(fix_slashes(filename));
267 if (i == files.end())
268 {
269 // create new file
270 create_temporary_directory();
271 FileInfo new_info;
272 new_info.name = fix_slashes(filename);
273 new_info.tmp_filename = get_temporary_directory()
274 + ETL_DIRECTORY_SEPARATOR
275 + generate_temporary_filename_base(tag + ".file");
276 stream = file_system->get_write_stream(new_info.tmp_filename);
277 if (stream)
278 {
279 files[new_info.name] = new_info;
280 autosave_temporary();
281 }
282 }
283 else
284 if (!i->second.is_directory || i->second.is_removed)
285 {
286 create_temporary_directory();
287 String tmp_filename = i->second.tmp_filename.empty()
288 ? get_temporary_directory()
289 + ETL_DIRECTORY_SEPARATOR
290 + generate_temporary_filename_base(tag + ".file")
291 : i->second.tmp_filename;
292 stream = file_system->get_write_stream(tmp_filename);
293 if (stream)
294 {
295 i->second.tmp_filename = tmp_filename;
296 i->second.is_directory = false;
297 i->second.is_removed = false;
298 autosave_temporary();
299 }
300 }
301
302 return stream;
303 }
304
305 String
get_real_uri(const String & filename)306 FileSystemTemporary::get_real_uri(const String &filename)
307 {
308 FileMap::const_iterator i = files.find(fix_slashes(filename));
309 if (i != files.end())
310 {
311 if (!i->second.tmp_filename.empty())
312 return file_system->get_real_uri(i->second.tmp_filename);
313 }
314 else
315 {
316 if (get_sub_file_system())
317 return get_sub_file_system()->get_real_uri(filename);
318 }
319 return String();
320 }
321
322 bool
save_changes(const FileSystemNative::Handle & file_system,const FileSystem::Handle & target_file_system,FileMap & files,bool remove_files)323 FileSystemTemporary::save_changes(
324 const FileSystemNative::Handle &file_system,
325 const FileSystem::Handle &target_file_system,
326 FileMap &files,
327 bool remove_files)
328 {
329 assert(file_system);
330 assert(target_file_system);
331
332 // remove files
333 bool processed = true;
334 while(processed)
335 {
336 processed = false;
337 for(FileMap::iterator i = files.begin(); i != files.end(); i++)
338 {
339 bool to_remove = i->second.is_directory
340 ? target_file_system->is_file(i->second.name)
341 : target_file_system->is_directory(i->second.name);
342 to_remove = to_remove || i->second.is_removed;
343 if (to_remove && target_file_system->file_remove(i->second.name))
344 {
345 processed = true;
346 if (i->second.is_removed) files.erase(i);
347 break;
348 }
349 }
350 }
351
352 // create directories
353 processed = true;
354 while(processed)
355 {
356 processed = false;
357 for(FileMap::iterator i = files.begin(); i != files.end(); i++)
358 {
359 if (!i->second.is_removed
360 && i->second.is_directory
361 && target_file_system->directory_create(i->second.name))
362 {
363 processed = true;
364 files.erase(i);
365 break;
366 }
367 }
368 }
369
370 // create files
371 for(FileMap::iterator i = files.begin(); i != files.end();)
372 {
373 if (!i->second.is_removed
374 && !i->second.is_directory
375 && !i->second.tmp_filename.empty()
376 && copy(file_system, i->second.tmp_filename, target_file_system, i->second.name))
377 {
378 if (!remove_files)
379 file_system->file_remove(i->second.tmp_filename);
380 processed = true;
381 files.erase(i++);
382 }
383 else i++;
384 }
385
386 return files.empty();
387 }
388
389 bool
save_changes_copy(const FileSystem::Handle & sub_file_system) const390 FileSystemTemporary::save_changes_copy(const FileSystem::Handle &sub_file_system) const
391 {
392 assert(sub_file_system);
393 assert(sub_file_system != get_sub_file_system());
394 FileMap files_copy = files;
395 return save_changes(file_system, sub_file_system, files_copy, false);
396 }
397
398 bool
save_changes(const FileSystem::Handle & sub_file_system,bool as_copy)399 FileSystemTemporary::save_changes(const FileSystem::Handle &sub_file_system, bool as_copy)
400 {
401 if (as_copy)
402 return save_changes_copy(sub_file_system);
403 set_sub_file_system(sub_file_system);
404 return save_changes();
405 }
406
407 bool
save_changes()408 FileSystemTemporary::save_changes()
409 {
410 assert(get_sub_file_system());
411 bool result = save_changes(this->file_system, get_sub_file_system(), files, true);
412 autosave_temporary();
413 return result;
414 }
415
416 void
discard_changes()417 FileSystemTemporary::discard_changes()
418 {
419 // remove temporary files
420 for(FileMap::iterator i = files.begin(); i != files.end(); i++)
421 {
422 if (!i->second.is_removed
423 && !i->second.is_directory
424 && !i->second.tmp_filename.empty())
425 {
426 file_system->file_remove(i->second.tmp_filename);
427 }
428 }
429
430 // update internal state
431 files.clear();
432 meta.clear();
433
434 // remove file with description
435 assert(empty());
436 save_temporary();
437 }
438
439 void
reset_temporary_filename_base(const String & tag,const String & temporary_directory)440 FileSystemTemporary::reset_temporary_filename_base(const String &tag, const String &temporary_directory)
441 {
442 // remove previous file
443 assert(empty());
444 save_temporary();
445 this->tag = tag;
446 this->temporary_directory = temporary_directory;
447 temporary_filename_base = generate_temporary_filename_base(this->tag);
448 }
449
450 String
get_meta(const String & key) const451 FileSystemTemporary::get_meta(const String &key) const
452 {
453 map<String, String>::const_iterator i = meta.find(key);
454 return i == meta.end() ? String() : i->second;
455 }
456
457 void
set_meta(const String & key,const String & value)458 FileSystemTemporary::set_meta(const String &key, const String &value)
459 {
460 meta[key] = value;
461 autosave_temporary();
462 }
463
464 void
clear_meta()465 FileSystemTemporary::clear_meta()
466 {
467 meta.clear();
468 autosave_temporary();
469 }
470
471 const std::map<String, String>&
get_metadata() const472 FileSystemTemporary::get_metadata() const
473 { return meta; }
474
475 void
set_metadata(const std::map<String,String> & data)476 FileSystemTemporary::set_metadata(const std::map<String, String> &data)
477 {
478 meta = data;
479 autosave_temporary();
480 }
481
482 bool
autosave_temporary() const483 FileSystemTemporary::autosave_temporary() const
484 {
485 return !autosave || save_temporary();
486 }
487
488 bool
save_temporary() const489 FileSystemTemporary::save_temporary() const
490 {
491 if (empty())
492 {
493 file_system->file_remove(get_temporary_directory() + ETL_DIRECTORY_SEPARATOR + get_temporary_filename_base());
494 return true;
495 }
496
497 xmlpp::Document document;
498 xmlpp::Element *root = document.create_root_node("temporary-file-system");
499
500 xmlpp::Element *meta_node = root->add_child("meta");
501 for(map<String, String>::const_iterator i = meta.begin(); i != meta.end(); i++)
502 {
503 xmlpp::Element *entry = meta_node->add_child("entry");
504 entry->add_child("key")->set_child_text(i->first);
505 entry->add_child("value")->set_child_text(i->second);
506 }
507
508 xmlpp::Element *files_node = root->add_child("files");
509 for(FileMap::const_iterator i = files.begin(); i != files.end(); i++)
510 {
511 xmlpp::Element *entry = files_node->add_child("entry");
512 entry->add_child("name")->set_child_text(i->second.name);
513 entry->add_child("tmp-basename")->set_child_text(basename(i->second.tmp_filename));
514 entry->add_child("is-directory")->set_child_text(i->second.is_directory ? "true" : "false");
515 entry->add_child("is-removed")->set_child_text(i->second.is_removed ? "true" : "false");
516 }
517
518 create_temporary_directory();
519 FileSystem::WriteStream::Handle stream =
520 file_system->get_write_stream(
521 get_temporary_directory()
522 + ETL_DIRECTORY_SEPARATOR
523 + get_temporary_filename_base() );
524 if (!stream) return false;
525
526 stream = new ZWriteStream(stream);
527 try
528 {
529 document.write_to_stream_formatted(*stream, "UTF-8");
530 }
531 catch(...)
532 {
533 synfig::error("FileSystemTemporary::save_temporary(): Caught unknown exception");
534 return false;
535 }
536 stream.reset();
537
538 return true;
539 }
540
541 String
get_xml_node_text(xmlpp::Node * node)542 FileSystemTemporary::get_xml_node_text(xmlpp::Node *node)
543 {
544 String s;
545 if (node != NULL)
546 {
547 xmlpp::Element::NodeList list = node->get_children();
548 for(xmlpp::Element::NodeList::iterator i = list.begin(); i != list.end(); i++)
549 if (dynamic_cast<xmlpp::TextNode*>(*i))
550 s += dynamic_cast<xmlpp::TextNode*>(*i)->get_content();
551 }
552 return s;
553 }
554
555 bool
open_temporary(const String & filename)556 FileSystemTemporary::open_temporary(const String &filename)
557 {
558 assert(empty());
559 discard_changes();
560
561 String tag;
562 String temporary_directory = etl::dirname(filename);
563 String temporary_filename_base = etl::basename(filename);
564
565 size_t tag_begin = temporary_filename_base.find_first_of("_");
566 size_t tag_end = temporary_filename_base.find_last_of("_");
567 if (tag_begin != String::npos && tag_end != String::npos && tag_end - tag_begin > 1)
568 tag = temporary_filename_base.substr(tag_begin + 1, tag_end - tag_begin - 1);
569
570 FileSystem::ReadStream::Handle stream = file_system->get_read_stream(filename);
571 if (!stream) return false;
572 stream = new ZReadStream(stream);
573
574 xmlpp::DomParser parser;
575 parser.parse_stream(*stream);
576 stream.reset();
577 if (!parser) return false;
578
579 xmlpp::Element *root = parser.get_document()->get_root_node();
580 if (root->get_name() != "temporary-file-system") return false;
581
582 xmlpp::Element::NodeList list = root->get_children();
583 for(xmlpp::Element::NodeList::iterator i = list.begin(); i != list.end(); i++)
584 {
585 if ((*i)->get_name() == "meta")
586 {
587 xmlpp::Element::NodeList meta_list = (*i)->get_children();
588 for(xmlpp::Element::NodeList::iterator j = meta_list.begin(); j != meta_list.end(); j++)
589 {
590 if ((*j)->get_name() == "entry")
591 {
592 String key, value;
593 xmlpp::Element::NodeList fields_list = (*j)->get_children();
594 for(xmlpp::Element::NodeList::iterator k = fields_list.begin(); k != fields_list.end(); k++)
595 {
596 if ((*k)->get_name() == "key")
597 key = get_xml_node_text(*k);
598 if ((*k)->get_name() == "value")
599 value = get_xml_node_text(*k);
600 }
601 meta[key] = value;
602 }
603 }
604 }
605
606 if ((*i)->get_name() == "files")
607 {
608 xmlpp::Element::NodeList files_list = (*i)->get_children();
609 for(xmlpp::Element::NodeList::iterator j = files_list.begin(); j != files_list.end(); j++)
610 {
611 if ((*j)->get_name() == "entry")
612 {
613 FileInfo info;
614 xmlpp::Element::NodeList fields_list = (*j)->get_children();
615 for(xmlpp::Element::NodeList::iterator k = fields_list.begin(); k != fields_list.end(); k++)
616 {
617 if ((*k)->get_name() == "name")
618 info.name = fix_slashes(get_xml_node_text(*k));
619 if ((*k)->get_name() == "tmp-basename")
620 info.tmp_filename = get_xml_node_text(*k);
621 if ((*k)->get_name() == "is-directory")
622 info.is_directory = get_xml_node_text(*k) == "true";
623 if ((*k)->get_name() == "is-removed")
624 info.is_removed = get_xml_node_text(*k) == "true";
625 }
626 if (!info.tmp_filename.empty())
627 info.tmp_filename = temporary_directory + ETL_DIRECTORY_SEPARATOR + info.tmp_filename;
628 files[info.name] = info;
629 }
630 }
631 }
632 }
633
634 this->tag = tag;
635 this->temporary_directory = temporary_directory;
636 this->temporary_filename_base = temporary_filename_base;
637 return true;
638 }
639
640 String
generate_indexed_temporary_filename(const FileSystem::Handle & fs,const String & filename)641 FileSystemTemporary::generate_indexed_temporary_filename(const FileSystem::Handle &fs, const String &filename)
642 {
643 String extension = filename_extension(filename);
644 String sans_extension = filename_sans_extension(filename);
645 for(int index = 1; index < 10000; ++index)
646 {
647 String indexed_filename = strprintf("%s_%04d%s", sans_extension.c_str(), index, extension.c_str());
648 if (!fs->is_exists(indexed_filename))
649 return indexed_filename;
650 }
651 assert(false);
652 return String();
653 }
654
655 /* === E N T R Y P O I N T ================================================= */
656
657
658