1 #include "preferences/broadcastsettings.h"
2
3 #include <QDir>
4 #include <QFileInfo>
5 #include <QFileInfoList>
6 #include <QStringList>
7
8 #include "broadcast/defs_broadcast.h"
9 #include "defs_urls.h"
10 #include "moc_broadcastsettings.cpp"
11 #include "util/logger.h"
12 #include "util/memory.h"
13
14 namespace {
15 const char* kProfilesSubfolder = "broadcast_profiles";
16 const char* kDefaultProfile = "Connection 1"; // Must be used only when initializing profiles
17 const mixxx::Logger kLogger("BroadcastSettings");
18 } // anonymous namespace
19
BroadcastSettings(UserSettingsPointer pConfig,QObject * parent)20 BroadcastSettings::BroadcastSettings(
21 UserSettingsPointer pConfig, QObject* parent)
22 : QObject(parent),
23 m_pConfig(pConfig),
24 m_profiles() {
25 loadProfiles();
26 }
27
loadProfiles()28 void BroadcastSettings::loadProfiles() {
29 QDir profilesFolder(getProfilesFolder());
30 if (!profilesFolder.exists()) {
31 kLogger.info() << "Profiles folder doesn't exist. Creating it.";
32 profilesFolder.mkpath(profilesFolder.absolutePath());
33 }
34
35 QStringList nameFilters("*.bcp.xml");
36 QFileInfoList files =
37 profilesFolder.entryInfoList(nameFilters, QDir::Files, QDir::Name);
38
39 // If *.bcp.xml files exist in the profiles subfolder, those will be loaded
40 // and instantiated in the class' internal profile list for other by it and
41 // Mixxx subsystems related to Live Broadcasting.
42 // If that directory is empty (common reasons: it has been created by the
43 // code at the beginning, or all profiles were deleted) then a default
44 // profile with default values is created. That case could also mean that
45 // Mixxx has just been upgraded to a new version, so "legacy format" values
46 // has fetched from mixxx.cfg and loaded into the fresh default profile.
47 // It's important to take into account that the "legacy" settings are left
48 // in mixxx.cfg for retro-compatibility during alpha and beta testing.
49
50 if (files.size() > 0) {
51 kLogger.info() << "Found" << files.size() << "profile(s)";
52
53 // Load profiles from filesystem
54 for (const QFileInfo& fileInfo : files) {
55 BroadcastProfilePtr profile =
56 BroadcastProfile::loadFromFile(fileInfo.absoluteFilePath());
57
58 if (profile) {
59 addProfile(profile);
60 }
61 }
62 } else {
63 kLogger.info() << "No profiles found. Creating default profile.";
64
65 BroadcastProfilePtr defaultProfile(
66 new BroadcastProfile(kDefaultProfile));
67 // Upgrade from mixxx.cfg format to XML (if required)
68 loadLegacySettings(defaultProfile);
69
70 addProfile(defaultProfile);
71 saveProfile(defaultProfile);
72 }
73 }
74
addProfile(BroadcastProfilePtr profile)75 bool BroadcastSettings::addProfile(BroadcastProfilePtr profile) {
76 if (!profile) {
77 return false;
78 }
79
80 if (m_profiles.size() >= BROADCAST_MAX_CONNECTIONS) {
81 kLogger.warning() << "addProfile: connection limit reached."
82 << "can't add more than" << QString::number(BROADCAST_MAX_CONNECTIONS)
83 << "connections.";
84 return false;
85 }
86
87 // It is best to avoid using QSharedPointer::data(), especially when
88 // passing it to another function, as it puts the associated pointer
89 // at risk of being manually deleted.
90 // However it's fine with Qt's connect because it can be trusted that
91 // it won't delete the pointer.
92 connect(profile.data(),
93 &BroadcastProfile::profileNameChanged,
94 this,
95 &BroadcastSettings::onProfileNameChanged);
96 connect(profile.data(),
97 &BroadcastProfile::connectionStatusChanged,
98 this,
99 &BroadcastSettings::onConnectionStatusChanged);
100 m_profiles.insert(profile->getProfileName(), BroadcastProfilePtr(profile));
101
102 emit profileAdded(profile);
103 return true;
104 }
105
saveProfile(BroadcastProfilePtr profile)106 bool BroadcastSettings::saveProfile(BroadcastProfilePtr profile) {
107 if (!profile) {
108 return false;
109 }
110
111 QString filename = profile->getLastFilename();
112 if (filename.isEmpty()) {
113 // there was no previous filename, find an unused filename
114 for (int index = 0;; ++index) {
115 if (index > 0) {
116 // add an index to the file name to make it unique
117 filename = filePathForProfile(profile->getProfileName() + QString::number(index));
118 } else {
119 filename = filePathForProfile(profile->getProfileName());
120 }
121
122 QFileInfo fileInfo(filename);
123 if (!fileInfo.exists()) {
124 break;
125 }
126 }
127 }
128
129 return profile->save(filename);
130 }
131
filePathForProfile(const QString & profileName)132 QString BroadcastSettings::filePathForProfile(const QString& profileName) {
133 QString filename = profileName + ".bcp.xml";
134 filename = BroadcastProfile::stripForbiddenChars(filename);
135 return QDir(getProfilesFolder()).absoluteFilePath(filename);
136 }
137
filePathForProfile(BroadcastProfilePtr profile)138 QString BroadcastSettings::filePathForProfile(BroadcastProfilePtr profile) {
139 if (!profile) {
140 return QString();
141 }
142
143 return filePathForProfile(profile->getProfileName());
144 }
145
deleteFileForProfile(BroadcastProfilePtr profile)146 bool BroadcastSettings::deleteFileForProfile(BroadcastProfilePtr profile) {
147 if (!profile) {
148 return false;
149 }
150
151 QString filename = profile->getLastFilename();
152 if (filename.isEmpty()) {
153 // no file was saved, there is no file to delete
154 return false;
155 }
156
157 QFileInfo xmlFile(filename);
158 if (xmlFile.exists()) {
159 return QFile::remove(xmlFile.absoluteFilePath());
160 }
161 return false;
162 }
163
getProfilesFolder()164 QString BroadcastSettings::getProfilesFolder() {
165 QString profilesPath(m_pConfig->getSettingsPath());
166 profilesPath.append(QDir::separator() + QString(kProfilesSubfolder));
167 return profilesPath;
168 }
169
saveAll()170 void BroadcastSettings::saveAll() {
171 for (const auto& kv : qAsConst(m_profiles)) {
172 saveProfile(kv);
173 }
174 emit profilesChanged();
175 }
176
onProfileNameChanged(const QString & oldName,const QString & newName)177 void BroadcastSettings::onProfileNameChanged(const QString& oldName, const QString& newName) {
178 if (!m_profiles.contains(oldName)) {
179 return;
180 }
181
182 BroadcastProfilePtr profile = m_profiles.take(oldName);
183 if (profile) {
184 m_profiles.insert(newName, profile);
185 emit profileRenamed(oldName, profile);
186
187 deleteFileForProfile(profile);
188 saveProfile(profile);
189 }
190 }
191
onConnectionStatusChanged(int newStatus)192 void BroadcastSettings::onConnectionStatusChanged(int newStatus) {
193 Q_UNUSED(newStatus);
194 }
195
profileAt(int index)196 BroadcastProfilePtr BroadcastSettings::profileAt(int index) {
197 auto it = m_profiles.begin() + index;
198 return it != m_profiles.end() ? it.value() : BroadcastProfilePtr(nullptr);
199 }
200
profiles()201 QList<BroadcastProfilePtr> BroadcastSettings::profiles() {
202 return m_profiles.values();
203 }
204
applyModel(BroadcastSettingsModel * pModel)205 void BroadcastSettings::applyModel(BroadcastSettingsModel* pModel) {
206 if (!pModel) {
207 return;
208 }
209 // TODO(Palakis): lock both lists against modifications while syncing
210
211 // Step 1: find profiles to delete from the settings
212 for (auto profileIter = m_profiles.begin(); profileIter != m_profiles.end();) {
213 QString profileName = (*profileIter)->getProfileName();
214 if (!pModel->getProfileByName(profileName)) {
215 // If profile exists in settings but not in the model,
216 // remove the profile from the settings
217 const auto removedProfile = *profileIter;
218 deleteFileForProfile(removedProfile);
219 profileIter = m_profiles.erase(profileIter);
220 emit profileRemoved(removedProfile);
221 } else {
222 ++profileIter;
223 }
224 }
225
226 // Step 2: add new profiles
227 const QList<BroadcastProfilePtr> existingProfiles = pModel->profiles();
228 for (auto profileCopy : existingProfiles) {
229 // Check if profile already exists in settings
230 BroadcastProfilePtr existingProfile =
231 m_profiles.value(profileCopy->getProfileName());
232 if (!existingProfile) {
233 // If no profile with the same name exists, add the new
234 // profile to the settings.
235 // The BroadcastProfile instance is a copy to separate it
236 // from its existence in the model
237 addProfile(profileCopy->valuesCopy());
238 }
239 }
240
241 // Step 3: update existing profiles
242 const QList<BroadcastProfilePtr> allProfiles = pModel->profiles();
243 for (BroadcastProfilePtr profileCopy : allProfiles) {
244 BroadcastProfilePtr actualProfile =
245 m_profiles.value(profileCopy->getProfileName());
246 if (actualProfile) {
247 profileCopy->copyValuesTo(actualProfile);
248 }
249 }
250
251 saveAll();
252 }
253