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) 2026 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 : #pragma once
23 :
24 : #include <cstdint>
25 : #include <optional>
26 : #include <unordered_map>
27 : #include <vector>
28 :
29 : #include <QObject>
30 : #include <QTimer>
31 :
32 : #include "project/iconvertfiletoscoreservice.h"
33 :
34 : #include "async/asyncable.h"
35 : #include "modularity/ioc.h"
36 :
37 : #include "cloud/musescorecom/imusescorecomservice.h"
38 : #include "io/ifilesystem.h"
39 : #include "multiwindows/imultiwindowsprovider.h"
40 : #include "project/iprojectconfiguration.h"
41 :
42 : namespace mu::project {
43 : class ConvertFileToScoreService : public QObject, public IConvertFileToScoreService, public muse::async::Asyncable, public muse::Contextable
44 : {
45 : Q_OBJECT
46 :
47 : public:
48 : muse::GlobalInject<muse::cloud::IMuseScoreComService> museScoreComService;
49 : muse::GlobalInject<muse::io::IFileSystem> fileSystem;
50 : muse::GlobalInject<IProjectConfiguration> configuration;
51 : muse::GlobalInject<muse::mi::IMultiWindowsProvider> multiwindowsProvider;
52 :
53 62 : explicit ConvertFileToScoreService(const muse::modularity::ContextPtr& iocCtx, QObject* parent = nullptr)
54 62 : : QObject(parent), muse::Contextable(iocCtx) {}
55 :
56 : void init();
57 : void resumeConvert();
58 :
59 : const ConvertConfig& config() const override;
60 :
61 : bool isFileSupported(const muse::io::path_t& path) const override;
62 : muse::RetVal<ConvertFilesValidation> validateFiles(const muse::io::paths_t& paths) const override;
63 : muse::Ret validateLink(const QUrl& link) const override;
64 :
65 : muse::RetVal<muse::Progress> uploadToConvert(const ConvertInput& input, const muse::String& convertedScoreName) override;
66 : muse::async::Channel<muse::Ret, WatchedScore> convertFinished() const override;
67 :
68 : muse::ValNt<UploadingScoreList> uploadingScores() const override;
69 : muse::ValNt<WatchedScoreList> watchedScores() const override;
70 : const WatchedScore* watchedScoreById(int scoreId) const override;
71 :
72 : muse::async::Channel<PollingStatus> pollingStatusChanged() const override;
73 : void retryPolling() override;
74 :
75 : void submitReview(int scoreId, ReviewRating rating, const QString& comment = QString()) override;
76 : void submitReviewComment(int scoreId, const QString& comment) override;
77 :
78 : void cancelUpload(int uploadId) override;
79 : void cancelConversion(ConvertType type, int convertId) override;
80 :
81 : protected:
82 : //! NOTE: overridable so tests can fake elapsed time instead of waiting on the real clock
83 : virtual int64_t nowMs() const;
84 :
85 : //! NOTE: protected so tests can trigger a poll directly instead of waiting on the real timer
86 : void poll();
87 :
88 : private:
89 : static constexpr int MIN_RETRY_INTERVAL_MS = 60000;
90 : static constexpr int MAX_POLL_RETRY_DURATION_MS = 10 * 60000; // gives up after ~10 minutes
91 :
92 : void loadWatchedScores();
93 : void saveWatchedScores();
94 :
95 : void removeUpload(int uploadId);
96 : void watch(ConvertType type, int itemId, const muse::String& convertedScoreName);
97 : bool hasAnyScoresToPoll() const;
98 : void resetPollState();
99 : void handlePollFailure(const muse::Ret& ret);
100 : void giveUpPolling(const muse::Ret& ret, muse::secs_t elapsed);
101 : void updateWatchedScores(const muse::cloud::ConvertQueueList& queue, const WatchedScoreList& snapshot);
102 :
103 : void updateStatus(WatchedScore& watched, muse::cloud::ConvertStatus status, muse::cloud::ConvertErrorCode errorCode);
104 :
105 : ConvertConfig m_config;
106 :
107 : QTimer m_timer;
108 : int64_t m_pollRetryStartMs = 0;
109 : int m_pollIntervalMs = MIN_RETRY_INTERVAL_MS;
110 : int m_pollFailureCount = 0;
111 : UploadingScoreList m_uploadingScores;
112 : std::vector<WatchedScore> m_watchedScores;
113 : std::unordered_map<int /*uploadId*/, muse::ProgressPtr> m_uploadProgresses;
114 : int m_lastUploadId = 0;
115 : bool m_pollInProgress = false;
116 : bool m_manualRetryRequested = false;
117 : bool m_isSaving = false;
118 :
119 : muse::async::Notification m_uploadingScoresChanged;
120 : muse::async::Notification m_watchedScoresChanged;
121 : muse::async::Channel<PollingStatus> m_pollingStatusChanged;
122 : muse::async::Channel<muse::Ret, WatchedScore> m_convertFinished;
123 : };
124 : }
|