1 /*
2 * Cantata
3 *
4 * Copyright (c) 2011-2020 Craig Drummond <craig.p.drummond@gmail.com>
5 *
6 * ----
7 *
8 * This program is free software; you can redistribute it and/or modify
9 * it under the terms of the GNU General Public License as published by
10 * the Free Software Foundation; either version 2 of the License, or
11 * (at your option) any later version.
12 *
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16 * General Public License for more details.
17 *
18 * You should have received a copy of the GNU General Public License
19 * along with this program; see the file COPYING. If not, write to
20 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
21 * Boston, MA 02110-1301, USA.
22 */
23
24 #include "umsdevice.h"
25 #include "support/utils.h"
26 #include "support/monoicon.h"
27 #include "devicepropertiesdialog.h"
28 #include "devicepropertieswidget.h"
29 #include "actiondialog.h"
30 #include <QDir>
31 #include <QFile>
32 #include <QFileInfo>
33 #include <QTextStream>
34 #include "solid-lite/storagedrive.h"
35
36 static const QLatin1String constSettingsFile("/.is_audio_player");
37 static const QLatin1String constMusicFolderKey("audio_folder");
38 static const QLatin1String constCollectionNameKey("collection_name");
39
UmsDevice(MusicLibraryModel * m,Solid::Device & dev)40 UmsDevice::UmsDevice(MusicLibraryModel *m, Solid::Device &dev)
41 : FsDevice(m, dev)
42 , access(dev.as<Solid::StorageAccess>())
43 {
44 spaceInfo.setPath(access->filePath());
45
46 QString details=QLatin1String(" (")+Utils::formatByteSize(spaceInfo.size());
47
48 QStringList udiParts=dev.udi().split(QLatin1Char('/'), QString::SkipEmptyParts);
49 if (udiParts.length()>1) {
50 details+=QLatin1String(" - ")+udiParts.last();
51 }
52
53 if (!details.isEmpty()) {
54 details+=QLatin1Char(')');
55 }
56
57 defaultName=data()+details;
58 setData(defaultName);
59 setup();
60 icn=MonoIcon::icon(FontAwesome::usb, Utils::monoIconColor());
61 }
62
~UmsDevice()63 UmsDevice::~UmsDevice()
64 {
65 }
66
connectionStateChanged()67 void UmsDevice::connectionStateChanged()
68 {
69 if (isConnected()) {
70 spaceInfo.setPath(access->filePath());
71 setup();
72 if (opts.autoScan || scanned){ // Only scan if we are set to auto scan, or we have already scanned before...
73 rescan(false); // Read from cache if we have it!
74 } else {
75 setStatusMessage(tr("Not Scanned"));
76 }
77 } else {
78 clear();
79 }
80 }
81
toggle()82 void UmsDevice::toggle()
83 {
84 if (solidDev.isValid() && access && access->isValid()) {
85 if (access->isAccessible()) {
86 stopScanner();
87 access->teardown();
88 } else {
89 access->setup();
90 }
91 }
92 }
93
isConnected() const94 bool UmsDevice::isConnected() const
95 {
96 return solidDev.isValid() && access && access->isValid() && access->isAccessible();
97 }
98
usedCapacity()99 double UmsDevice::usedCapacity()
100 {
101 if (cacheProgress>-1) {
102 return (cacheProgress*1.0)/100.0;
103 }
104 if (!isConnected()) {
105 return -1.0;
106 }
107
108 return spaceInfo.size()>0 ? (spaceInfo.used()*1.0)/(spaceInfo.size()*1.0) : -1.0;
109 }
110
capacityString()111 QString UmsDevice::capacityString()
112 {
113 if (cacheProgress>-1) {
114 return statusMessage();
115 }
116 if (!isConnected()) {
117 return tr("Not Connected");
118 }
119
120 return tr("%1 free").arg(Utils::formatByteSize(spaceInfo.size()-spaceInfo.used()));
121 }
122
freeSpace()123 qint64 UmsDevice::freeSpace()
124 {
125 if (!isConnected()) {
126 return 0;
127 }
128
129 return spaceInfo.size()-spaceInfo.used();
130 }
131
setup()132 void UmsDevice::setup()
133 {
134 if (!isConnected()) {
135 return;
136 }
137
138 QString path=spaceInfo.path();
139 audioFolder = path;
140
141 QFile file(path+constSettingsFile);
142 QString audioFolderSetting;
143 QString n=data();
144 bool haveOpts=FsDevice::readOpts(path+constCantataSettingsFile, opts, false);
145
146 if (file.open(QIODevice::ReadOnly|QIODevice::Text)) {
147 configured=true;
148 QTextStream in(&file);
149 while (!in.atEnd()) {
150 QString line = in.readLine();
151 if (line.startsWith(constMusicFolderKey+"=")) {
152 audioFolderSetting=audioFolder=Utils::cleanPath(path+'/'+line.section('=', 1, 1));
153 if (!QDir(audioFolder).exists()) {
154 audioFolder = path;
155 }
156 } else if (line.startsWith(constMusicFilenameSchemeKey+"=")) {
157 QString scheme = line.section('=', 1, 1);
158 //protect against empty setting.
159 if( !scheme.isEmpty() ) {
160 opts.scheme = scheme;
161 }
162 } else if (line.startsWith(constVfatSafeKey+"=")) {
163 opts.vfatSafe = QLatin1String("true")==line.section('=', 1, 1);
164 } else if (line.startsWith(constAsciiOnlyKey+"=")) {
165 opts.asciiOnly = QLatin1String("true")==line.section('=', 1, 1);
166 } else if (line.startsWith(constIgnoreTheKey+"=")) {
167 opts.ignoreThe = QLatin1String("true")==line.section('=', 1, 1);
168 } else if (line.startsWith(constReplaceSpacesKey+"=")) {
169 opts.replaceSpaces = QLatin1String("true")==line.section('=', 1, 1);
170 } else if (line.startsWith(constCollectionNameKey+"=")) {
171 opts.name = line.section('=', 1, 1).trimmed();
172 } else {
173 unusedParams+=line;
174 }
175 }
176 }
177
178 if (!configured) {
179 configured=haveOpts;
180 }
181
182 if (opts.coverName.isEmpty()) {
183 opts.coverName=constDefCoverFileName;
184 }
185
186 // No setting, see if any standard dirs exist in path...
187 if (audioFolderSetting.isEmpty() || audioFolderSetting!=audioFolder) {
188 QStringList dirs;
189 dirs << QLatin1String("Music") << QLatin1String("MUSIC")
190 << QLatin1String("Albums") << QLatin1String("ALBUMS");
191
192 for (const QString &d: dirs) {
193 if (QDir(path+d).exists()) {
194 audioFolder=path+d;
195 break;
196 }
197 }
198 }
199
200 if (!audioFolder.endsWith('/')) {
201 audioFolder+='/';
202 }
203
204 if (opts.autoScan || scanned){ // Only scan if we are set to auto scan, or we have already scanned before...
205 rescan(false); // Read from cache if we have it!
206 } else {
207 setStatusMessage(tr("Not Scanned"));
208 }
209 if (!opts.name.isEmpty() && opts.name!=n) {
210 setData(opts.name);
211 emit renamed();
212 }
213 }
214
configure(QWidget * parent)215 void UmsDevice::configure(QWidget *parent)
216 {
217 if (!isIdle()) {
218 return;
219 }
220
221 DevicePropertiesDialog *dlg=new DevicePropertiesDialog(parent);
222 connect(dlg, SIGNAL(updatedSettings(const QString &, const DeviceOptions &)), SLOT(saveProperties(const QString &, const DeviceOptions &)));
223 if (!configured) {
224 connect(dlg, SIGNAL(cancelled()), SLOT(saveProperties()));
225 }
226 DeviceOptions o=opts;
227 if (o.name.isEmpty()) {
228 o.name=data();
229 }
230 dlg->show(audioFolder, o, DevicePropertiesWidget::Prop_All,
231 qobject_cast<ActionDialog *>(parent) ? DevicePropertiesWidget::Prop_Folder : 0);
232 }
233
saveProperties()234 void UmsDevice::saveProperties()
235 {
236 saveProperties(audioFolder, opts);
237 }
238
toString(bool b)239 static inline QString toString(bool b)
240 {
241 return b ? QLatin1String("true") : QLatin1String("false");
242 }
243
saveOptions()244 void UmsDevice::saveOptions()
245 {
246 if (!isConnected()) {
247 return;
248 }
249
250 QString path=spaceInfo.path();
251 QFile file(path+constSettingsFile);
252 QString fixedPath(audioFolder);
253
254 if (fixedPath.startsWith(path)) {
255 fixedPath=QLatin1String("./")+fixedPath.mid(path.length());
256 }
257
258 DeviceOptions def;
259 if (file.open(QIODevice::WriteOnly|QIODevice::Text)) {
260 QTextStream out(&file);
261 if (!fixedPath.isEmpty()) {
262 out << constMusicFolderKey << '=' << fixedPath << '\n';
263 }
264 if (opts.scheme!=def.scheme) {
265 out << constMusicFilenameSchemeKey << '=' << opts.scheme << '\n';
266 }
267 if (opts.scheme!=def.scheme) {
268 out << constVfatSafeKey << '=' << toString(opts.vfatSafe) << '\n';
269 }
270 if (opts.asciiOnly!=def.asciiOnly) {
271 out << constAsciiOnlyKey << '=' << toString(opts.asciiOnly) << '\n';
272 }
273 if (opts.ignoreThe!=def.ignoreThe) {
274 out << constIgnoreTheKey << '=' << toString(opts.ignoreThe) << '\n';
275 }
276 if (opts.replaceSpaces!=def.replaceSpaces) {
277 out << constReplaceSpacesKey << '=' << toString(opts.replaceSpaces) << '\n';
278 }
279 if (!opts.name.isEmpty() && opts.name!=defaultName) {
280 out << constCollectionNameKey << '=' << opts.name << '\n';
281 }
282
283 for (const QString &u: unusedParams) {
284 out << u << '\n';
285 }
286 }
287 }
288
saveProperties(const QString & newPath,const DeviceOptions & newOpts)289 void UmsDevice::saveProperties(const QString &newPath, const DeviceOptions &newOpts)
290 {
291 QString nPath=Utils::fixPath(newPath);
292 if (configured && opts==newOpts && nPath==audioFolder) {
293 return;
294 }
295
296 configured=true;
297 QString newName=newOpts.name.isEmpty() ? defaultName : newOpts.name;
298 bool diffName=opts.name!=newName;
299 bool diffCacheSettings=opts.useCache!=newOpts.useCache;
300 opts=newOpts;
301 if (diffName) {
302 setData(newName);
303 }
304 if (diffCacheSettings) {
305 if (opts.useCache) {
306 saveCache();
307 } else {
308 removeCache();
309 }
310 }
311 emit configurationChanged();
312
313 QString oldPath=audioFolder;
314 if (!isConnected()) {
315 return;
316 }
317
318 audioFolder=nPath;
319 saveOptions();
320
321 FsDevice::writeOpts(spaceInfo.path()+constCantataSettingsFile, opts, false);
322
323 if (oldPath!=audioFolder) {
324 rescan(); // Path changed, so we can ignore cache...
325 }
326 if (diffName) {
327 emit renamed();
328 }
329 }
330
331 #include "moc_umsdevice.cpp"
332