Line data Source code
1 : /*
2 : * SPDX-License-Identifier: GPL-3.0-only
3 : * MuseScore-Studio-CLA-applies
4 : *
5 : * MuseScore Studio
6 : * Music Composition & Notation
7 : *
8 : * Copyright (C) 2021 MuseScore Limited and others
9 : *
10 : * This program is free software: you can redistribute it and/or modify
11 : * it under the terms of the GNU General Public License version 3 as
12 : * published by the Free Software Foundation.
13 : *
14 : * This program is distributed in the hope that it will be useful,
15 : * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 : * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 : * GNU General Public License for more details.
18 : *
19 : * You should have received a copy of the GNU General Public License
20 : * along with this program. If not, see <https://www.gnu.org/licenses/>.
21 : */
22 :
23 : #include "projectautosaver.h"
24 :
25 : #include "engraving/infrastructure/mscio.h"
26 :
27 : #include "project/inotationproject.h"
28 :
29 : #include "defer.h"
30 : #include "log.h"
31 :
32 : using namespace muse;
33 : using namespace mu::project;
34 :
35 0 : void ProjectAutoSaver::init()
36 : {
37 0 : m_timer.setSingleShot(true);
38 0 : m_timer.setTimerType(Qt::VeryCoarseTimer);
39 0 : m_timer.setInterval(configuration()->autoSaveIntervalMinutes() * 60000);
40 :
41 0 : QObject::connect(&m_timer, &QTimer::timeout, [this]() { onTrySave(); });
42 :
43 0 : if (configuration()->isAutoSaveEnabled()) {
44 0 : m_timer.start();
45 : }
46 :
47 0 : configuration()->autoSaveEnabledChanged().onReceive(this, [this](bool enabled) {
48 0 : if (enabled != m_timer.isActive()) {
49 0 : if (enabled) {
50 0 : m_timer.start();
51 : } else {
52 0 : m_timer.stop();
53 : }
54 : }
55 0 : });
56 :
57 0 : configuration()->autoSaveIntervalChanged().onReceive(this, [this](int minutes) {
58 0 : m_timer.setInterval(minutes * 60000);
59 0 : });
60 :
61 0 : update();
62 :
63 0 : projectSession()->projectChanged().onNotify(this, [this]() {
64 0 : if (INotationProjectPtr project = currentProject()) {
65 0 : if (project->isNewlyCreated() && !project->isImported()) {
66 0 : Ret ret = project->save(configuration()->newProjectTemporaryPath(), SaveMode::AutoSave);
67 0 : if (!ret) {
68 0 : LOGE() << "[autosave] failed to save project, err: " << ret.toString();
69 0 : return;
70 : }
71 0 : }
72 :
73 0 : project->pathChanged().onNotify(this, [this]() {
74 0 : update();
75 0 : });
76 :
77 0 : project->needSaveChanged().onNotify(this, [this]() {
78 0 : update();
79 0 : });
80 0 : }
81 :
82 0 : update();
83 : });
84 0 : }
85 :
86 0 : bool ProjectAutoSaver::projectHasUnsavedChanges(const muse::io::path_t& projectPath) const
87 : {
88 0 : muse::io::path_t autoSavePath = projectAutoSavePath(projectPath);
89 0 : return fileSystem()->exists(autoSavePath);
90 0 : }
91 :
92 0 : void ProjectAutoSaver::removeProjectUnsavedChanges(const muse::io::path_t& projectPath)
93 : {
94 0 : muse::io::path_t path = projectPath;
95 0 : if (!isAutosaveOfNewlyCreatedProject(projectPath)) {
96 0 : path = projectAutoSavePath(projectPath);
97 : }
98 :
99 0 : fileSystem()->remove(path);
100 0 : }
101 :
102 0 : bool ProjectAutoSaver::isAutosaveOfNewlyCreatedProject(const muse::io::path_t& projectPath) const
103 : {
104 0 : return projectPath == configuration()->newProjectTemporaryPath();
105 : }
106 :
107 0 : muse::io::path_t ProjectAutoSaver::projectOriginalPath(const muse::io::path_t& projectAutoSavePath) const
108 : {
109 0 : IF_ASSERT_FAILED(io::suffix(projectAutoSavePath) == AUTOSAVE_SUFFIX) {
110 0 : return engraving::mainFilePath(projectAutoSavePath);
111 : }
112 :
113 0 : muse::io::path_t withoutAutosaveSuffix = io::filename(projectAutoSavePath, false);
114 :
115 0 : return engraving::mainFilePath(io::absoluteDirpath(projectAutoSavePath).appendingComponent(withoutAutosaveSuffix));
116 0 : }
117 :
118 0 : muse::io::path_t ProjectAutoSaver::projectAutoSavePath(const muse::io::path_t& projectPath) const
119 : {
120 0 : return engraving::containerPath(projectPath).appendingSuffix(AUTOSAVE_SUFFIX);
121 : }
122 :
123 0 : INotationProjectPtr ProjectAutoSaver::currentProject() const
124 : {
125 0 : return projectSession()->project();
126 : }
127 :
128 0 : void ProjectAutoSaver::update()
129 : {
130 0 : TRACEFUNC;
131 :
132 0 : muse::io::path_t newProjectPath;
133 :
134 0 : auto project = currentProject();
135 0 : if (project && project->needAutoSave()) {
136 0 : newProjectPath = projectPath(project);
137 : }
138 :
139 0 : if (!m_lastProjectPathNeedingAutosave.empty()
140 0 : && m_lastProjectPathNeedingAutosave != newProjectPath) {
141 0 : removeProjectUnsavedChanges(m_lastProjectPathNeedingAutosave);
142 : }
143 :
144 0 : m_lastProjectPathNeedingAutosave = newProjectPath;
145 0 : }
146 :
147 0 : void ProjectAutoSaver::onTrySave()
148 : {
149 0 : TRACEFUNC;
150 :
151 0 : DEFER {
152 0 : if (configuration()->isAutoSaveEnabled()) {
153 0 : m_timer.start();
154 : }
155 0 : };
156 :
157 0 : LOGI() << "[autosave] trying to auto save";
158 :
159 0 : INotationProjectPtr project = projectSession()->project();
160 0 : if (!project) {
161 0 : LOGI() << "[autosave] no project";
162 0 : return;
163 : }
164 :
165 0 : if (!project->needAutoSave()) {
166 0 : LOGI() << "[autosave] project does not need save";
167 0 : return;
168 : }
169 :
170 0 : if (!project->canSave()) {
171 0 : LOGI() << "[autosave] project could not be saved";
172 0 : return;
173 : }
174 :
175 0 : muse::io::path_t projectPath = this->projectPath(project);
176 0 : muse::io::path_t savePath = project->isNewlyCreated() ? projectPath : projectAutoSavePath(projectPath);
177 :
178 0 : Ret ret = project->save(savePath, SaveMode::AutoSave);
179 0 : if (!ret) {
180 0 : LOGE() << "[autosave] failed to save project, err: " << ret.toString();
181 0 : return;
182 : }
183 :
184 0 : project->setNeedAutoSave(false);
185 :
186 0 : LOGI() << "[autosave] successfully saved project";
187 0 : }
188 :
189 0 : muse::io::path_t ProjectAutoSaver::projectPath(INotationProjectPtr project) const
190 : {
191 0 : return project->isNewlyCreated() ? configuration()->newProjectTemporaryPath() : project->path();
192 : }
|