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