Inkscape
Vector Graphics Editor
Loading...
Searching...
No Matches
auto-save.cpp
Go to the documentation of this file.
1// SPDX-License-Identifier: GPL-2.0-or-later
2/*
3 * Auto-save
4 *
5 * Copyright (C) 2020 Tavmjong Bah
6 *
7 * Re-write of code formerly in inkscape.cpp and originally written by Jon Cruz and others.
8 *
9 * The contents of this file may be used under the GNU General Public License Version 2 or later.
10 *
11 */
12
13#include <algorithm>
14#include <ctime>
15#include <iomanip>
16#include <iostream>
17#include <string>
18#include <sstream>
19#include <vector>
20#include <glibmm/fileutils.h>
21#include <glibmm/i18n.h> // Internationalization
22#include <glibmm/main.h>
23#include <glibmm/miscutils.h>
24
25#include "auto-save.h"
26#include "document.h"
28#include "preferences.h"
29#include "extension/output.h"
30#include <sigc++/scoped_connection.h>
31#include "io/sys.h"
32#include "xml/repr.h"
33
34#ifdef _WIN32
35#include <process.h>
36typedef int uid_t;
37#define getuid() 0
38#endif
39
40namespace Inkscape {
41
42void
44{
45 _app = app;
46 start();
47}
48
49void
51{
53 static sigc::scoped_connection autosave_connection;
54
55 // Turn off any previous timeout.
56 autosave_connection.disconnect();
57
58 if (prefs->getBool("/options/autosave/enable", true)) {
59 // Turn on autosave (timeout is in seconds).
60 guint32 timeout = std::max(prefs->getInt("/options/autosave/interval", 10), 1) * 60;
61 if (timeout > 60 * 60 * 24) {
62 // Sanity check
63 std::cerr << "AutoSave::start: auto-save interval set to greater than one day. Not enabling." << std::endl;
64 return;
65 }
66 autosave_connection = Glib::signal_timeout().connect_seconds(sigc::mem_fun(*this, &AutoSave::save), timeout);
67 }
68}
69
70bool
72{
73 std::vector<SPDocument *> documents = _app->get_documents();
74 if (documents.empty()) {
75 // Nothing to save!
76 return true;
77 }
78
80
81 // Find/create autosave directory
82 std::string autosave_dir = prefs->getString("/options/autosave/path"); // Filenames should be std::string
83 if (autosave_dir.empty()) {
84 autosave_dir = Glib::build_filename(Glib::get_user_cache_dir(), "inkscape");
85 }
86
87 Glib::RefPtr<Gio::File> dir_file = Gio::File::create_for_path(autosave_dir);
88 if (!dir_file->query_exists()) {
89 if (!dir_file->make_directory_with_parents()) {
90 std::cerr << "InkscapeApplication::document_autosave: Failed to create autosave directory: " << autosave_dir << std::endl;
91 return true;
92 }
93 }
94
95 // Get unique info
96 uid_t uid = getuid(); // Avoid naming conflicts between users
97 int pid = ::getpid(); // Avoid naming conflicts between processes
98
99 // Get time stamp
100 std::time_t time = std::time(nullptr);
101 std::tm tm = *std::localtime(&time);
102 std::stringstream datetime;
103 datetime << std::put_time(&tm, "%Y_%m_%d_%H_%M_%S");
104
105 int docnum = 0;
106 int autosave_max = prefs->getInt("/options/autosave/max", 10);
107 for (auto document : documents) {
108
109 ++docnum; // Give each document a unique number.
110
111 if (document->isModifiedSinceAutoSave()) {
112 std::string base_name = "automatic-save-" + std::to_string(uid);
113
114 // The following we do for each document (rather wasteful...) so that
115 // we make room for each document that needs saving. We probably should
116 // be counting per document and not overall documents.
117
118 // Open directory
119 Glib::Dir directory(autosave_dir);
120 std::vector<std::string> file_names(directory.begin(), directory.end());
121
122 // Sort them so that oldest are last (file name encodes time).
123 std::sort(file_names.begin(), file_names.end(), std::greater<std::string>());
124
125 // Delete oldest files.
126 int count = 0;
127 for (auto &file_name : file_names) {
128 if (file_name.compare(0, base_name.size(), base_name) == 0) {
129 ++count;
130 if (count >= autosave_max) {
131 // Delete (making room for one more).
132 std::string path = Glib::build_filename(autosave_dir, file_name);
133 if (unlink(path.c_str()) == -1) {
134 std::cerr << "InkscapeApplication::document_autosave: Failed to unlink file: "
135 << path << ": " << strerror(errno) << std::endl;
136 }
137 }
138 }
139 }
140
141 // Construct save file path
142 // datetime MUST happen first, otherwise the above sorting will fail
143 std::string filename = base_name + "-" + datetime.str() + "-" + std::to_string(pid) + "-" + std::to_string(docnum) + ".svg";
144 std::string path = Glib::build_filename(autosave_dir, filename.c_str());
145
146 // Try to save the file
147 // Following code needs to be reviewed
148 FILE *file = Inkscape::IO::fopen_utf8name(path.c_str(), "w");
149 gchar *errortext = nullptr;
150 if (file) {
151 try {
152 Inkscape::XML::Node *repr = document->getReprRoot();
153 sp_repr_save_stream(repr->document(), file, SP_SVG_NS_URI);
155 errortext = g_strdup(_("Autosave failed! Could not find inkscape extension to save document."));
157 auto const safeUri = Inkscape::IO::sanitizeString(path.c_str());
158 errortext = g_strdup_printf(_("Autosave failed! File %s could not be saved."), safeUri.c_str());
159 }
160 fclose(file);
161 } else {
162 auto const safeUri = Inkscape::IO::sanitizeString(path.c_str());
163 errortext = g_strdup_printf(_("Autosave failed! File %s could not be saved."), safeUri.c_str());
164 }
165
166 if (errortext) {
167 g_warning("%s", errortext);
168 g_free(errortext);
169 } else {
170 document->setModifiedSinceAutoSaveFalse();
171 }
172 }
173 } // Loop over documents
174
175 return true;
176}
177
178void
183
184} // namespace Inkscape
185
186/*
187 Local Variables:
188 mode:c++
189 c-file-style:"stroustrup"
190 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
191 indent-tabs-mode:nil
192 fill-column:99
193 End:
194*/
195// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :
int uid_t
Definition auto-save.cpp:36
std::vector< SPDocument * > get_documents()
Get a list of open documents (from document map).
void init(InkscapeApplication *app)
Definition auto-save.cpp:43
InkscapeApplication * _app
Definition auto-save.h:42
static void restart()
static AutoSave & getInstance()
Definition auto-save.h:30
Failed because we couldn't find an extension to match the filename.
Definition output.h:36
Generic failure for an undescribed reason.
Definition output.h:34
Preference storage class.
Definition preferences.h:61
bool getBool(Glib::ustring const &pref_path, bool def=false)
Retrieve a Boolean value.
Glib::ustring getString(Glib::ustring const &pref_path, Glib::ustring const &def="")
Retrieve an UTF-8 string.
static Preferences * get()
Access the singleton Preferences object.
int getInt(Glib::ustring const &pref_path, int def=0)
Retrieve an integer.
Interface for refcounted XML nodes.
Definition node.h:80
virtual Document * document()=0
Get the node's associated document.
sigc::scoped_connection timeout
std::string file_name
unsigned int guint32
Glib::ustring sanitizeString(char const *str)
Definition sys.cpp:183
FILE * fopen_utf8name(char const *utf8name, char const *mode)
Open a file with g_fopen().
Definition sys.cpp:72
Helper class to stream background task notifications as a series of messages.
Singleton class to access the preferences file in a convenient way.
void sp_repr_save_stream(Document *doc, FILE *fp, gchar const *default_ns, bool compress, gchar const *const old_href_abs_base, gchar const *const new_href_abs_base)
Definition repr-io.cpp:645
C facade to Inkscape::XML::Node.