diff options
Diffstat (limited to 'utils/rbutilqt/base')
86 files changed, 12727 insertions, 0 deletions
diff --git a/utils/rbutilqt/base/archiveutil.cpp b/utils/rbutilqt/base/archiveutil.cpp new file mode 100644 index 0000000000..d5f0a12471 --- /dev/null +++ b/utils/rbutilqt/base/archiveutil.cpp | |||
@@ -0,0 +1,30 @@ | |||
1 | /*************************************************************************** | ||
2 | * __________ __ ___. | ||
3 | * Open \______ \ ____ ____ | | _\_ |__ _______ ___ | ||
4 | * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / | ||
5 | * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < | ||
6 | * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ | ||
7 | * \/ \/ \/ \/ \/ | ||
8 | * | ||
9 | * Copyright (C) 2013 Amaury Pouly | ||
10 | * | ||
11 | * All files in this archive are subject to the GNU General Public License. | ||
12 | * See the file COPYING in the source tree root for full license agreement. | ||
13 | * | ||
14 | * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY | ||
15 | * KIND, either express or implied. | ||
16 | * | ||
17 | ****************************************************************************/ | ||
18 | |||
19 | #include <QtCore> | ||
20 | #include <QDebug> | ||
21 | #include "archiveutil.h" | ||
22 | |||
23 | ArchiveUtil::ArchiveUtil(QObject* parent) | ||
24 | :QObject(parent) | ||
25 | { | ||
26 | } | ||
27 | |||
28 | ArchiveUtil::~ArchiveUtil() | ||
29 | { | ||
30 | } | ||
diff --git a/utils/rbutilqt/base/archiveutil.h b/utils/rbutilqt/base/archiveutil.h new file mode 100644 index 0000000000..76616728c3 --- /dev/null +++ b/utils/rbutilqt/base/archiveutil.h | |||
@@ -0,0 +1,41 @@ | |||
1 | /*************************************************************************** | ||
2 | * __________ __ ___. | ||
3 | * Open \______ \ ____ ____ | | _\_ |__ _______ ___ | ||
4 | * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / | ||
5 | * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < | ||
6 | * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ | ||
7 | * \/ \/ \/ \/ \/ | ||
8 | * | ||
9 | * Copyright (C) 2013 Amaury Pouly | ||
10 | * | ||
11 | * All files in this archive are subject to the GNU General Public License. | ||
12 | * See the file COPYING in the source tree root for full license agreement. | ||
13 | * | ||
14 | * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY | ||
15 | * KIND, either express or implied. | ||
16 | * | ||
17 | ****************************************************************************/ | ||
18 | |||
19 | #ifndef ARCHIVEUTIL_H | ||
20 | #define ARCHIVEUTIL_H | ||
21 | |||
22 | #include <QtCore> | ||
23 | |||
24 | class ArchiveUtil : public QObject | ||
25 | { | ||
26 | Q_OBJECT | ||
27 | |||
28 | public: | ||
29 | ArchiveUtil(QObject* parent); | ||
30 | ~ArchiveUtil(); | ||
31 | virtual bool close(void) = 0; | ||
32 | virtual bool extractArchive(const QString& dest, QString file = "") = 0; | ||
33 | virtual QStringList files(void) = 0; | ||
34 | |||
35 | signals: | ||
36 | void logProgress(int, int); | ||
37 | void logItem(QString, int); | ||
38 | }; | ||
39 | #endif | ||
40 | |||
41 | |||
diff --git a/utils/rbutilqt/base/autodetection.cpp b/utils/rbutilqt/base/autodetection.cpp new file mode 100644 index 0000000000..341f219c30 --- /dev/null +++ b/utils/rbutilqt/base/autodetection.cpp | |||
@@ -0,0 +1,376 @@ | |||
1 | /*************************************************************************** | ||
2 | * __________ __ ___. | ||
3 | * Open \______ \ ____ ____ | | _\_ |__ _______ ___ | ||
4 | * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / | ||
5 | * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < | ||
6 | * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ | ||
7 | * \/ \/ \/ \/ \/ | ||
8 | * | ||
9 | * Copyright (C) 2007 by Dominik Wenger | ||
10 | * | ||
11 | * All files in this archive are subject to the GNU General Public License. | ||
12 | * See the file COPYING in the source tree root for full license agreement. | ||
13 | * | ||
14 | * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY | ||
15 | * KIND, either express or implied. | ||
16 | * | ||
17 | ****************************************************************************/ | ||
18 | |||
19 | #include <QtCore> | ||
20 | #include "autodetection.h" | ||
21 | #include "rbsettings.h" | ||
22 | #include "playerbuildinfo.h" | ||
23 | |||
24 | #include "../ipodpatcher/ipodpatcher.h" | ||
25 | #include "../sansapatcher/sansapatcher.h" | ||
26 | |||
27 | |||
28 | #include "system.h" | ||
29 | #include "utils.h" | ||
30 | #include "rockboxinfo.h" | ||
31 | #include "Logger.h" | ||
32 | |||
33 | Autodetection::Autodetection(QObject* parent): QObject(parent) | ||
34 | { | ||
35 | } | ||
36 | |||
37 | |||
38 | bool Autodetection::detect(void) | ||
39 | { | ||
40 | QMap<PlayerStatus, QString> states; | ||
41 | states[PlayerOk] = "Ok"; | ||
42 | states[PlayerAmbiguous] = "Ambiguous"; | ||
43 | states[PlayerError] = "Error"; | ||
44 | states[PlayerIncompatible] = "Incompatible"; | ||
45 | states[PlayerMtpMode] = "MtpMode"; | ||
46 | |||
47 | // clear detection state | ||
48 | m_detected.clear(); | ||
49 | |||
50 | detectUsb(); | ||
51 | mergeMounted(); | ||
52 | mergePatcher(); | ||
53 | // if any entry with usbdevices containing a value is left that entry | ||
54 | // hasn't been merged later. This indicates a problem during detection | ||
55 | // (ambiguous player but refining it failed). In this case create an entry | ||
56 | // for eacho of those so the user can select. | ||
57 | QList<struct Detected> detected; | ||
58 | for(int i = 0; i < m_detected.size(); ++i) { | ||
59 | int j = m_detected.at(i).usbdevices.size(); | ||
60 | if(j > 0) { | ||
61 | struct Detected entry = m_detected.at(i); | ||
62 | while(j--) { | ||
63 | struct Detected d; | ||
64 | d.device = entry.usbdevices.at(j); | ||
65 | d.mountpoint = entry.mountpoint; | ||
66 | d.status = PlayerAmbiguous; | ||
67 | detected.append(d); | ||
68 | } | ||
69 | } | ||
70 | else { | ||
71 | detected.append(m_detected.at(i)); | ||
72 | } | ||
73 | } | ||
74 | m_detected = detected; | ||
75 | for(int i = 0; i < m_detected.size(); ++i) { | ||
76 | LOG_INFO() << "Detected player:" << m_detected.at(i).device | ||
77 | << "at" << m_detected.at(i).mountpoint | ||
78 | << states[m_detected.at(i).status]; | ||
79 | } | ||
80 | |||
81 | return m_detected.size() > 0; | ||
82 | } | ||
83 | |||
84 | |||
85 | /** @brief detect devices based on usb pid / vid. | ||
86 | */ | ||
87 | void Autodetection::detectUsb() | ||
88 | { | ||
89 | // usb pid detection | ||
90 | QList<uint32_t> attached; | ||
91 | attached = System::listUsbIds(); | ||
92 | |||
93 | int i = attached.size(); | ||
94 | while(i--) { | ||
95 | QStringList a = PlayerBuildInfo::instance()->value(PlayerBuildInfo::UsbIdTargetList, attached.at(i)).toStringList(); | ||
96 | if(a.size() > 0) { | ||
97 | struct Detected d; | ||
98 | d.status = PlayerOk; | ||
99 | d.usbdevices = a; | ||
100 | m_detected.append(d); | ||
101 | LOG_INFO() << "[USB] detected supported player" << d.usbdevices; | ||
102 | } | ||
103 | QStringList b = PlayerBuildInfo::instance()->value(PlayerBuildInfo::UsbIdErrorList, attached.at(i)).toStringList(); | ||
104 | if(b.size() > 0) { | ||
105 | struct Detected d; | ||
106 | d.status = PlayerMtpMode; | ||
107 | d.usbdevices = b; | ||
108 | m_detected.append(d); | ||
109 | LOG_WARNING() << "[USB] detected problem with player" << d.device; | ||
110 | } | ||
111 | QString idstring = QString("%1").arg(attached.at(i), 8, 16, QChar('0')); | ||
112 | if(!PlayerBuildInfo::instance()->value( | ||
113 | PlayerBuildInfo::DisplayName, idstring).toString().isEmpty()) { | ||
114 | struct Detected d; | ||
115 | d.status = PlayerIncompatible; | ||
116 | d.device = idstring; | ||
117 | m_detected.append(d); | ||
118 | LOG_WARNING() << "[USB] detected incompatible player" << d.device; | ||
119 | } | ||
120 | } | ||
121 | } | ||
122 | |||
123 | |||
124 | // Merge players detected by checking mounted filesystems for known files: | ||
125 | // - rockbox-info.txt / rbutil.log | ||
126 | // - player specific files | ||
127 | void Autodetection::mergeMounted(void) | ||
128 | { | ||
129 | QStringList mounts = Utils::mountpoints(Utils::MountpointsSupported); | ||
130 | LOG_INFO() << "paths to check:" << mounts; | ||
131 | |||
132 | for(int i = 0; i < mounts.size(); i++) | ||
133 | { | ||
134 | // do the file checking | ||
135 | QDir dir(mounts.at(i)); | ||
136 | if(dir.exists()) | ||
137 | { | ||
138 | // check logfile first. | ||
139 | if(QFile(mounts.at(i) + "/.rockbox/rbutil.log").exists()) { | ||
140 | QSettings log(mounts.at(i) + "/.rockbox/rbutil.log", | ||
141 | QSettings::IniFormat, this); | ||
142 | if(!log.value("platform").toString().isEmpty()) { | ||
143 | struct Detected d; | ||
144 | d.device = log.value("platform").toString(); | ||
145 | d.mountpoint = mounts.at(i); | ||
146 | d.status = PlayerOk; | ||
147 | updateDetectedDevice(d); | ||
148 | LOG_INFO() << "rbutil.log detected:" | ||
149 | << log.value("platform").toString() << mounts.at(i); | ||
150 | } | ||
151 | } | ||
152 | |||
153 | // check rockbox-info.txt afterwards. | ||
154 | RockboxInfo info(mounts.at(i)); | ||
155 | if(info.success()) | ||
156 | { | ||
157 | struct Detected d; | ||
158 | d.device = info.target(); | ||
159 | d.mountpoint = mounts.at(i); | ||
160 | d.status = PlayerOk; | ||
161 | updateDetectedDevice(d); | ||
162 | LOG_INFO() << "rockbox-info.txt detected:" | ||
163 | << info.target() << mounts.at(i); | ||
164 | } | ||
165 | |||
166 | // check for some specific files in root folder | ||
167 | QDir root(mounts.at(i)); | ||
168 | QStringList rootentries = root.entryList(QDir::Files); | ||
169 | if(rootentries.contains("archos.mod", Qt::CaseInsensitive)) | ||
170 | { | ||
171 | // archos.mod in root folder -> Archos Player | ||
172 | struct Detected d; | ||
173 | d.device = "player"; | ||
174 | d.mountpoint = mounts.at(i); | ||
175 | d.status = PlayerOk; | ||
176 | updateDetectedDevice(d); | ||
177 | } | ||
178 | if(rootentries.contains("ONDIOST.BIN", Qt::CaseInsensitive)) | ||
179 | { | ||
180 | // ONDIOST.BIN in root -> Ondio FM | ||
181 | struct Detected d; | ||
182 | d.device = "ondiofm"; | ||
183 | d.mountpoint = mounts.at(i); | ||
184 | d.status = PlayerOk; | ||
185 | updateDetectedDevice(d); | ||
186 | } | ||
187 | if(rootentries.contains("ONDIOSP.BIN", Qt::CaseInsensitive)) | ||
188 | { | ||
189 | // ONDIOSP.BIN in root -> Ondio SP | ||
190 | struct Detected d; | ||
191 | d.device = "ondiosp"; | ||
192 | d.mountpoint = mounts.at(i); | ||
193 | d.status = PlayerOk; | ||
194 | updateDetectedDevice(d); | ||
195 | } | ||
196 | if(rootentries.contains("ajbrec.ajz", Qt::CaseInsensitive)) | ||
197 | { | ||
198 | LOG_INFO() << "ajbrec.ajz found. Trying detectAjbrec()"; | ||
199 | struct Detected d; | ||
200 | d.device = detectAjbrec(mounts.at(i)); | ||
201 | d.mountpoint = mounts.at(i); | ||
202 | d.status = PlayerOk; | ||
203 | if(!d.device.isEmpty()) { | ||
204 | LOG_INFO() << d.device; | ||
205 | updateDetectedDevice(d); | ||
206 | } | ||
207 | } | ||
208 | // detection based on player specific folders | ||
209 | QStringList rootfolders = root.entryList(QDir::Dirs | ||
210 | | QDir::NoDotAndDotDot | QDir::Hidden | QDir::System); | ||
211 | if(rootfolders.contains("GBSYSTEM", Qt::CaseInsensitive)) | ||
212 | { | ||
213 | // GBSYSTEM folder -> Gigabeat | ||
214 | struct Detected d; | ||
215 | d.device = "gigabeatf"; | ||
216 | d.mountpoint = mounts.at(i); | ||
217 | updateDetectedDevice(d); | ||
218 | } | ||
219 | } | ||
220 | } | ||
221 | #if 0 | ||
222 | // Ipods have a folder "iPod_Control" in the root. | ||
223 | for(int i = 0; i < m_detected.size(); ++i) { | ||
224 | struct Detected entry = m_detected.at(i); | ||
225 | for(int j = 0; j < entry.usbdevices.size(); ++j) { | ||
226 | // limit this to Ipods only. | ||
227 | if(!entry.usbdevices.at(j).startsWith("ipod") | ||
228 | && !entry.device.startsWith("ipod")) { | ||
229 | continue; | ||
230 | } | ||
231 | // look for iPod_Control on all supported volumes. | ||
232 | for(int k = 0; k < mounts.size(); k++) { | ||
233 | QDir root(mounts.at(k)); | ||
234 | QStringList rootfolders = root.entryList(QDir::Dirs | ||
235 | | QDir::NoDotAndDotDot | QDir::Hidden | QDir::System); | ||
236 | if(rootfolders.contains("iPod_Control", Qt::CaseInsensitive)) { | ||
237 | entry.mountpoint = mounts.at(k); | ||
238 | m_detected.takeAt(i); | ||
239 | m_detected.append(entry); | ||
240 | } | ||
241 | } | ||
242 | } | ||
243 | } | ||
244 | #endif | ||
245 | |||
246 | } | ||
247 | |||
248 | |||
249 | void Autodetection::mergePatcher(void) | ||
250 | { | ||
251 | int n; | ||
252 | // try ipodpatcher | ||
253 | // initialize sector buffer. Needed. | ||
254 | struct ipod_t ipod; | ||
255 | ipod.sectorbuf = nullptr; | ||
256 | ipod_alloc_buffer(&ipod, BUFFER_SIZE); | ||
257 | n = ipod_scan(&ipod); | ||
258 | // FIXME: handle more than one Ipod connected in ipodpatcher. | ||
259 | if(n == 1) { | ||
260 | LOG_INFO() << "Ipod found:" << ipod.modelstr << "at" << ipod.diskname; | ||
261 | // since resolveMountPoint is doing exact matches we need to select | ||
262 | // the correct partition. | ||
263 | QString mp(ipod.diskname); | ||
264 | #ifdef Q_OS_LINUX | ||
265 | mp.append("2"); | ||
266 | #endif | ||
267 | #ifdef Q_OS_MACX | ||
268 | mp.append("s2"); | ||
269 | #endif | ||
270 | struct Detected d; | ||
271 | d.device = ipod.targetname; | ||
272 | d.mountpoint = Utils::resolveMountPoint(mp); | ||
273 | // if the found ipod is a macpod also notice it as device with problem. | ||
274 | if(ipod.macpod) | ||
275 | d.status = PlayerWrongFilesystem; | ||
276 | else | ||
277 | d.status = PlayerOk; | ||
278 | updateDetectedDevice(d); | ||
279 | } | ||
280 | else { | ||
281 | LOG_INFO() << "ipodpatcher: no Ipod found." << n; | ||
282 | } | ||
283 | ipod_dealloc_buffer(&ipod); | ||
284 | |||
285 | // try sansapatcher | ||
286 | // initialize sector buffer. Needed. | ||
287 | struct sansa_t sansa; | ||
288 | sansa_alloc_buffer(&sansa, BUFFER_SIZE); | ||
289 | n = sansa_scan(&sansa); | ||
290 | if(n == 1) { | ||
291 | LOG_INFO() << "Sansa found:" | ||
292 | << sansa.targetname << "at" << sansa.diskname; | ||
293 | QString mp(sansa.diskname); | ||
294 | #ifdef Q_OS_LINUX | ||
295 | mp.append("1"); | ||
296 | #endif | ||
297 | #ifdef Q_OS_MACX | ||
298 | mp.append("s1"); | ||
299 | #endif | ||
300 | struct Detected d; | ||
301 | d.device = QString("sansa%1").arg(sansa.targetname); | ||
302 | d.mountpoint = Utils::resolveMountPoint(mp); | ||
303 | d.status = PlayerOk; | ||
304 | updateDetectedDevice(d); | ||
305 | } | ||
306 | else { | ||
307 | LOG_INFO() << "sansapatcher: no Sansa found." << n; | ||
308 | } | ||
309 | sansa_dealloc_buffer(&sansa); | ||
310 | } | ||
311 | |||
312 | |||
313 | QString Autodetection::detectAjbrec(QString root) | ||
314 | { | ||
315 | QFile f(root + "/ajbrec.ajz"); | ||
316 | char header[24]; | ||
317 | f.open(QIODevice::ReadOnly); | ||
318 | if(!f.read(header, 24)) return QString(); | ||
319 | f.close(); | ||
320 | |||
321 | // check the header of the file. | ||
322 | // recorder v1 had a 6 bytes sized header | ||
323 | // recorder v2, FM, Ondio SP and FM have a 24 bytes header. | ||
324 | |||
325 | // recorder v1 has the binary length in the first 4 bytes, so check | ||
326 | // for them first. | ||
327 | int len = (header[0]<<24) | (header[1]<<16) | (header[2]<<8) | header[3]; | ||
328 | LOG_INFO() << "abjrec.ajz possible bin length:" << len | ||
329 | << "file len:" << f.size(); | ||
330 | if((f.size() - 6) == len) | ||
331 | return "recorder"; | ||
332 | |||
333 | // size didn't match, now we need to assume we have a headerlength of 24. | ||
334 | switch(header[11]) { | ||
335 | case 2: | ||
336 | return "recorderv2"; | ||
337 | case 4: | ||
338 | return "fmrecorder"; | ||
339 | case 8: | ||
340 | return "ondiofm"; | ||
341 | case 16: | ||
342 | return "ondiosp"; | ||
343 | default: | ||
344 | break; | ||
345 | } | ||
346 | return QString(); | ||
347 | } | ||
348 | |||
349 | |||
350 | int Autodetection::findDetectedDevice(QString device) | ||
351 | { | ||
352 | int i = m_detected.size(); | ||
353 | while(i--) { | ||
354 | if(m_detected.at(i).usbdevices.contains(device)) | ||
355 | return i; | ||
356 | } | ||
357 | i = m_detected.size(); | ||
358 | while(i--) { | ||
359 | if(m_detected.at(i).device == device) | ||
360 | return i; | ||
361 | } | ||
362 | return -1; | ||
363 | } | ||
364 | |||
365 | |||
366 | void Autodetection::updateDetectedDevice(Detected& entry) | ||
367 | { | ||
368 | int index = findDetectedDevice(entry.device); | ||
369 | if(index < 0) { | ||
370 | m_detected.append(entry); | ||
371 | } | ||
372 | else { | ||
373 | m_detected.takeAt(index); | ||
374 | m_detected.append(entry); | ||
375 | } | ||
376 | } | ||
diff --git a/utils/rbutilqt/base/autodetection.h b/utils/rbutilqt/base/autodetection.h new file mode 100644 index 0000000000..cdbb94d303 --- /dev/null +++ b/utils/rbutilqt/base/autodetection.h | |||
@@ -0,0 +1,72 @@ | |||
1 | /*************************************************************************** | ||
2 | * __________ __ ___. | ||
3 | * Open \______ \ ____ ____ | | _\_ |__ _______ ___ | ||
4 | * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / | ||
5 | * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < | ||
6 | * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ | ||
7 | * \/ \/ \/ \/ \/ | ||
8 | * | ||
9 | * Copyright (C) 2007 by Dominik Wenger | ||
10 | * | ||
11 | * This program is free software; you can redistribute it and/or | ||
12 | * modify it under the terms of the GNU General Public License | ||
13 | * as published by the Free Software Foundation; either version 2 | ||
14 | * of the License, or (at your option) any later version. | ||
15 | * | ||
16 | * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY | ||
17 | * KIND, either express or implied. | ||
18 | * | ||
19 | ****************************************************************************/ | ||
20 | |||
21 | |||
22 | #ifndef AUTODETECTION_H_ | ||
23 | #define AUTODETECTION_H_ | ||
24 | |||
25 | #include <QObject> | ||
26 | #include <QString> | ||
27 | #include <QList> | ||
28 | #include <QStringList> | ||
29 | |||
30 | class Autodetection :public QObject | ||
31 | { | ||
32 | Q_OBJECT | ||
33 | |||
34 | public: | ||
35 | Autodetection(QObject* parent=nullptr); | ||
36 | |||
37 | enum PlayerStatus { | ||
38 | PlayerOk, | ||
39 | PlayerIncompatible, | ||
40 | PlayerMtpMode, | ||
41 | PlayerWrongFilesystem, | ||
42 | PlayerError, | ||
43 | PlayerAmbiguous, | ||
44 | }; | ||
45 | |||
46 | struct Detected { | ||
47 | QString device; | ||
48 | QStringList usbdevices; | ||
49 | QString mountpoint; | ||
50 | enum PlayerStatus status; | ||
51 | }; | ||
52 | |||
53 | bool detect(); | ||
54 | |||
55 | QList<struct Detected> detected(void) { return m_detected; } | ||
56 | |||
57 | private: | ||
58 | QString resolveMountPoint(QString); | ||
59 | void detectUsb(void); | ||
60 | void mergeMounted(void); | ||
61 | void mergePatcher(void); | ||
62 | QString detectAjbrec(QString); | ||
63 | int findDetectedDevice(QString device); | ||
64 | void updateDetectedDevice(struct Detected& entry); | ||
65 | |||
66 | QList<struct Detected> m_detected; | ||
67 | QList<int> m_usbconid; | ||
68 | }; | ||
69 | |||
70 | |||
71 | #endif /*AUTODETECTION_H_*/ | ||
72 | |||
diff --git a/utils/rbutilqt/base/bootloaderinstallams.cpp b/utils/rbutilqt/base/bootloaderinstallams.cpp new file mode 100644 index 0000000000..be43ed52db --- /dev/null +++ b/utils/rbutilqt/base/bootloaderinstallams.cpp | |||
@@ -0,0 +1,201 @@ | |||
1 | /*************************************************************************** | ||
2 | * __________ __ ___. | ||
3 | * Open \______ \ ____ ____ | | _\_ |__ _______ ___ | ||
4 | * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / | ||
5 | * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < | ||
6 | * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ | ||
7 | * \/ \/ \/ \/ \/ | ||
8 | * | ||
9 | * Copyright (C) 2008 by Dominik Wenger | ||
10 | * | ||
11 | * All files in this archive are subject to the GNU General Public License. | ||
12 | * See the file COPYING in the source tree root for full license agreement. | ||
13 | * | ||
14 | * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY | ||
15 | * KIND, either express or implied. | ||
16 | * | ||
17 | ****************************************************************************/ | ||
18 | |||
19 | #include <QtCore> | ||
20 | #include "bootloaderinstallbase.h" | ||
21 | #include "bootloaderinstallams.h" | ||
22 | #include "Logger.h" | ||
23 | |||
24 | #include "../mkamsboot/mkamsboot.h" | ||
25 | |||
26 | BootloaderInstallAms::BootloaderInstallAms(QObject *parent) | ||
27 | : BootloaderInstallBase(parent) | ||
28 | { | ||
29 | } | ||
30 | |||
31 | QString BootloaderInstallAms::ofHint() | ||
32 | { | ||
33 | return tr("Bootloader installation requires you to provide " | ||
34 | "a copy of the original Sandisk firmware (bin file). " | ||
35 | "This firmware file will be patched and then installed to your " | ||
36 | "player along with the rockbox bootloader. " | ||
37 | "You need to download this file yourself due to legal " | ||
38 | "reasons. Please browse the " | ||
39 | "<a href='http://forums.sandisk.com/sansa/'>Sansa Forums</a> " | ||
40 | "or refer to the " | ||
41 | "<a href='http://www.rockbox.org/manual.shtml'>manual</a> and " | ||
42 | "the <a href='http://www.rockbox.org/wiki/SansaAMS'>SansaAMS</a> " | ||
43 | "wiki page on how to obtain this file.<br/>" | ||
44 | "<b>Note:</b> This file is not present on your player and will " | ||
45 | "disappear automatically after installing it.<br/><br/>" | ||
46 | "Press Ok to continue and browse your computer for the firmware " | ||
47 | "file."); | ||
48 | } | ||
49 | |||
50 | bool BootloaderInstallAms::install(void) | ||
51 | { | ||
52 | if(m_offile.isEmpty()) | ||
53 | return false; | ||
54 | |||
55 | LOG_INFO() << "installing bootloader"; | ||
56 | |||
57 | // download firmware from server | ||
58 | emit logItem(tr("Downloading bootloader file"), LOGINFO); | ||
59 | |||
60 | connect(this, &BootloaderInstallBase::downloadDone, this, &BootloaderInstallAms::installStage2); | ||
61 | downloadBlStart(m_blurl); | ||
62 | |||
63 | return true; | ||
64 | } | ||
65 | |||
66 | void BootloaderInstallAms::installStage2(void) | ||
67 | { | ||
68 | LOG_INFO() << "installStage2"; | ||
69 | |||
70 | unsigned char* buf; | ||
71 | unsigned char* of_packed; | ||
72 | int of_packedsize; | ||
73 | unsigned char* rb_packed; | ||
74 | int rb_packedsize; | ||
75 | off_t len; | ||
76 | struct md5sums sum; | ||
77 | char md5sum[33]; /* 32 hex digits, plus terminating zero */ | ||
78 | int n; | ||
79 | int model; | ||
80 | int firmware_size; | ||
81 | int bootloader_size; | ||
82 | int patchable; | ||
83 | int totalsize; | ||
84 | char errstr[200]; | ||
85 | |||
86 | sum.md5 = md5sum; | ||
87 | |||
88 | m_tempfile.open(); | ||
89 | QString bootfile = m_tempfile.fileName(); | ||
90 | m_tempfile.close(); | ||
91 | |||
92 | /* Load bootloader file */ | ||
93 | rb_packed = load_rockbox_file(bootfile.toLocal8Bit().data(), &model, | ||
94 | &bootloader_size,&rb_packedsize, | ||
95 | errstr,sizeof(errstr)); | ||
96 | if (rb_packed == nullptr) | ||
97 | { | ||
98 | LOG_ERROR() << "could not load bootloader: " << bootfile; | ||
99 | emit logItem(errstr, LOGERROR); | ||
100 | emit logItem(tr("Could not load %1").arg(bootfile), LOGERROR); | ||
101 | emit done(true); | ||
102 | return; | ||
103 | } | ||
104 | |||
105 | /* Load original firmware file */ | ||
106 | buf = load_of_file(m_offile.toLocal8Bit().data(), model, &len, &sum, | ||
107 | &firmware_size, &of_packed ,&of_packedsize, | ||
108 | errstr, sizeof(errstr)); | ||
109 | if (buf == nullptr) | ||
110 | { | ||
111 | LOG_ERROR() << "could not load OF: " << m_offile; | ||
112 | emit logItem(errstr, LOGERROR); | ||
113 | emit logItem(tr("Could not load %1").arg(m_offile), LOGERROR); | ||
114 | free(rb_packed); | ||
115 | emit done(true); | ||
116 | return; | ||
117 | } | ||
118 | |||
119 | /* check total size */ | ||
120 | patchable = check_sizes(sum.model, rb_packedsize, bootloader_size, | ||
121 | of_packedsize, firmware_size, &totalsize, errstr, sizeof(errstr)); | ||
122 | |||
123 | if (!patchable) | ||
124 | { | ||
125 | LOG_ERROR() << "No room to insert bootloader"; | ||
126 | emit logItem(errstr, LOGERROR); | ||
127 | emit logItem(tr("No room to insert bootloader, try another firmware version"), | ||
128 | LOGERROR); | ||
129 | free(buf); | ||
130 | free(of_packed); | ||
131 | free(rb_packed); | ||
132 | emit done(true); | ||
133 | return; | ||
134 | } | ||
135 | |||
136 | /* patch the firmware */ | ||
137 | emit logItem(tr("Patching Firmware..."), LOGINFO); | ||
138 | |||
139 | patch_firmware(sum.model,firmware_revision(sum.model),firmware_size,buf, | ||
140 | len,of_packed,of_packedsize,rb_packed,rb_packedsize); | ||
141 | |||
142 | /* write out file */ | ||
143 | QFile out(m_blfile); | ||
144 | |||
145 | if(!out.open(QIODevice::WriteOnly | QIODevice::Truncate)) | ||
146 | { | ||
147 | LOG_ERROR() << "Could not open" << m_blfile << "for writing"; | ||
148 | emit logItem(tr("Could not open %1 for writing").arg(m_blfile),LOGERROR); | ||
149 | free(buf); | ||
150 | free(of_packed); | ||
151 | free(rb_packed); | ||
152 | emit done(true); | ||
153 | return; | ||
154 | } | ||
155 | |||
156 | n = out.write((char*)buf, len); | ||
157 | |||
158 | if (n != len) | ||
159 | { | ||
160 | LOG_ERROR() << "Could not write firmware file"; | ||
161 | emit logItem(tr("Could not write firmware file"),LOGERROR); | ||
162 | free(buf); | ||
163 | free(of_packed); | ||
164 | free(rb_packed); | ||
165 | emit done(true); | ||
166 | return; | ||
167 | } | ||
168 | |||
169 | out.close(); | ||
170 | |||
171 | free(buf); | ||
172 | free(of_packed); | ||
173 | free(rb_packed); | ||
174 | |||
175 | //end of install | ||
176 | LOG_INFO() << "install successfull"; | ||
177 | emit logItem(tr("Success: modified firmware file created"), LOGINFO); | ||
178 | logInstall(LogAdd); | ||
179 | emit done(false); | ||
180 | return; | ||
181 | } | ||
182 | |||
183 | bool BootloaderInstallAms::uninstall(void) | ||
184 | { | ||
185 | emit logItem(tr("To uninstall, perform a normal upgrade with an unmodified " | ||
186 | "original firmware"), LOGINFO); | ||
187 | logInstall(LogRemove); | ||
188 | emit done(true); | ||
189 | return false; | ||
190 | } | ||
191 | |||
192 | BootloaderInstallBase::BootloaderType BootloaderInstallAms::installed(void) | ||
193 | { | ||
194 | return BootloaderUnknown; | ||
195 | } | ||
196 | |||
197 | BootloaderInstallBase::Capabilities BootloaderInstallAms::capabilities(void) | ||
198 | { | ||
199 | return (Install | NeedsOf); | ||
200 | } | ||
201 | |||
diff --git a/utils/rbutilqt/base/bootloaderinstallams.h b/utils/rbutilqt/base/bootloaderinstallams.h new file mode 100644 index 0000000000..f3ce15ed24 --- /dev/null +++ b/utils/rbutilqt/base/bootloaderinstallams.h | |||
@@ -0,0 +1,42 @@ | |||
1 | /*************************************************************************** | ||
2 | * __________ __ ___. | ||
3 | * Open \______ \ ____ ____ | | _\_ |__ _______ ___ | ||
4 | * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / | ||
5 | * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < | ||
6 | * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ | ||
7 | * \/ \/ \/ \/ \/ | ||
8 | * | ||
9 | * Copyright (C) 2008 by Dominik Wenger | ||
10 | * | ||
11 | * All files in this archive are subject to the GNU General Public License. | ||
12 | * See the file COPYING in the source tree root for full license agreement. | ||
13 | * | ||
14 | * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY | ||
15 | * KIND, either express or implied. | ||
16 | * | ||
17 | ****************************************************************************/ | ||
18 | #ifndef BOOTLOADERINSTALLAMS_H | ||
19 | #define BOOTLOADERINSTALLAMS_H | ||
20 | |||
21 | #include <QtCore> | ||
22 | #include "bootloaderinstallbase.h" | ||
23 | |||
24 | //! bootloader installation derivate based on mkamsboot | ||
25 | class BootloaderInstallAms : public BootloaderInstallBase | ||
26 | { | ||
27 | Q_OBJECT | ||
28 | public: | ||
29 | BootloaderInstallAms(QObject *parent); | ||
30 | bool install(void); | ||
31 | bool uninstall(void); | ||
32 | BootloaderInstallBase::BootloaderType installed(void); | ||
33 | Capabilities capabilities(void); | ||
34 | QString ofHint(); | ||
35 | |||
36 | private: | ||
37 | |||
38 | private slots: | ||
39 | void installStage2(void); | ||
40 | }; | ||
41 | |||
42 | #endif | ||
diff --git a/utils/rbutilqt/base/bootloaderinstallbase.cpp b/utils/rbutilqt/base/bootloaderinstallbase.cpp new file mode 100644 index 0000000000..096c601b91 --- /dev/null +++ b/utils/rbutilqt/base/bootloaderinstallbase.cpp | |||
@@ -0,0 +1,302 @@ | |||
1 | /*************************************************************************** | ||
2 | * __________ __ ___. | ||
3 | * Open \______ \ ____ ____ | | _\_ |__ _______ ___ | ||
4 | * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / | ||
5 | * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < | ||
6 | * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ | ||
7 | * \/ \/ \/ \/ \/ | ||
8 | * | ||
9 | * Copyright (C) 2008 by Dominik Riebeling | ||
10 | * | ||
11 | * All files in this archive are subject to the GNU General Public License. | ||
12 | * See the file COPYING in the source tree root for full license agreement. | ||
13 | * | ||
14 | * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY | ||
15 | * KIND, either express or implied. | ||
16 | * | ||
17 | ****************************************************************************/ | ||
18 | |||
19 | |||
20 | #include <QtCore> | ||
21 | |||
22 | #include "bootloaderinstallbase.h" | ||
23 | #include "utils.h" | ||
24 | #include "ziputil.h" | ||
25 | #include "mspackutil.h" | ||
26 | #include "Logger.h" | ||
27 | |||
28 | #if defined(Q_OS_MACX) | ||
29 | #include <sys/param.h> | ||
30 | #include <sys/ucred.h> | ||
31 | #include <sys/mount.h> | ||
32 | #endif | ||
33 | |||
34 | |||
35 | BootloaderInstallBase::BootloaderType BootloaderInstallBase::installed(void) | ||
36 | { | ||
37 | return BootloaderUnknown; | ||
38 | } | ||
39 | |||
40 | |||
41 | BootloaderInstallBase::Capabilities BootloaderInstallBase::capabilities(void) | ||
42 | { | ||
43 | return Capabilities(); | ||
44 | } | ||
45 | |||
46 | |||
47 | void BootloaderInstallBase::downloadBlStart(QUrl source) | ||
48 | { | ||
49 | m_http.setFile(&m_tempfile); | ||
50 | m_http.setCache(true); | ||
51 | connect(&m_http, &HttpGet::done, this, &BootloaderInstallBase::downloadBlFinish); | ||
52 | // connect the http read signal to our logProgess *signal* | ||
53 | // to immediately emit it without any helper function. | ||
54 | connect(&m_http, &HttpGet::dataReadProgress, | ||
55 | this, &BootloaderInstallBase::logProgress); | ||
56 | m_http.getFile(source); | ||
57 | } | ||
58 | |||
59 | |||
60 | void BootloaderInstallBase::downloadReqFinished(int id, bool error) | ||
61 | { | ||
62 | LOG_INFO() << "Download Request" << id | ||
63 | << "finished, error:" << m_http.errorString(); | ||
64 | |||
65 | downloadBlFinish(error); | ||
66 | } | ||
67 | |||
68 | |||
69 | void BootloaderInstallBase::downloadBlFinish(bool error) | ||
70 | { | ||
71 | LOG_INFO() << "Downloading bootloader finished, error:" | ||
72 | << error; | ||
73 | |||
74 | // update progress bar | ||
75 | emit logProgress(100, 100); | ||
76 | |||
77 | if(m_http.httpResponse() != 200) { | ||
78 | emit logItem(tr("Download error: received HTTP error %1.") | ||
79 | .arg(m_http.errorString()), LOGERROR); | ||
80 | emit done(true); | ||
81 | return; | ||
82 | } | ||
83 | if(error) { | ||
84 | emit logItem(tr("Download error: %1") | ||
85 | .arg(m_http.errorString()), LOGERROR); | ||
86 | emit done(true); | ||
87 | return; | ||
88 | } | ||
89 | else if(m_http.isCached()) | ||
90 | emit logItem(tr("Download finished (cache used)."), LOGOK); | ||
91 | else | ||
92 | emit logItem(tr("Download finished."), LOGOK); | ||
93 | |||
94 | QCoreApplication::processEvents(); | ||
95 | m_blversion = m_http.timestamp(); | ||
96 | emit downloadDone(); | ||
97 | } | ||
98 | |||
99 | |||
100 | void BootloaderInstallBase::installBlfile(void) | ||
101 | { | ||
102 | LOG_INFO() << "installBlFile(void)"; | ||
103 | } | ||
104 | |||
105 | |||
106 | void BootloaderInstallBase::progressAborted(void) | ||
107 | { | ||
108 | LOG_INFO() << "progressAborted(void)"; | ||
109 | emit installAborted(); | ||
110 | } | ||
111 | |||
112 | |||
113 | //! @brief backup OF file. | ||
114 | //! @param to folder to write backup file to. Folder will get created. | ||
115 | //! @return true on success, false on error. | ||
116 | bool BootloaderInstallBase::backup(QString to) | ||
117 | { | ||
118 | LOG_INFO() << "Backing up bootloader file"; | ||
119 | QDir targetDir("."); | ||
120 | emit logItem(tr("Creating backup of original firmware file."), LOGINFO); | ||
121 | if(!targetDir.mkpath(to)) { | ||
122 | emit logItem(tr("Creating backup folder failed"), LOGERROR); | ||
123 | return false; | ||
124 | } | ||
125 | QString tofile = to + "/" + QFileInfo(m_blfile).fileName(); | ||
126 | LOG_INFO() << "trying to backup" << m_blfile << "to" << tofile; | ||
127 | if(!QFile::copy(Utils::resolvePathCase(m_blfile), tofile)) { | ||
128 | emit logItem(tr("Creating backup copy failed."), LOGERROR); | ||
129 | return false; | ||
130 | } | ||
131 | emit logItem(tr("Backup created."), LOGOK); | ||
132 | return true; | ||
133 | } | ||
134 | |||
135 | |||
136 | //! @brief log installation to logfile. | ||
137 | //! @param mode action to perform. 0: add to log, 1: remove from log. | ||
138 | //! @return 0 on success | ||
139 | int BootloaderInstallBase::logInstall(LogMode mode) | ||
140 | { | ||
141 | int result = 0; | ||
142 | QString section = m_blurl.path().section('/', -1); | ||
143 | QSettings s(m_logfile, QSettings::IniFormat, this); | ||
144 | emit logItem(tr("Creating installation log"), LOGINFO); | ||
145 | |||
146 | if(mode == LogAdd) { | ||
147 | s.setValue("Bootloader/" + section, m_blversion.toString(Qt::ISODate)); | ||
148 | LOG_INFO() << "Writing log, version:" | ||
149 | << m_blversion.toString(Qt::ISODate); | ||
150 | } | ||
151 | else { | ||
152 | s.remove("Bootloader/" + section); | ||
153 | } | ||
154 | s.sync(); | ||
155 | |||
156 | emit logItem(tr("Installation log created"), LOGOK); | ||
157 | |||
158 | return result; | ||
159 | } | ||
160 | |||
161 | |||
162 | #if defined(Q_OS_MACX) | ||
163 | void BootloaderInstallBase::waitRemount() | ||
164 | { | ||
165 | m_remountTries = 600; | ||
166 | emit logItem(tr("Waiting for system to remount player"), LOGINFO); | ||
167 | |||
168 | QTimer::singleShot(100, this, SLOT(checkRemount())); | ||
169 | } | ||
170 | #endif | ||
171 | |||
172 | |||
173 | void BootloaderInstallBase::checkRemount() | ||
174 | { | ||
175 | #if defined(Q_OS_MACX) | ||
176 | if(m_remountTries--) { | ||
177 | int status = 0; | ||
178 | // check if device has been remounted | ||
179 | QCoreApplication::processEvents(); | ||
180 | int num; | ||
181 | struct statfs *mntinf; | ||
182 | |||
183 | num = getmntinfo(&mntinf, MNT_WAIT); | ||
184 | while(num--) { | ||
185 | if(QString(mntinf->f_mntfromname).startsWith(m_remountDevice) | ||
186 | && QString(mntinf->f_fstypename).contains("msdos", Qt::CaseInsensitive)) | ||
187 | status = 1; | ||
188 | mntinf++; | ||
189 | } | ||
190 | if(!status) { | ||
191 | // still not remounted, restart timer. | ||
192 | QTimer::singleShot(500, this, SLOT(checkRemount())); | ||
193 | LOG_INFO() << "Player not remounted yet" << m_remountDevice; | ||
194 | } | ||
195 | else { | ||
196 | emit logItem(tr("Player remounted"), LOGINFO); | ||
197 | emit remounted(true); | ||
198 | } | ||
199 | } | ||
200 | else { | ||
201 | emit logItem(tr("Timeout on remount"), LOGERROR); | ||
202 | emit remounted(false); | ||
203 | } | ||
204 | #endif | ||
205 | } | ||
206 | |||
207 | |||
208 | //! @brief set list of possible bootloader files and pick the existing one. | ||
209 | //! @param sl list of possible bootloader files. | ||
210 | void BootloaderInstallBase::setBlFile(QStringList sl) | ||
211 | { | ||
212 | // figue which of the possible bootloader filenames is correct. | ||
213 | for(int a = 0; a < sl.size(); a++) { | ||
214 | if(!Utils::resolvePathCase(sl.at(a)).isEmpty()) { | ||
215 | m_blfile = sl.at(a); | ||
216 | } | ||
217 | } | ||
218 | if(m_blfile.isEmpty()) { | ||
219 | m_blfile = sl.at(0); | ||
220 | } | ||
221 | } | ||
222 | |||
223 | |||
224 | bool BootloaderInstallBase::setOfFile(QString of, QStringList blfile) | ||
225 | { | ||
226 | bool found = false; | ||
227 | ArchiveUtil *util = nullptr; | ||
228 | |||
229 | // check if we're actually looking for a zip file. If so we must avoid | ||
230 | // trying to unzip it. | ||
231 | bool wantZip = false; | ||
232 | for (int i = 0; i < blfile.size(); i++) | ||
233 | { | ||
234 | if (blfile.at(i).endsWith(".zip")) | ||
235 | wantZip = true; | ||
236 | } | ||
237 | |||
238 | // try ZIP first | ||
239 | ZipUtil *zu = new ZipUtil(this); | ||
240 | if(zu->open(of) && !wantZip) | ||
241 | { | ||
242 | emit logItem(tr("Zip file format detected"), LOGINFO); | ||
243 | util = zu; | ||
244 | } | ||
245 | else | ||
246 | delete zu; | ||
247 | |||
248 | // if ZIP failed, try CAB | ||
249 | if(util == nullptr) | ||
250 | { | ||
251 | MsPackUtil *msu = new MsPackUtil(this); | ||
252 | if(msu->open(of)) | ||
253 | { | ||
254 | emit logItem(tr("CAB file format detected"), LOGINFO); | ||
255 | util = msu; | ||
256 | } | ||
257 | else | ||
258 | delete msu; | ||
259 | } | ||
260 | |||
261 | // check if the file set is in zip format | ||
262 | if(util) { | ||
263 | QStringList contents = util->files(); | ||
264 | LOG_INFO() << "archive contains:" << contents; | ||
265 | for(int i = 0; i < blfile.size(); ++i) { | ||
266 | // strip any path, we don't know the structure in the zip | ||
267 | QString f = QFileInfo(blfile.at(i)).fileName(); | ||
268 | LOG_INFO() << "searching archive for" << f; | ||
269 | // contents.indexOf() works case sensitive. Since the filename | ||
270 | // casing is unknown (and might change) do this manually. | ||
271 | // FIXME: support files in folders | ||
272 | for(int j = 0; j < contents.size(); ++j) { | ||
273 | if(contents.at(j).compare(f, Qt::CaseInsensitive) == 0) { | ||
274 | found = true; | ||
275 | emit logItem(tr("Extracting firmware %1 from archive") | ||
276 | .arg(f), LOGINFO); | ||
277 | // store in class temporary file | ||
278 | m_tempof.open(); | ||
279 | m_offile = m_tempof.fileName(); | ||
280 | m_tempof.close(); | ||
281 | if(!util->extractArchive(m_offile, contents.at(j))) { | ||
282 | emit logItem(tr("Error extracting firmware from archive"), LOGERROR); | ||
283 | found = false; | ||
284 | break; | ||
285 | } | ||
286 | break; | ||
287 | } | ||
288 | } | ||
289 | } | ||
290 | if(!found) { | ||
291 | emit logItem(tr("Could not find firmware in archive"), LOGERROR); | ||
292 | } | ||
293 | delete util; | ||
294 | } | ||
295 | else { | ||
296 | m_offile = of; | ||
297 | found = true; | ||
298 | } | ||
299 | |||
300 | return found; | ||
301 | } | ||
302 | |||
diff --git a/utils/rbutilqt/base/bootloaderinstallbase.h b/utils/rbutilqt/base/bootloaderinstallbase.h new file mode 100644 index 0000000000..23aac4f92f --- /dev/null +++ b/utils/rbutilqt/base/bootloaderinstallbase.h | |||
@@ -0,0 +1,118 @@ | |||
1 | /*************************************************************************** | ||
2 | * __________ __ ___. | ||
3 | * Open \______ \ ____ ____ | | _\_ |__ _______ ___ | ||
4 | * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / | ||
5 | * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < | ||
6 | * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ | ||
7 | * \/ \/ \/ \/ \/ | ||
8 | * | ||
9 | * Copyright (C) 2008 by Dominik Riebeling | ||
10 | * | ||
11 | * All files in this archive are subject to the GNU General Public License. | ||
12 | * See the file COPYING in the source tree root for full license agreement. | ||
13 | * | ||
14 | * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY | ||
15 | * KIND, either express or implied. | ||
16 | * | ||
17 | ****************************************************************************/ | ||
18 | |||
19 | #ifndef BOOTLOADERINSTALLBASE_H | ||
20 | #define BOOTLOADERINSTALLBASE_H | ||
21 | |||
22 | #include <QtCore> | ||
23 | #include "progressloggerinterface.h" | ||
24 | #include "httpget.h" | ||
25 | |||
26 | //! baseclass for all Bootloader installs | ||
27 | class BootloaderInstallBase : public QObject | ||
28 | { | ||
29 | Q_OBJECT | ||
30 | public: | ||
31 | enum Capability | ||
32 | { Install = 0x01, Uninstall = 0x02, Backup = 0x04, | ||
33 | IsFile = 0x08, IsRaw = 0x10, NeedsOf = 0x20, | ||
34 | CanCheckInstalled = 0x40, CanCheckVersion = 0x80 }; | ||
35 | Q_DECLARE_FLAGS(Capabilities, Capability) | ||
36 | |||
37 | enum BootloaderType | ||
38 | { BootloaderNone, BootloaderRockbox, BootloaderOther, BootloaderUnknown }; | ||
39 | |||
40 | BootloaderInstallBase(QObject *parent) : QObject(parent) | ||
41 | { } | ||
42 | |||
43 | //! install the bootloader, must be implemented | ||
44 | virtual bool install(void) = 0; | ||
45 | //! uninstall the bootloader, must be implemented | ||
46 | virtual bool uninstall(void) = 0; | ||
47 | //! returns the installed bootloader | ||
48 | virtual BootloaderType installed(void)=0; | ||
49 | //! returns the capabilities of the bootloader class | ||
50 | virtual Capabilities capabilities(void)=0; | ||
51 | //! returns a OF Firmware hint or empty if there is none | ||
52 | virtual QString ofHint() {return QString();} | ||
53 | |||
54 | //! backup a already installed bootloader | ||
55 | bool backup(QString to); | ||
56 | |||
57 | //! set the different filenames and paths | ||
58 | void setBlFile(QStringList f); | ||
59 | void setBlUrl(QUrl u) | ||
60 | { m_blurl = u; } | ||
61 | void setLogfile(QString f) | ||
62 | { m_logfile = f; } | ||
63 | bool setOfFile(QString of, QStringList blfile); | ||
64 | |||
65 | //! returns a port Install Hint or empty if there is none | ||
66 | //! static and in the base class, so the installer classes dont need to | ||
67 | // be modified for new targets | ||
68 | static QString postinstallHints(QString model); | ||
69 | |||
70 | //! returns the correct BootloaderInstaller object for the requested type | ||
71 | static BootloaderInstallBase* createBootloaderInstaller(QObject* parent,QString type); | ||
72 | protected slots: | ||
73 | void downloadReqFinished(int id, bool error); | ||
74 | void downloadBlFinish(bool error); | ||
75 | void installBlfile(void); | ||
76 | void progressAborted(void); | ||
77 | |||
78 | // NOTE: we need to keep this slot even on targets that don't need it | ||
79 | // -- using the preprocessor here confused moc. | ||
80 | void checkRemount(void); | ||
81 | protected: | ||
82 | enum LogMode | ||
83 | { LogAdd, LogRemove }; | ||
84 | |||
85 | void downloadBlStart(QUrl source); | ||
86 | int logInstall(LogMode mode); | ||
87 | |||
88 | HttpGet m_http; //! http download object | ||
89 | QString m_blfile; //! bootloader filename on player | ||
90 | QString m_logfile; //! file for installation log | ||
91 | QUrl m_blurl; //! bootloader download URL | ||
92 | QTemporaryFile m_tempfile; //! temporary file for download | ||
93 | QTemporaryFile m_tempof; //! temporary file for OF extracted from archive | ||
94 | QDateTime m_blversion; //! download timestamp used for version information | ||
95 | QString m_offile; //! path to the offile | ||
96 | #if defined(Q_OS_MACX) | ||
97 | void waitRemount(void); | ||
98 | |||
99 | int m_remountTries; | ||
100 | QString m_remountDevice; | ||
101 | #endif | ||
102 | |||
103 | signals: | ||
104 | void downloadDone(void); //! internal signal sent when download finished. | ||
105 | void installAborted(void); //! internal signal sent on abort button click. | ||
106 | void done(bool); | ||
107 | void logItem(QString, int); //! set logger item | ||
108 | void logProgress(int, int); //! set progress bar. | ||
109 | |||
110 | // NOTE: we need to keep this signal even on targets that don't need it | ||
111 | // -- using the preprocessor here confused moc. | ||
112 | void remounted(bool); | ||
113 | }; | ||
114 | |||
115 | Q_DECLARE_OPERATORS_FOR_FLAGS(BootloaderInstallBase::Capabilities) | ||
116 | |||
117 | #endif | ||
118 | |||
diff --git a/utils/rbutilqt/base/bootloaderinstallbspatch.cpp b/utils/rbutilqt/base/bootloaderinstallbspatch.cpp new file mode 100644 index 0000000000..02291b0151 --- /dev/null +++ b/utils/rbutilqt/base/bootloaderinstallbspatch.cpp | |||
@@ -0,0 +1,178 @@ | |||
1 | /*************************************************************************** | ||
2 | * __________ __ ___. | ||
3 | * Open \______ \ ____ ____ | | _\_ |__ _______ ___ | ||
4 | * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / | ||
5 | * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < | ||
6 | * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ | ||
7 | * \/ \/ \/ \/ \/ | ||
8 | * | ||
9 | * Copyright (C) 2020 by Solomon Peachy | ||
10 | * | ||
11 | * All files in this archive are subject to the GNU General Public License. | ||
12 | * See the file COPYING in the source tree root for full license agreement. | ||
13 | * | ||
14 | * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY | ||
15 | * KIND, either express or implied. | ||
16 | * | ||
17 | ****************************************************************************/ | ||
18 | |||
19 | #include <QtCore> | ||
20 | #include <QtDebug> | ||
21 | #include "bootloaderinstallbase.h" | ||
22 | #include "bootloaderinstallbspatch.h" | ||
23 | #include "../bspatch/bspatch.h" | ||
24 | #include "Logger.h" | ||
25 | |||
26 | /* class for running bspatch() in a separate thread to keep the UI responsive. */ | ||
27 | class BootloaderThreadBSPatch : public QThread | ||
28 | { | ||
29 | public: | ||
30 | void run(void); | ||
31 | void setInputFile(QString f) | ||
32 | { m_inputfile = f; } | ||
33 | void setOutputFile(QString f) | ||
34 | { m_outputfile = f; } | ||
35 | void setBootloaderFile(QString f) | ||
36 | { m_bootfile = f; } | ||
37 | int error(void) | ||
38 | { return m_error; } | ||
39 | private: | ||
40 | QString m_inputfile; | ||
41 | QString m_bootfile; | ||
42 | QString m_outputfile; | ||
43 | int m_error; | ||
44 | }; | ||
45 | |||
46 | void BootloaderThreadBSPatch::run(void) | ||
47 | { | ||
48 | LOG_INFO() << "Thread started."; | ||
49 | |||
50 | m_error = apply_bspatch(m_inputfile.toLocal8Bit().constData(), | ||
51 | m_outputfile.toLocal8Bit().constData(), | ||
52 | m_bootfile.toLocal8Bit().constData()); | ||
53 | |||
54 | LOG_INFO() << "Thread finished, result:" << m_error; | ||
55 | } | ||
56 | |||
57 | BootloaderInstallBSPatch::BootloaderInstallBSPatch(QObject *parent) | ||
58 | : BootloaderInstallBase(parent) | ||
59 | { | ||
60 | m_thread = nullptr; | ||
61 | } | ||
62 | |||
63 | QString BootloaderInstallBSPatch::ofHint() | ||
64 | { | ||
65 | return tr("Bootloader installation requires you to provide " | ||
66 | "the correct verrsion of the original firmware file. " | ||
67 | "This file will be patched with the Rockbox bootloader and " | ||
68 | "installed to your player. You need to download this file " | ||
69 | "yourself due to legal reasons. Please refer to the " | ||
70 | "<a href='http://www.rockbox.org/wiki/'>rockbox wiki</a> " | ||
71 | "pages on how to obtain this file.<br/>" | ||
72 | "Press Ok to continue and browse your computer for the firmware " | ||
73 | "file."); | ||
74 | } | ||
75 | |||
76 | /** Start bootloader installation. | ||
77 | */ | ||
78 | bool BootloaderInstallBSPatch::install(void) | ||
79 | { | ||
80 | if(!QFileInfo(m_offile).isReadable()) | ||
81 | { | ||
82 | LOG_ERROR() << "could not read original firmware file" | ||
83 | << m_offile; | ||
84 | emit logItem(tr("Could not read original firmware file"), LOGERROR); | ||
85 | return false; | ||
86 | } | ||
87 | |||
88 | LOG_INFO() << "downloading bootloader"; | ||
89 | // download bootloader from server | ||
90 | emit logItem(tr("Downloading bootloader file"), LOGINFO); | ||
91 | connect(this, &BootloaderInstallBase::downloadDone, this, &BootloaderInstallBSPatch::installStage2); | ||
92 | downloadBlStart(m_blurl); | ||
93 | return true; | ||
94 | } | ||
95 | |||
96 | void BootloaderInstallBSPatch::installStage2(void) | ||
97 | { | ||
98 | LOG_INFO() << "patching file..."; | ||
99 | emit logItem(tr("Patching file..."), LOGINFO); | ||
100 | m_tempfile.open(); | ||
101 | |||
102 | // we have not detailed progress on the patching so just show a busy | ||
103 | // indicator instead. | ||
104 | emit logProgress(0, 0); | ||
105 | m_patchedFile.open(); | ||
106 | m_thread = new BootloaderThreadBSPatch(); | ||
107 | m_thread->setInputFile(m_offile); | ||
108 | m_thread->setBootloaderFile(m_tempfile.fileName()); | ||
109 | m_thread->setOutputFile(m_patchedFile.fileName()); | ||
110 | m_tempfile.close(); | ||
111 | m_patchedFile.close(); | ||
112 | connect(m_thread, &QThread::finished, this, &BootloaderInstallBSPatch::installStage3); | ||
113 | m_thread->start(); | ||
114 | } | ||
115 | |||
116 | void BootloaderInstallBSPatch::installStage3(void) | ||
117 | { | ||
118 | int err = m_thread->error(); | ||
119 | emit logProgress(1, 1); | ||
120 | // if the patch failed | ||
121 | if (err != 0) | ||
122 | { | ||
123 | LOG_ERROR() << "Could not patch the original firmware file"; | ||
124 | emit logItem(tr("Patching the original firmware failed"), LOGERROR); | ||
125 | emit done(true); | ||
126 | return; | ||
127 | } | ||
128 | |||
129 | LOG_INFO() << "Original Firmware succesfully patched"; | ||
130 | emit logItem(tr("Succesfully patched firmware file"), LOGINFO); | ||
131 | |||
132 | // if a bootloader is already present delete it. | ||
133 | QString fwfile(m_blfile); | ||
134 | if(QFileInfo(fwfile).isFile()) | ||
135 | { | ||
136 | LOG_INFO() << "deleting old target file"; | ||
137 | QFile::remove(fwfile); | ||
138 | } | ||
139 | |||
140 | // place (new) bootloader. Copy, since the temporary file will be removed | ||
141 | // automatically. | ||
142 | LOG_INFO() << "moving patched bootloader to" << fwfile; | ||
143 | if(m_patchedFile.copy(fwfile)) | ||
144 | { | ||
145 | emit logItem(tr("Bootloader successful installed"), LOGOK); | ||
146 | logInstall(LogAdd); | ||
147 | emit done(false); | ||
148 | } | ||
149 | else | ||
150 | { | ||
151 | emit logItem(tr("Patched bootloader could not be installed"), LOGERROR); | ||
152 | emit done(true); | ||
153 | } | ||
154 | // clean up thread object. | ||
155 | delete m_thread; | ||
156 | return; | ||
157 | } | ||
158 | |||
159 | bool BootloaderInstallBSPatch::uninstall(void) | ||
160 | { | ||
161 | emit logItem(tr("To uninstall, perform a normal upgrade with an unmodified " | ||
162 | "original firmware."), LOGINFO); | ||
163 | logInstall(LogRemove); | ||
164 | emit done(true); | ||
165 | return false; | ||
166 | } | ||
167 | |||
168 | |||
169 | BootloaderInstallBase::BootloaderType BootloaderInstallBSPatch::installed(void) | ||
170 | { | ||
171 | return BootloaderUnknown; | ||
172 | } | ||
173 | |||
174 | |||
175 | BootloaderInstallBase::Capabilities BootloaderInstallBSPatch::capabilities(void) | ||
176 | { | ||
177 | return (Install | NeedsOf); | ||
178 | } | ||
diff --git a/utils/rbutilqt/base/bootloaderinstallbspatch.h b/utils/rbutilqt/base/bootloaderinstallbspatch.h new file mode 100644 index 0000000000..7108820fda --- /dev/null +++ b/utils/rbutilqt/base/bootloaderinstallbspatch.h | |||
@@ -0,0 +1,47 @@ | |||
1 | /*************************************************************************** | ||
2 | * __________ __ ___. | ||
3 | * Open \______ \ ____ ____ | | _\_ |__ _______ ___ | ||
4 | * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / | ||
5 | * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < | ||
6 | * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ | ||
7 | * \/ \/ \/ \/ \/ | ||
8 | * | ||
9 | * Copyright (C) 2020 Solomon Peachy | ||
10 | * | ||
11 | * All files in this archive are subject to the GNU General Public License. | ||
12 | * See the file COPYING in the source tree root for full license agreement. | ||
13 | * | ||
14 | * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY | ||
15 | * KIND, either express or implied. | ||
16 | * | ||
17 | ****************************************************************************/ | ||
18 | #ifndef BOOTLOADERINSTALLBSPATCH_H | ||
19 | #define BOOTLOADERINSTALLBSPATCH_H | ||
20 | |||
21 | #include <QtCore> | ||
22 | #include "bootloaderinstallbase.h" | ||
23 | |||
24 | class BootloaderThreadBSPatch; | ||
25 | |||
26 | //! bootloader installation class for devices handled by mkimxboot. | ||
27 | class BootloaderInstallBSPatch : public BootloaderInstallBase | ||
28 | { | ||
29 | Q_OBJECT | ||
30 | public: | ||
31 | BootloaderInstallBSPatch(QObject *parent); | ||
32 | bool install(void); | ||
33 | bool uninstall(void); | ||
34 | BootloaderInstallBase::BootloaderType installed(void); | ||
35 | Capabilities capabilities(void); | ||
36 | QString ofHint(); | ||
37 | |||
38 | private slots: | ||
39 | void installStage2(void); | ||
40 | void installStage3(void); | ||
41 | |||
42 | private: | ||
43 | BootloaderThreadBSPatch *m_thread; | ||
44 | QTemporaryFile m_patchedFile; | ||
45 | }; | ||
46 | |||
47 | #endif | ||
diff --git a/utils/rbutilqt/base/bootloaderinstallchinachip.cpp b/utils/rbutilqt/base/bootloaderinstallchinachip.cpp new file mode 100644 index 0000000000..f6af952b9a --- /dev/null +++ b/utils/rbutilqt/base/bootloaderinstallchinachip.cpp | |||
@@ -0,0 +1,133 @@ | |||
1 | /*************************************************************************** | ||
2 | * __________ __ ___. | ||
3 | * Open \______ \ ____ ____ | | _\_ |__ _______ ___ | ||
4 | * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / | ||
5 | * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < | ||
6 | * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ | ||
7 | * \/ \/ \/ \/ \/ | ||
8 | * | ||
9 | * Copyright (C) 2009 by Maurus Cuelenaere | ||
10 | * | ||
11 | * All files in this archive are subject to the GNU General Public License. | ||
12 | * See the file COPYING in the source tree root for full license agreement. | ||
13 | * | ||
14 | * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY | ||
15 | * KIND, either express or implied. | ||
16 | * | ||
17 | ****************************************************************************/ | ||
18 | |||
19 | #include <QtCore> | ||
20 | #include "bootloaderinstallbase.h" | ||
21 | #include "bootloaderinstallchinachip.h" | ||
22 | |||
23 | #include "../chinachippatcher/chinachip.h" | ||
24 | |||
25 | BootloaderInstallChinaChip::BootloaderInstallChinaChip(QObject *parent) | ||
26 | : BootloaderInstallBase(parent) | ||
27 | { | ||
28 | (void)parent; | ||
29 | } | ||
30 | |||
31 | QString BootloaderInstallChinaChip::ofHint() | ||
32 | { | ||
33 | return tr("Bootloader installation requires you to provide " | ||
34 | "a firmware file of the original firmware (HXF file). " | ||
35 | "You need to download this file yourself due to legal " | ||
36 | "reasons. Please refer to the " | ||
37 | "<a href='http://www.rockbox.org/manual.shtml'>manual</a> and the " | ||
38 | "<a href='http://www.rockbox.org/wiki/OndaVX747" | ||
39 | "#Download_and_extract_a_recent_ve'>OndaVX747</a> wiki page on " | ||
40 | "how to obtain this file.<br/>" | ||
41 | "Press Ok to continue and browse your computer for the firmware " | ||
42 | "file."); | ||
43 | } | ||
44 | |||
45 | bool BootloaderInstallChinaChip::install() | ||
46 | { | ||
47 | if(m_offile.isEmpty()) | ||
48 | return false; | ||
49 | |||
50 | emit logItem(tr("Downloading bootloader file"), LOGINFO); | ||
51 | |||
52 | connect(this, &BootloaderInstallBase::downloadDone, this, &BootloaderInstallChinaChip::installStage2); | ||
53 | downloadBlStart(m_blurl); | ||
54 | |||
55 | return true; | ||
56 | } | ||
57 | |||
58 | void BootloaderInstallChinaChip::installStage2() | ||
59 | { | ||
60 | enum cc_error result; | ||
61 | bool error = true; | ||
62 | m_tempfile.open(); | ||
63 | QString blfile = m_tempfile.fileName(); | ||
64 | m_tempfile.close(); | ||
65 | |||
66 | QString backupfile = QFileInfo(m_blfile).absoluteDir().absoluteFilePath("ccpmp.bin"); | ||
67 | |||
68 | result = chinachip_patch(m_offile.toLocal8Bit(), blfile.toLocal8Bit(), | ||
69 | m_blfile.toLocal8Bit(), backupfile.toLocal8Bit()); | ||
70 | switch(result) { | ||
71 | case E_OK: | ||
72 | error = false; | ||
73 | break; | ||
74 | case E_OPEN_FIRMWARE: | ||
75 | emit logItem(tr("Could not open firmware file"), LOGERROR); | ||
76 | break; | ||
77 | case E_OPEN_BOOTLOADER: | ||
78 | emit logItem(tr("Could not open bootloader file"), LOGERROR); | ||
79 | break; | ||
80 | case E_MEMALLOC: | ||
81 | emit logItem(tr("Could not allocate memory"), LOGERROR); | ||
82 | break; | ||
83 | case E_LOAD_FIRMWARE: | ||
84 | emit logItem(tr("Could not load firmware file"), LOGERROR); | ||
85 | break; | ||
86 | case E_INVALID_FILE: | ||
87 | emit logItem(tr("File is not a valid ChinaChip firmware"), LOGERROR); | ||
88 | break; | ||
89 | case E_NO_CCPMP: | ||
90 | emit logItem(tr("Could not find ccpmp.bin in input file"), LOGERROR); | ||
91 | break; | ||
92 | case E_OPEN_BACKUP: | ||
93 | emit logItem(tr("Could not open backup file for ccpmp.bin"), LOGERROR); | ||
94 | break; | ||
95 | case E_WRITE_BACKUP: | ||
96 | emit logItem(tr("Could not write backup file for ccpmp.bin"), LOGERROR); | ||
97 | break; | ||
98 | case E_LOAD_BOOTLOADER: | ||
99 | emit logItem(tr("Could not load bootloader file"), LOGERROR); | ||
100 | break; | ||
101 | case E_GET_TIME: | ||
102 | emit logItem(tr("Could not get current time"), LOGERROR); | ||
103 | break; | ||
104 | case E_OPEN_OUTFILE: | ||
105 | emit logItem(tr("Could not open output file"), LOGERROR); | ||
106 | break; | ||
107 | case E_WRITE_OUTFILE: | ||
108 | emit logItem(tr("Could not write output file"), LOGERROR); | ||
109 | break; | ||
110 | default: | ||
111 | emit logItem(tr("Unexpected error from chinachippatcher"), LOGERROR); | ||
112 | break; | ||
113 | } | ||
114 | |||
115 | emit done(error); | ||
116 | } | ||
117 | |||
118 | bool BootloaderInstallChinaChip::uninstall() | ||
119 | { | ||
120 | /* TODO: only way is to restore the OF */ | ||
121 | return false; | ||
122 | } | ||
123 | |||
124 | BootloaderInstallBase::BootloaderType BootloaderInstallChinaChip::installed() | ||
125 | { | ||
126 | /* TODO: find a way to figure this out */ | ||
127 | return BootloaderUnknown; | ||
128 | } | ||
129 | |||
130 | BootloaderInstallBase::Capabilities BootloaderInstallChinaChip::capabilities() | ||
131 | { | ||
132 | return (Install | IsFile | NeedsOf); | ||
133 | } | ||
diff --git a/utils/rbutilqt/base/bootloaderinstallchinachip.h b/utils/rbutilqt/base/bootloaderinstallchinachip.h new file mode 100644 index 0000000000..e92f292283 --- /dev/null +++ b/utils/rbutilqt/base/bootloaderinstallchinachip.h | |||
@@ -0,0 +1,41 @@ | |||
1 | /*************************************************************************** | ||
2 | * __________ __ ___. | ||
3 | * Open \______ \ ____ ____ | | _\_ |__ _______ ___ | ||
4 | * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / | ||
5 | * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < | ||
6 | * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ | ||
7 | * \/ \/ \/ \/ \/ | ||
8 | * | ||
9 | * Copyright (C) 2009 by Maurus Cuelenaere | ||
10 | * | ||
11 | * All files in this archive are subject to the GNU General Public License. | ||
12 | * See the file COPYING in the source tree root for full license agreement. | ||
13 | * | ||
14 | * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY | ||
15 | * KIND, either express or implied. | ||
16 | * | ||
17 | ****************************************************************************/ | ||
18 | |||
19 | #ifndef BOOTLOADERINSTALLCCPMP_H | ||
20 | #define BOOTLOADERINSTALLCCPMP_H | ||
21 | |||
22 | #include <QtCore> | ||
23 | #include "bootloaderinstallbase.h" | ||
24 | |||
25 | class BootloaderInstallChinaChip : public BootloaderInstallBase | ||
26 | { | ||
27 | Q_OBJECT | ||
28 | |||
29 | public: | ||
30 | BootloaderInstallChinaChip(QObject *parent = nullptr); | ||
31 | bool install(void); | ||
32 | bool uninstall(void); | ||
33 | BootloaderInstallBase::BootloaderType installed(void); | ||
34 | Capabilities capabilities(void); | ||
35 | QString ofHint(); | ||
36 | |||
37 | private slots: | ||
38 | void installStage2(void); | ||
39 | }; | ||
40 | |||
41 | #endif // BOOTLOADERINSTALLCCPMP_H | ||
diff --git a/utils/rbutilqt/base/bootloaderinstallfile.cpp b/utils/rbutilqt/base/bootloaderinstallfile.cpp new file mode 100644 index 0000000000..bf3d5e449d --- /dev/null +++ b/utils/rbutilqt/base/bootloaderinstallfile.cpp | |||
@@ -0,0 +1,159 @@ | |||
1 | /*************************************************************************** | ||
2 | * __________ __ ___. | ||
3 | * Open \______ \ ____ ____ | | _\_ |__ _______ ___ | ||
4 | * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / | ||
5 | * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < | ||
6 | * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ | ||
7 | * \/ \/ \/ \/ \/ | ||
8 | * | ||
9 | * Copyright (C) 2008 by Dominik Riebeling | ||
10 | * | ||
11 | * All files in this archive are subject to the GNU General Public License. | ||
12 | * See the file COPYING in the source tree root for full license agreement. | ||
13 | * | ||
14 | * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY | ||
15 | * KIND, either express or implied. | ||
16 | * | ||
17 | ****************************************************************************/ | ||
18 | |||
19 | #include <QtCore> | ||
20 | #include <QtDebug> | ||
21 | #include "bootloaderinstallfile.h" | ||
22 | #include "utils.h" | ||
23 | #include "Logger.h" | ||
24 | |||
25 | |||
26 | BootloaderInstallFile::BootloaderInstallFile(QObject *parent) | ||
27 | : BootloaderInstallBase(parent) | ||
28 | { | ||
29 | } | ||
30 | |||
31 | |||
32 | bool BootloaderInstallFile::install(void) | ||
33 | { | ||
34 | emit logItem(tr("Downloading bootloader"), LOGINFO); | ||
35 | LOG_INFO() << "installing bootloader"; | ||
36 | downloadBlStart(m_blurl); | ||
37 | connect(this, &BootloaderInstallBase::downloadDone, this, &BootloaderInstallFile::installStage2); | ||
38 | return true; | ||
39 | } | ||
40 | |||
41 | void BootloaderInstallFile::installStage2(void) | ||
42 | { | ||
43 | emit logItem(tr("Installing Rockbox bootloader"), LOGINFO); | ||
44 | QCoreApplication::processEvents(); | ||
45 | |||
46 | // if an old bootloader is present (Gigabeat) move it out of the way. | ||
47 | QString fwfile(Utils::resolvePathCase(m_blfile)); | ||
48 | if(!fwfile.isEmpty()) { | ||
49 | QString moved = Utils::resolvePathCase(m_blfile) + ".ORIG"; | ||
50 | LOG_INFO() << "renaming" << fwfile << "to" << moved; | ||
51 | QFile::rename(fwfile, moved); | ||
52 | } | ||
53 | |||
54 | // if no old file found resolve path without basename | ||
55 | QFileInfo fi(m_blfile); | ||
56 | QString absPath = Utils::resolvePathCase(fi.absolutePath()); | ||
57 | |||
58 | // if it's not possible to locate the base path try to create it | ||
59 | if(absPath.isEmpty()) { | ||
60 | QStringList pathElements = m_blfile.split("/"); | ||
61 | // remove filename from list and save last path element | ||
62 | pathElements.removeLast(); | ||
63 | QString lastElement = pathElements.last(); | ||
64 | // remove last path element for base | ||
65 | pathElements.removeLast(); | ||
66 | QString basePath = pathElements.join("/"); | ||
67 | |||
68 | // check for base and bail out if not found. Otherwise create folder. | ||
69 | absPath = Utils::resolvePathCase(basePath); | ||
70 | QDir d(absPath); | ||
71 | d.mkpath(lastElement); | ||
72 | absPath = Utils::resolvePathCase(fi.absolutePath()); | ||
73 | |||
74 | if(absPath.isEmpty()) { | ||
75 | emit logItem(tr("Error accessing output folder"), LOGERROR); | ||
76 | emit done(true); | ||
77 | return; | ||
78 | } | ||
79 | } | ||
80 | fwfile = absPath + "/" + fi.fileName(); | ||
81 | |||
82 | // place (new) bootloader | ||
83 | m_tempfile.open(); | ||
84 | LOG_INFO() << "renaming" << m_tempfile.fileName() | ||
85 | << "to" << fwfile; | ||
86 | m_tempfile.close(); | ||
87 | |||
88 | if(!Utils::resolvePathCase(fwfile).isEmpty()) { | ||
89 | emit logItem(tr("A firmware file is already present on player"), LOGERROR); | ||
90 | emit done(true); | ||
91 | return; | ||
92 | } | ||
93 | if(m_tempfile.copy(fwfile)) { | ||
94 | emit logItem(tr("Bootloader successful installed"), LOGOK); | ||
95 | } | ||
96 | else { | ||
97 | emit logItem(tr("Copying modified firmware file failed"), LOGERROR); | ||
98 | emit done(true); | ||
99 | return; | ||
100 | } | ||
101 | |||
102 | logInstall(LogAdd); | ||
103 | |||
104 | emit done(false); | ||
105 | } | ||
106 | |||
107 | |||
108 | bool BootloaderInstallFile::uninstall(void) | ||
109 | { | ||
110 | LOG_INFO() << "Uninstalling bootloader"; | ||
111 | emit logItem(tr("Removing Rockbox bootloader"), LOGINFO); | ||
112 | // check if a .ORIG file is present, and allow moving it back. | ||
113 | QString origbl = Utils::resolvePathCase(m_blfile + ".ORIG"); | ||
114 | if(origbl.isEmpty()) { | ||
115 | emit logItem(tr("No original firmware file found."), LOGERROR); | ||
116 | emit done(true); | ||
117 | return false; | ||
118 | } | ||
119 | QString fwfile = Utils::resolvePathCase(m_blfile); | ||
120 | if(!QFile::remove(fwfile)) { | ||
121 | emit logItem(tr("Can't remove Rockbox bootloader file."), LOGERROR); | ||
122 | emit done(true); | ||
123 | return false; | ||
124 | } | ||
125 | if(!QFile::rename(origbl, fwfile)) { | ||
126 | emit logItem(tr("Can't restore bootloader file."), LOGERROR); | ||
127 | emit done(true); | ||
128 | return false; | ||
129 | } | ||
130 | emit logItem(tr("Original bootloader restored successfully."), LOGOK); | ||
131 | logInstall(LogRemove); | ||
132 | emit logProgress(1, 1); | ||
133 | emit done(false); | ||
134 | |||
135 | return true; | ||
136 | } | ||
137 | |||
138 | |||
139 | //! @brief check if bootloader is installed. | ||
140 | //! @return BootloaderRockbox, BootloaderOther or BootloaderUnknown. | ||
141 | BootloaderInstallBase::BootloaderType BootloaderInstallFile::installed(void) | ||
142 | { | ||
143 | LOG_INFO() << "checking installed bootloader"; | ||
144 | if(!Utils::resolvePathCase(m_blfile).isEmpty() | ||
145 | && !Utils::resolvePathCase(m_blfile + ".ORIG").isEmpty()) | ||
146 | return BootloaderRockbox; | ||
147 | else if(!Utils::resolvePathCase(m_blfile).isEmpty()) | ||
148 | return BootloaderOther; | ||
149 | else | ||
150 | return BootloaderUnknown; | ||
151 | } | ||
152 | |||
153 | |||
154 | BootloaderInstallBase::Capabilities BootloaderInstallFile::capabilities(void) | ||
155 | { | ||
156 | LOG_INFO() << "getting capabilities"; | ||
157 | return Install | Uninstall | IsFile | CanCheckInstalled | Backup; | ||
158 | } | ||
159 | |||
diff --git a/utils/rbutilqt/base/bootloaderinstallfile.h b/utils/rbutilqt/base/bootloaderinstallfile.h new file mode 100644 index 0000000000..9f9586279c --- /dev/null +++ b/utils/rbutilqt/base/bootloaderinstallfile.h | |||
@@ -0,0 +1,48 @@ | |||
1 | /*************************************************************************** | ||
2 | * __________ __ ___. | ||
3 | * Open \______ \ ____ ____ | | _\_ |__ _______ ___ | ||
4 | * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / | ||
5 | * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < | ||
6 | * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ | ||
7 | * \/ \/ \/ \/ \/ | ||
8 | * | ||
9 | * Copyright (C) 2008 by Dominik Riebeling | ||
10 | * | ||
11 | * All files in this archive are subject to the GNU General Public License. | ||
12 | * See the file COPYING in the source tree root for full license agreement. | ||
13 | * | ||
14 | * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY | ||
15 | * KIND, either express or implied. | ||
16 | * | ||
17 | ****************************************************************************/ | ||
18 | |||
19 | #ifndef BOOTLOADERINSTALLFILE_H | ||
20 | #define BOOTLOADERINSTALLFILE_H | ||
21 | |||
22 | #include <QtCore> | ||
23 | #include "progressloggerinterface.h" | ||
24 | #include "bootloaderinstallbase.h" | ||
25 | |||
26 | //! install a bootloader by putting a single file on the player. | ||
27 | // This installation method is used by Iaudio (firmware is flashed | ||
28 | // automatically) and Gigabeat (Firmware is a file, OF needs to get | ||
29 | // renamed). | ||
30 | class BootloaderInstallFile : public BootloaderInstallBase | ||
31 | { | ||
32 | Q_OBJECT | ||
33 | |||
34 | public: | ||
35 | BootloaderInstallFile(QObject *parent); | ||
36 | bool install(void); | ||
37 | bool uninstall(void); | ||
38 | BootloaderInstallBase::BootloaderType installed(void); | ||
39 | Capabilities capabilities(void); | ||
40 | |||
41 | private slots: | ||
42 | void installStage2(void); | ||
43 | |||
44 | private: | ||
45 | }; | ||
46 | |||
47 | #endif | ||
48 | |||
diff --git a/utils/rbutilqt/base/bootloaderinstallhelper.cpp b/utils/rbutilqt/base/bootloaderinstallhelper.cpp new file mode 100644 index 0000000000..c94ed29140 --- /dev/null +++ b/utils/rbutilqt/base/bootloaderinstallhelper.cpp | |||
@@ -0,0 +1,140 @@ | |||
1 | /*************************************************************************** | ||
2 | * __________ __ ___. | ||
3 | * Open \______ \ ____ ____ | | _\_ |__ _______ ___ | ||
4 | * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / | ||
5 | * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < | ||
6 | * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ | ||
7 | * \/ \/ \/ \/ \/ | ||
8 | * | ||
9 | * Copyright (C) 2012 Dominik Riebeling | ||
10 | * | ||
11 | * All files in this archive are subject to the GNU General Public License. | ||
12 | * See the file COPYING in the source tree root for full license agreement. | ||
13 | * | ||
14 | * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY | ||
15 | * KIND, either express or implied. | ||
16 | * | ||
17 | * This file is a modified version of the AMS installer by Dominik Wenger | ||
18 | * | ||
19 | ****************************************************************************/ | ||
20 | |||
21 | #include <QtCore> | ||
22 | |||
23 | #include "bootloaderinstallhelper.h" | ||
24 | #include "bootloaderinstallmi4.h" | ||
25 | #include "bootloaderinstallhex.h" | ||
26 | #include "bootloaderinstallipod.h" | ||
27 | #include "bootloaderinstallsansa.h" | ||
28 | #include "bootloaderinstallfile.h" | ||
29 | #include "bootloaderinstallchinachip.h" | ||
30 | #include "bootloaderinstallams.h" | ||
31 | #include "bootloaderinstalltcc.h" | ||
32 | #include "bootloaderinstallmpio.h" | ||
33 | #include "bootloaderinstallimx.h" | ||
34 | #include "bootloaderinstalls5l.h" | ||
35 | #include "bootloaderinstallbspatch.h" | ||
36 | |||
37 | BootloaderInstallBase* BootloaderInstallHelper::createBootloaderInstaller(QObject* parent, QString type) | ||
38 | { | ||
39 | if(type == "mi4") { | ||
40 | return new BootloaderInstallMi4(parent); | ||
41 | } | ||
42 | else if(type == "hex") { | ||
43 | return new BootloaderInstallHex(parent); | ||
44 | } | ||
45 | else if(type == "sansa") { | ||
46 | return new BootloaderInstallSansa(parent); | ||
47 | } | ||
48 | else if(type == "ipod") { | ||
49 | return new BootloaderInstallIpod(parent); | ||
50 | } | ||
51 | else if(type == "file") { | ||
52 | return new BootloaderInstallFile(parent); | ||
53 | } | ||
54 | else if(type == "chinachip") { | ||
55 | return new BootloaderInstallChinaChip(parent); | ||
56 | } | ||
57 | else if(type == "ams") { | ||
58 | return new BootloaderInstallAms(parent); | ||
59 | } | ||
60 | else if(type == "tcc") { | ||
61 | return new BootloaderInstallTcc(parent); | ||
62 | } | ||
63 | else if(type == "mpio") { | ||
64 | return new BootloaderInstallMpio(parent); | ||
65 | } | ||
66 | else if(type == "imx") { | ||
67 | return new BootloaderInstallImx(parent); | ||
68 | } | ||
69 | else if(type == "s5l") { | ||
70 | return new BootloaderInstallS5l(parent); | ||
71 | } | ||
72 | else if(type == "bspatch") { | ||
73 | return new BootloaderInstallBSPatch(parent); | ||
74 | } | ||
75 | else { | ||
76 | return nullptr; | ||
77 | } | ||
78 | } | ||
79 | |||
80 | |||
81 | //! @brief Return post install hints string. | ||
82 | //! @param model model string | ||
83 | //! @return hints. | ||
84 | QString BootloaderInstallHelper::postinstallHints(QString model) | ||
85 | { | ||
86 | bool hint = false; | ||
87 | QString msg = QObject::tr("Bootloader installation is almost complete. " | ||
88 | "Installation <b>requires</b> you to perform the " | ||
89 | "following steps manually:"); | ||
90 | |||
91 | msg += "<ol>"; | ||
92 | if(model != "sansafuzeplus") { | ||
93 | msg += QObject::tr("<li>Safely remove your player.</li>"); | ||
94 | } | ||
95 | if(model == "iriverh100" || model == "iriverh120" || model == "iriverh300" | ||
96 | || model == "ondavx747" || model == "agptekrocker" | ||
97 | || model == "xduoox3" || model == "xduoox3ii" || model == "xduoox20") { | ||
98 | hint = true; | ||
99 | msg += QObject::tr("<li>Reboot your player into the original firmware.</li>" | ||
100 | "<li>Perform a firmware upgrade using the update functionality " | ||
101 | "of the original firmware. Please refer to your player's manual " | ||
102 | "on details.<br/><b>Important:</b> updating the firmware is a " | ||
103 | "critical process that must not be interrupted. <b>Make sure the " | ||
104 | "player is charged before starting the firmware update " | ||
105 | "process.</b></li>" | ||
106 | "<li>After the firmware has been updated reboot your player.</li>"); | ||
107 | } | ||
108 | if(model == "sansafuzeplus") { | ||
109 | hint = true; | ||
110 | msg += QObject::tr("<li>Remove any previously inserted microSD card</li>"); | ||
111 | msg += QObject::tr("<li>Disconnect your player. The player will reboot and " | ||
112 | "perform an update of the original firmware. " | ||
113 | "Please refer to your players manual on details.<br/>" | ||
114 | "<b>Important:</b> updating the firmware is a " | ||
115 | "critical process that must not be interrupted. <b>Make sure the " | ||
116 | "player is charged before disconnecting the player.</b></li>" | ||
117 | "<li>After the firmware has been updated reboot your player.</li>"); | ||
118 | } | ||
119 | if(model == "iaudiox5" || model == "iaudiom5" | ||
120 | || model == "iaudiox5v" || model == "iaudiom3" || model == "mpioh200") { | ||
121 | hint = true; | ||
122 | msg += QObject::tr("<li>Turn the player off</li>" | ||
123 | "<li>Insert the charger</li>"); | ||
124 | } | ||
125 | if(model == "gigabeatf") { | ||
126 | hint = true; | ||
127 | msg += QObject::tr("<li>Unplug USB and power adaptors</li>" | ||
128 | "<li>Hold <i>Power</i> to turn the player off</li>" | ||
129 | "<li>Toggle the battery switch on the player</li>" | ||
130 | "<li>Hold <i>Power</i> to boot into Rockbox</li>"); | ||
131 | } | ||
132 | msg += "</ol>"; | ||
133 | msg += QObject::tr("<p><b>Note:</b> You can safely install other parts first, but " | ||
134 | "the above steps are <b>required</b> to finish the installation!</p>"); | ||
135 | |||
136 | if(hint) | ||
137 | return msg; | ||
138 | else | ||
139 | return QString(); | ||
140 | } | ||
diff --git a/utils/rbutilqt/base/bootloaderinstallhelper.h b/utils/rbutilqt/base/bootloaderinstallhelper.h new file mode 100644 index 0000000000..c89444d7a7 --- /dev/null +++ b/utils/rbutilqt/base/bootloaderinstallhelper.h | |||
@@ -0,0 +1,36 @@ | |||
1 | /*************************************************************************** | ||
2 | * __________ __ ___. | ||
3 | * Open \______ \ ____ ____ | | _\_ |__ _______ ___ | ||
4 | * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / | ||
5 | * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < | ||
6 | * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ | ||
7 | * \/ \/ \/ \/ \/ | ||
8 | * | ||
9 | * Copyright (C) 2012 Dominik Riebeling | ||
10 | * | ||
11 | * All files in this archive are subject to the GNU General Public License. | ||
12 | * See the file COPYING in the source tree root for full license agreement. | ||
13 | * | ||
14 | * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY | ||
15 | * KIND, either express or implied. | ||
16 | * | ||
17 | * This file is a modified version of the AMS installer by Dominik Wenger | ||
18 | * | ||
19 | ****************************************************************************/ | ||
20 | |||
21 | #ifndef BOOTLOADERINSTALLHELPER_H | ||
22 | #define BOOTLOADERINSTALLHELPER_H | ||
23 | |||
24 | #include <QtCore> | ||
25 | #include "bootloaderinstallbase.h" | ||
26 | |||
27 | class BootloaderInstallHelper : public QObject | ||
28 | { | ||
29 | Q_OBJECT | ||
30 | public: | ||
31 | static BootloaderInstallBase* createBootloaderInstaller(QObject* parent, QString type); | ||
32 | static QString postinstallHints(QString model); | ||
33 | }; | ||
34 | |||
35 | #endif | ||
36 | |||
diff --git a/utils/rbutilqt/base/bootloaderinstallhex.cpp b/utils/rbutilqt/base/bootloaderinstallhex.cpp new file mode 100644 index 0000000000..b3dde0bbfa --- /dev/null +++ b/utils/rbutilqt/base/bootloaderinstallhex.cpp | |||
@@ -0,0 +1,271 @@ | |||
1 | /*************************************************************************** | ||
2 | * __________ __ ___. | ||
3 | * Open \______ \ ____ ____ | | _\_ |__ _______ ___ | ||
4 | * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / | ||
5 | * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < | ||
6 | * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ | ||
7 | * \/ \/ \/ \/ \/ | ||
8 | * | ||
9 | * Copyright (C) 2008 by Dominik Riebeling | ||
10 | * | ||
11 | * All files in this archive are subject to the GNU General Public License. | ||
12 | * See the file COPYING in the source tree root for full license agreement. | ||
13 | * | ||
14 | * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY | ||
15 | * KIND, either express or implied. | ||
16 | * | ||
17 | ****************************************************************************/ | ||
18 | |||
19 | #include <QtCore> | ||
20 | #include "bootloaderinstallbase.h" | ||
21 | #include "bootloaderinstallhex.h" | ||
22 | #include "utils.h" | ||
23 | #include "Logger.h" | ||
24 | |||
25 | #include "../../tools/iriver.h" | ||
26 | #include "../../tools/mkboot.h" | ||
27 | |||
28 | struct md5s { | ||
29 | const char* orig; | ||
30 | const char* patched; | ||
31 | }; | ||
32 | |||
33 | struct md5s md5sums[] = { | ||
34 | #include "irivertools/h100sums.h" | ||
35 | { nullptr, nullptr }, | ||
36 | #include "irivertools/h120sums.h" | ||
37 | { nullptr, nullptr }, | ||
38 | #include "irivertools/h300sums.h" | ||
39 | { nullptr, nullptr } | ||
40 | }; | ||
41 | |||
42 | |||
43 | BootloaderInstallHex::BootloaderInstallHex(QObject *parent) | ||
44 | : BootloaderInstallBase(parent) | ||
45 | { | ||
46 | } | ||
47 | |||
48 | QString BootloaderInstallHex::ofHint() | ||
49 | { | ||
50 | return tr("Bootloader installation requires you to provide " | ||
51 | "a firmware file of the original firmware (hex file). " | ||
52 | "You need to download this file yourself due to legal " | ||
53 | "reasons. Please refer to the " | ||
54 | "<a href='http://www.rockbox.org/manual.shtml'>manual</a> and the " | ||
55 | "<a href='http://www.rockbox.org/wiki/IriverBoot" | ||
56 | "#Download_and_extract_a_recent_ve'>IriverBoot</a> wiki page on " | ||
57 | "how to obtain this file.<br/>" | ||
58 | "Press Ok to continue and browse your computer for the firmware " | ||
59 | "file."); | ||
60 | } | ||
61 | |||
62 | bool BootloaderInstallHex::install(void) | ||
63 | { | ||
64 | if(m_offile.isEmpty()) | ||
65 | return false; | ||
66 | m_hashindex = -1; | ||
67 | |||
68 | // md5sum hex file | ||
69 | emit logItem(tr("checking MD5 hash of input file ..."), LOGINFO); | ||
70 | QByteArray filedata; | ||
71 | // read hex file into QByteArray | ||
72 | QFile file(m_offile); | ||
73 | file.open(QIODevice::ReadOnly); | ||
74 | filedata = file.readAll(); | ||
75 | file.close(); | ||
76 | QString hash = QCryptographicHash::hash(filedata, | ||
77 | QCryptographicHash::Md5).toHex(); | ||
78 | LOG_INFO() << "hexfile hash:" << hash; | ||
79 | if(file.error() != QFile::NoError) { | ||
80 | emit logItem(tr("Could not verify original firmware file"), LOGERROR); | ||
81 | emit done(true); | ||
82 | return false; | ||
83 | } | ||
84 | // check hash and figure model from md5sum | ||
85 | int i = sizeof(md5sums) / sizeof(struct md5s); | ||
86 | m_model = 4; | ||
87 | // 3: h300, 2: h120, 1: h100, 0:invalid | ||
88 | while(i--) { | ||
89 | if(md5sums[i].orig == nullptr) | ||
90 | m_model--; | ||
91 | if(!qstrcmp(md5sums[i].orig, hash.toLatin1())) | ||
92 | break; | ||
93 | } | ||
94 | if(i < 0) { | ||
95 | emit logItem(tr("Firmware file not recognized."), LOGERROR); | ||
96 | return false; | ||
97 | } | ||
98 | else { | ||
99 | emit logItem(tr("MD5 hash ok"), LOGOK); | ||
100 | m_hashindex = i; | ||
101 | } | ||
102 | |||
103 | // check model agains download link. | ||
104 | QString match[] = {"", "h100", "h120", "h300"}; | ||
105 | if(!m_blurl.path().contains(match[m_model])) { | ||
106 | emit logItem(tr("Firmware file doesn't match selected player."), | ||
107 | LOGERROR); | ||
108 | return false; | ||
109 | } | ||
110 | |||
111 | emit logItem(tr("Descrambling file"), LOGINFO); | ||
112 | m_descrambled.open(); | ||
113 | int result; | ||
114 | result = iriver_decode(m_offile.toLatin1().data(), | ||
115 | m_descrambled.fileName().toLatin1().data(), FALSE, STRIP_NONE); | ||
116 | LOG_INFO() << "iriver_decode():" << result; | ||
117 | |||
118 | if(result < 0) { | ||
119 | emit logItem(tr("Error in descramble: %1").arg(scrambleError(result)), LOGERROR); | ||
120 | return false; | ||
121 | } | ||
122 | |||
123 | // download firmware from server | ||
124 | emit logItem(tr("Downloading bootloader file"), LOGINFO); | ||
125 | connect(this, &BootloaderInstallBase::downloadDone, this, &BootloaderInstallHex::installStage2); | ||
126 | |||
127 | downloadBlStart(m_blurl); | ||
128 | return true; | ||
129 | } | ||
130 | |||
131 | |||
132 | void BootloaderInstallHex::installStage2(void) | ||
133 | { | ||
134 | emit logItem(tr("Adding bootloader to firmware file"), LOGINFO); | ||
135 | QCoreApplication::processEvents(); | ||
136 | |||
137 | // local temp file | ||
138 | QTemporaryFile tempbin; | ||
139 | tempbin.open(); | ||
140 | QString tempbinName = tempbin.fileName(); | ||
141 | tempbin.close(); | ||
142 | // get temporary files filenames -- external tools need this. | ||
143 | m_descrambled.open(); | ||
144 | QString descrambledName = m_descrambled.fileName(); | ||
145 | m_descrambled.close(); | ||
146 | m_tempfile.open(); | ||
147 | QString tempfileName = m_tempfile.fileName(); | ||
148 | m_tempfile.close(); | ||
149 | |||
150 | int origin = 0; | ||
151 | switch(m_model) { | ||
152 | case 3: | ||
153 | origin = 0x3f0000; | ||
154 | break; | ||
155 | case 2: | ||
156 | case 1: | ||
157 | origin = 0x1f0000; | ||
158 | break; | ||
159 | default: | ||
160 | origin = 0; | ||
161 | break; | ||
162 | } | ||
163 | |||
164 | // iriver decode already done in stage 1 | ||
165 | int result; | ||
166 | if((result = mkboot_iriver(descrambledName.toLocal8Bit().constData(), | ||
167 | tempfileName.toLocal8Bit().constData(), | ||
168 | tempbinName.toLocal8Bit().constData(), origin)) < 0) | ||
169 | { | ||
170 | QString error; | ||
171 | switch(result) { | ||
172 | case -1: error = tr("could not open input file"); break; | ||
173 | case -2: error = tr("reading header failed"); break; | ||
174 | case -3: error = tr("reading firmware failed"); break; | ||
175 | case -4: error = tr("can't open bootloader file"); break; | ||
176 | case -5: error = tr("reading bootloader file failed"); break; | ||
177 | case -6: error = tr("can't open output file"); break; | ||
178 | case -7: error = tr("writing output file failed"); break; | ||
179 | } | ||
180 | emit logItem(tr("Error in patching: %1").arg(error), LOGERROR); | ||
181 | |||
182 | emit done(true); | ||
183 | return; | ||
184 | } | ||
185 | QTemporaryFile targethex; | ||
186 | targethex.open(); | ||
187 | QString targethexName = targethex.fileName(); | ||
188 | if((result = iriver_encode(tempbinName.toLocal8Bit().constData(), | ||
189 | targethexName.toLocal8Bit().constData(), FALSE)) < 0) | ||
190 | { | ||
191 | emit logItem(tr("Error in scramble: %1").arg(scrambleError(result)), LOGERROR); | ||
192 | targethex.close(); | ||
193 | |||
194 | emit done(true); | ||
195 | return; | ||
196 | } | ||
197 | |||
198 | // finally check the md5sum of the created file | ||
199 | QByteArray filedata; | ||
200 | filedata = targethex.readAll(); | ||
201 | targethex.close(); | ||
202 | QString hash = QCryptographicHash::hash(filedata, | ||
203 | QCryptographicHash::Md5).toHex(); | ||
204 | LOG_INFO() << "created hexfile hash:" << hash; | ||
205 | |||
206 | emit logItem(tr("Checking modified firmware file"), LOGINFO); | ||
207 | if(hash != QString(md5sums[m_hashindex].patched)) { | ||
208 | emit logItem(tr("Error: modified file checksum wrong"), LOGERROR); | ||
209 | targethex.remove(); | ||
210 | emit done(true); | ||
211 | return; | ||
212 | } | ||
213 | // finally copy file to player | ||
214 | if(!Utils::resolvePathCase(m_blfile).isEmpty()) { | ||
215 | emit logItem(tr("A firmware file is already present on player"), LOGERROR); | ||
216 | emit done(true); | ||
217 | return; | ||
218 | } | ||
219 | if(targethex.copy(m_blfile)) { | ||
220 | emit logItem(tr("Success: modified firmware file created"), LOGINFO); | ||
221 | } | ||
222 | else { | ||
223 | emit logItem(tr("Copying modified firmware file failed"), LOGERROR); | ||
224 | emit done(true); | ||
225 | return; | ||
226 | } | ||
227 | |||
228 | logInstall(LogAdd); | ||
229 | emit done(false); | ||
230 | |||
231 | return; | ||
232 | } | ||
233 | |||
234 | |||
235 | bool BootloaderInstallHex::uninstall(void) | ||
236 | { | ||
237 | emit logItem(tr("Uninstallation not possible, only installation info removed"), LOGINFO); | ||
238 | logInstall(LogRemove); | ||
239 | emit done(true); | ||
240 | return false; | ||
241 | } | ||
242 | |||
243 | |||
244 | BootloaderInstallBase::BootloaderType BootloaderInstallHex::installed(void) | ||
245 | { | ||
246 | return BootloaderUnknown; | ||
247 | } | ||
248 | |||
249 | |||
250 | BootloaderInstallBase::Capabilities BootloaderInstallHex::capabilities(void) | ||
251 | { | ||
252 | return (Install | NeedsOf); | ||
253 | } | ||
254 | |||
255 | QString BootloaderInstallHex::scrambleError(int err) | ||
256 | { | ||
257 | QString error; | ||
258 | switch(err) { | ||
259 | case -1: error = tr("Can't open input file"); break; | ||
260 | case -2: error = tr("Can't open output file"); break; | ||
261 | case -3: error = tr("invalid file: header length wrong"); break; | ||
262 | case -4: error = tr("invalid file: unrecognized header"); break; | ||
263 | case -5: error = tr("invalid file: \"length\" field wrong"); break; | ||
264 | case -6: error = tr("invalid file: \"length2\" field wrong"); break; | ||
265 | case -7: error = tr("invalid file: internal checksum error"); break; | ||
266 | case -8: error = tr("invalid file: \"length3\" field wrong"); break; | ||
267 | default: error = tr("unknown"); break; | ||
268 | } | ||
269 | return error; | ||
270 | } | ||
271 | |||
diff --git a/utils/rbutilqt/base/bootloaderinstallhex.h b/utils/rbutilqt/base/bootloaderinstallhex.h new file mode 100644 index 0000000000..700d77447a --- /dev/null +++ b/utils/rbutilqt/base/bootloaderinstallhex.h | |||
@@ -0,0 +1,53 @@ | |||
1 | /*************************************************************************** | ||
2 | * __________ __ ___. | ||
3 | * Open \______ \ ____ ____ | | _\_ |__ _______ ___ | ||
4 | * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / | ||
5 | * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < | ||
6 | * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ | ||
7 | * \/ \/ \/ \/ \/ | ||
8 | * | ||
9 | * Copyright (C) 2008 by Dominik Riebeling | ||
10 | * | ||
11 | * All files in this archive are subject to the GNU General Public License. | ||
12 | * See the file COPYING in the source tree root for full license agreement. | ||
13 | * | ||
14 | * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY | ||
15 | * KIND, either express or implied. | ||
16 | * | ||
17 | ****************************************************************************/ | ||
18 | |||
19 | #ifndef BOOTLOADERINSTALLHEX_H | ||
20 | #define BOOTLOADERINSTALLHEX_H | ||
21 | |||
22 | #include <QtCore> | ||
23 | #include "bootloaderinstallbase.h" | ||
24 | |||
25 | |||
26 | // bootloader installation derivate based on fwpatcher | ||
27 | // This will patch a given hex file using (de)scramble / mkboot | ||
28 | // and put it on the player. | ||
29 | class BootloaderInstallHex : public BootloaderInstallBase | ||
30 | { | ||
31 | Q_OBJECT | ||
32 | |||
33 | public: | ||
34 | BootloaderInstallHex(QObject *parent = nullptr); | ||
35 | bool install(void); | ||
36 | bool uninstall(void); | ||
37 | BootloaderInstallBase::BootloaderType installed(void); | ||
38 | Capabilities capabilities(void); | ||
39 | QString ofHint(); | ||
40 | |||
41 | private: | ||
42 | int m_hashindex; | ||
43 | int m_model; | ||
44 | QTemporaryFile m_descrambled; | ||
45 | QString scrambleError(int); | ||
46 | |||
47 | private slots: | ||
48 | void installStage2(void); | ||
49 | }; | ||
50 | |||
51 | |||
52 | #endif | ||
53 | |||
diff --git a/utils/rbutilqt/base/bootloaderinstallimx.cpp b/utils/rbutilqt/base/bootloaderinstallimx.cpp new file mode 100644 index 0000000000..7428cf10c8 --- /dev/null +++ b/utils/rbutilqt/base/bootloaderinstallimx.cpp | |||
@@ -0,0 +1,193 @@ | |||
1 | /*************************************************************************** | ||
2 | * __________ __ ___. | ||
3 | * Open \______ \ ____ ____ | | _\_ |__ _______ ___ | ||
4 | * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / | ||
5 | * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < | ||
6 | * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ | ||
7 | * \/ \/ \/ \/ \/ | ||
8 | * | ||
9 | * Copyright (C) 2011 by Jean-Louis Biasini | ||
10 | * | ||
11 | * All files in this archive are subject to the GNU General Public License. | ||
12 | * See the file COPYING in the source tree root for full license agreement. | ||
13 | * | ||
14 | * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY | ||
15 | * KIND, either express or implied. | ||
16 | * | ||
17 | ****************************************************************************/ | ||
18 | |||
19 | #include <QtCore> | ||
20 | #include <QtDebug> | ||
21 | #include "bootloaderinstallbase.h" | ||
22 | #include "bootloaderinstallimx.h" | ||
23 | #include "../mkimxboot/mkimxboot.h" | ||
24 | #include "Logger.h" | ||
25 | |||
26 | // class for running mkimxboot() in a separate thread to keep the UI responsive. | ||
27 | class BootloaderThreadImx : public QThread | ||
28 | { | ||
29 | public: | ||
30 | void run(void); | ||
31 | void setInputFile(QString f) | ||
32 | { m_inputfile = f; } | ||
33 | void setOutputFile(QString f) | ||
34 | { m_outputfile = f; } | ||
35 | void setBootloaderFile(QString f) | ||
36 | { m_bootfile = f; } | ||
37 | enum imx_error_t error(void) | ||
38 | { return m_error; } | ||
39 | private: | ||
40 | QString m_inputfile; | ||
41 | QString m_bootfile; | ||
42 | QString m_outputfile; | ||
43 | enum imx_error_t m_error; | ||
44 | }; | ||
45 | |||
46 | |||
47 | void BootloaderThreadImx::run(void) | ||
48 | { | ||
49 | LOG_INFO() << "Thread started."; | ||
50 | struct imx_option_t opt; | ||
51 | memset(&opt, 0, sizeof(opt)); | ||
52 | opt.debug = false; | ||
53 | opt.output = IMX_DUALBOOT; | ||
54 | opt.fw_variant = VARIANT_DEFAULT; | ||
55 | |||
56 | m_error = mkimxboot(m_inputfile.toLocal8Bit().constData(), | ||
57 | m_bootfile.toLocal8Bit().constData(), | ||
58 | m_outputfile.toLocal8Bit().constData(), opt); | ||
59 | LOG_INFO() << "Thread finished, result:" << m_error; | ||
60 | } | ||
61 | |||
62 | |||
63 | BootloaderInstallImx::BootloaderInstallImx(QObject *parent) | ||
64 | : BootloaderInstallBase(parent) | ||
65 | { | ||
66 | m_thread = nullptr; | ||
67 | } | ||
68 | |||
69 | |||
70 | QString BootloaderInstallImx::ofHint() | ||
71 | { | ||
72 | return tr("Bootloader installation requires you to provide " | ||
73 | "a copy of the original Sandisk firmware (firmware.sb file). " | ||
74 | "This file will be patched with the Rockbox bootloader and " | ||
75 | "installed to your player. You need to download this file " | ||
76 | "yourself due to legal reasons. Please browse the " | ||
77 | "<a href='http://forums.sandisk.com/sansa/'>Sansa Forums</a> " | ||
78 | "or refer to the " | ||
79 | "<a href= 'http://www.rockbox.org/wiki/SansaFuzePlus'>SansaFuzePlus</a> " | ||
80 | "wiki page on how to obtain this file.<br/>" | ||
81 | "Press Ok to continue and browse your computer for the firmware " | ||
82 | "file."); | ||
83 | } | ||
84 | |||
85 | |||
86 | /** Start bootloader installation. | ||
87 | */ | ||
88 | bool BootloaderInstallImx::install(void) | ||
89 | { | ||
90 | if(!QFileInfo(m_offile).isReadable()) | ||
91 | { | ||
92 | LOG_ERROR() << "could not read original firmware file" | ||
93 | << m_offile; | ||
94 | emit logItem(tr("Could not read original firmware file"), LOGERROR); | ||
95 | return false; | ||
96 | } | ||
97 | |||
98 | LOG_INFO() << "downloading bootloader"; | ||
99 | // download bootloader from server | ||
100 | emit logItem(tr("Downloading bootloader file"), LOGINFO); | ||
101 | connect(this, &BootloaderInstallBase::downloadDone, this, &BootloaderInstallImx::installStage2); | ||
102 | downloadBlStart(m_blurl); | ||
103 | return true; | ||
104 | } | ||
105 | |||
106 | |||
107 | void BootloaderInstallImx::installStage2(void) | ||
108 | { | ||
109 | LOG_INFO() << "patching file..."; | ||
110 | emit logItem(tr("Patching file..."), LOGINFO); | ||
111 | m_tempfile.open(); | ||
112 | |||
113 | // we have not detailed progress on the patching so just show a busy | ||
114 | // indicator instead. | ||
115 | emit logProgress(0, 0); | ||
116 | m_patchedFile.open(); | ||
117 | m_thread = new BootloaderThreadImx(); | ||
118 | m_thread->setInputFile(m_offile); | ||
119 | m_thread->setBootloaderFile(m_tempfile.fileName()); | ||
120 | m_thread->setOutputFile(m_patchedFile.fileName()); | ||
121 | m_tempfile.close(); | ||
122 | m_patchedFile.close(); | ||
123 | connect(m_thread, &QThread::finished, this, &BootloaderInstallImx::installStage3); | ||
124 | connect(m_thread, SIGNAL(terminated()), this, SLOT(installStage3())); | ||
125 | m_thread->start(); | ||
126 | } | ||
127 | |||
128 | |||
129 | void BootloaderInstallImx::installStage3(void) | ||
130 | { | ||
131 | enum imx_error_t err = m_thread->error(); | ||
132 | emit logProgress(1, 1); | ||
133 | // if the patch failed | ||
134 | if (err != IMX_SUCCESS) | ||
135 | { | ||
136 | LOG_ERROR() << "Could not patch the original firmware file"; | ||
137 | emit logItem(tr("Patching the original firmware failed"), LOGERROR); | ||
138 | emit done(true); | ||
139 | return; | ||
140 | } | ||
141 | |||
142 | LOG_INFO() << "Original Firmware succesfully patched"; | ||
143 | emit logItem(tr("Succesfully patched firmware file"), LOGINFO); | ||
144 | |||
145 | // if a bootloader is already present delete it. | ||
146 | QString fwfile(m_blfile); | ||
147 | if(QFileInfo(fwfile).isFile()) | ||
148 | { | ||
149 | LOG_INFO() << "deleting old target file"; | ||
150 | QFile::remove(fwfile); | ||
151 | } | ||
152 | |||
153 | // place (new) bootloader. Copy, since the temporary file will be removed | ||
154 | // automatically. | ||
155 | LOG_INFO() << "moving patched bootloader to" << fwfile; | ||
156 | if(m_patchedFile.copy(fwfile)) | ||
157 | { | ||
158 | emit logItem(tr("Bootloader successful installed"), LOGOK); | ||
159 | logInstall(LogAdd); | ||
160 | emit done(false); | ||
161 | } | ||
162 | else | ||
163 | { | ||
164 | emit logItem(tr("Patched bootloader could not be installed"), LOGERROR); | ||
165 | emit done(true); | ||
166 | } | ||
167 | // clean up thread object. | ||
168 | delete m_thread; | ||
169 | return; | ||
170 | } | ||
171 | |||
172 | |||
173 | bool BootloaderInstallImx::uninstall(void) | ||
174 | { | ||
175 | emit logItem(tr("To uninstall, perform a normal upgrade with an unmodified " | ||
176 | "original firmware."), LOGINFO); | ||
177 | logInstall(LogRemove); | ||
178 | emit done(true); | ||
179 | return false; | ||
180 | } | ||
181 | |||
182 | |||
183 | BootloaderInstallBase::BootloaderType BootloaderInstallImx::installed(void) | ||
184 | { | ||
185 | return BootloaderUnknown; | ||
186 | } | ||
187 | |||
188 | |||
189 | BootloaderInstallBase::Capabilities BootloaderInstallImx::capabilities(void) | ||
190 | { | ||
191 | return (Install | NeedsOf); | ||
192 | } | ||
193 | |||
diff --git a/utils/rbutilqt/base/bootloaderinstallimx.h b/utils/rbutilqt/base/bootloaderinstallimx.h new file mode 100644 index 0000000000..1d780998ef --- /dev/null +++ b/utils/rbutilqt/base/bootloaderinstallimx.h | |||
@@ -0,0 +1,47 @@ | |||
1 | /*************************************************************************** | ||
2 | * __________ __ ___. | ||
3 | * Open \______ \ ____ ____ | | _\_ |__ _______ ___ | ||
4 | * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / | ||
5 | * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < | ||
6 | * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ | ||
7 | * \/ \/ \/ \/ \/ | ||
8 | * | ||
9 | * Copyright (C) 2011 by Jean-Louis Biasini | ||
10 | * | ||
11 | * All files in this archive are subject to the GNU General Public License. | ||
12 | * See the file COPYING in the source tree root for full license agreement. | ||
13 | * | ||
14 | * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY | ||
15 | * KIND, either express or implied. | ||
16 | * | ||
17 | ****************************************************************************/ | ||
18 | #ifndef BOOTLOADERINSTALLIMX_H | ||
19 | #define BOOTLOADERINSTALLIMX_H | ||
20 | |||
21 | #include <QtCore> | ||
22 | #include "bootloaderinstallbase.h" | ||
23 | |||
24 | class BootloaderThreadImx; | ||
25 | |||
26 | //! bootloader installation class for devices handled by mkimxboot. | ||
27 | class BootloaderInstallImx : public BootloaderInstallBase | ||
28 | { | ||
29 | Q_OBJECT | ||
30 | public: | ||
31 | BootloaderInstallImx(QObject *parent); | ||
32 | bool install(void); | ||
33 | bool uninstall(void); | ||
34 | BootloaderInstallBase::BootloaderType installed(void); | ||
35 | Capabilities capabilities(void); | ||
36 | QString ofHint(); | ||
37 | |||
38 | private slots: | ||
39 | void installStage2(void); | ||
40 | void installStage3(void); | ||
41 | |||
42 | private: | ||
43 | BootloaderThreadImx *m_thread; | ||
44 | QTemporaryFile m_patchedFile; | ||
45 | }; | ||
46 | |||
47 | #endif | ||
diff --git a/utils/rbutilqt/base/bootloaderinstallipod.cpp b/utils/rbutilqt/base/bootloaderinstallipod.cpp new file mode 100644 index 0000000000..807c6e870b --- /dev/null +++ b/utils/rbutilqt/base/bootloaderinstallipod.cpp | |||
@@ -0,0 +1,272 @@ | |||
1 | /*************************************************************************** | ||
2 | * __________ __ ___. | ||
3 | * Open \______ \ ____ ____ | | _\_ |__ _______ ___ | ||
4 | * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / | ||
5 | * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < | ||
6 | * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ | ||
7 | * \/ \/ \/ \/ \/ | ||
8 | * | ||
9 | * Copyright (C) 2008 by Dominik Riebeling | ||
10 | * | ||
11 | * All files in this archive are subject to the GNU General Public License. | ||
12 | * See the file COPYING in the source tree root for full license agreement. | ||
13 | * | ||
14 | * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY | ||
15 | * KIND, either express or implied. | ||
16 | * | ||
17 | ****************************************************************************/ | ||
18 | |||
19 | #include <QtCore> | ||
20 | #include "bootloaderinstallbase.h" | ||
21 | #include "bootloaderinstallipod.h" | ||
22 | |||
23 | #include "../ipodpatcher/ipodpatcher.h" | ||
24 | #include "utils.h" | ||
25 | #include "Logger.h" | ||
26 | |||
27 | |||
28 | BootloaderInstallIpod::BootloaderInstallIpod(QObject *parent) | ||
29 | : BootloaderInstallBase(parent) | ||
30 | { | ||
31 | (void)parent; | ||
32 | // initialize sector buffer. The sector buffer is part of the ipod_t | ||
33 | // structure, so a second instance of this class will have its own buffer. | ||
34 | ipod_alloc_buffer(&ipod, BUFFER_SIZE); | ||
35 | } | ||
36 | |||
37 | |||
38 | BootloaderInstallIpod::~BootloaderInstallIpod() | ||
39 | { | ||
40 | if(ipod.sectorbuf) { | ||
41 | ipod_dealloc_buffer(&ipod); | ||
42 | } | ||
43 | } | ||
44 | |||
45 | |||
46 | bool BootloaderInstallIpod::install(void) | ||
47 | { | ||
48 | if(ipod.sectorbuf == nullptr) { | ||
49 | emit logItem(tr("Error: can't allocate buffer memory!"), LOGERROR); | ||
50 | emit done(true); | ||
51 | return false; | ||
52 | } | ||
53 | // save buffer pointer before cleaning up ipod_t structure | ||
54 | unsigned char* sb = ipod.sectorbuf; | ||
55 | memset(&ipod, 0, sizeof(struct ipod_t)); | ||
56 | ipod.sectorbuf = sb; | ||
57 | |||
58 | if(!ipodInitialize(&ipod)) { | ||
59 | emit done(true); | ||
60 | return false; | ||
61 | } | ||
62 | |||
63 | if(ipod.nimages <= 0) { | ||
64 | emit logItem(tr("Failed to read firmware directory"), LOGERROR); | ||
65 | emit done(true); | ||
66 | return false; | ||
67 | } | ||
68 | if(getmodel(&ipod,(ipod.ipod_directory[ipod.ososimage].vers>>8)) < 0) { | ||
69 | emit logItem(tr("Unknown version number in firmware (%1)").arg( | ||
70 | ipod.ipod_directory[ipod.ososimage].vers), LOGERROR); | ||
71 | emit done(true); | ||
72 | return false; | ||
73 | } | ||
74 | if(ipod.macpod) { | ||
75 | emit logItem(tr("Warning: This is a MacPod, Rockbox only runs on WinPods. \n" | ||
76 | "See http://www.rockbox.org/wiki/IpodConversionToFAT32"), LOGERROR); | ||
77 | emit done(true); | ||
78 | return false; | ||
79 | } | ||
80 | emit logItem(tr("Downloading bootloader file"), LOGINFO); | ||
81 | |||
82 | downloadBlStart(m_blurl); | ||
83 | connect(this, &BootloaderInstallBase::downloadDone, this, &BootloaderInstallIpod::installStage2); | ||
84 | return true; | ||
85 | } | ||
86 | |||
87 | |||
88 | void BootloaderInstallIpod::installStage2(void) | ||
89 | { | ||
90 | emit logItem(tr("Installing Rockbox bootloader"), LOGINFO); | ||
91 | QCoreApplication::processEvents(); | ||
92 | |||
93 | if(ipod_reopen_rw(&ipod) < 0) { | ||
94 | emit logItem(tr("Could not open Ipod in R/W mode"), LOGERROR); | ||
95 | emit done(true); | ||
96 | return; | ||
97 | } | ||
98 | QCoreApplication::processEvents(); | ||
99 | |||
100 | m_tempfile.open(); | ||
101 | QString blfile = m_tempfile.fileName(); | ||
102 | m_tempfile.close(); | ||
103 | if(add_bootloader(&ipod, blfile.toLatin1().data(), FILETYPE_DOT_IPOD) == 0) { | ||
104 | emit logItem(tr("Successfull added bootloader"), LOGOK); | ||
105 | ipod_close(&ipod); | ||
106 | #if defined(Q_OS_MACX) | ||
107 | m_remountDevice = ipod.diskname; | ||
108 | connect(this, SIGNAL(remounted(bool)), this, SLOT(installStage3(bool))); | ||
109 | waitRemount(); | ||
110 | #else | ||
111 | installStage3(true); | ||
112 | #endif | ||
113 | } | ||
114 | else { | ||
115 | emit logItem(tr("Failed to add bootloader"), LOGERROR); | ||
116 | ipod_close(&ipod); | ||
117 | emit done(true); | ||
118 | return; | ||
119 | } | ||
120 | } | ||
121 | |||
122 | |||
123 | void BootloaderInstallIpod::installStage3(bool mounted) | ||
124 | { | ||
125 | if(mounted) { | ||
126 | logInstall(LogAdd); | ||
127 | emit logItem(tr("Bootloader Installation complete."), LOGINFO); | ||
128 | emit done(false); | ||
129 | return; | ||
130 | } | ||
131 | else { | ||
132 | emit logItem(tr("Writing log aborted"), LOGERROR); | ||
133 | emit done(true); | ||
134 | } | ||
135 | LOG_INFO() << "version installed:" | ||
136 | << m_blversion.toString(Qt::ISODate); | ||
137 | } | ||
138 | |||
139 | |||
140 | bool BootloaderInstallIpod::uninstall(void) | ||
141 | { | ||
142 | emit logItem(tr("Uninstalling bootloader"), LOGINFO); | ||
143 | QCoreApplication::processEvents(); | ||
144 | |||
145 | if(!ipodInitialize(&ipod)) { | ||
146 | emit done(true); | ||
147 | return false; | ||
148 | } | ||
149 | |||
150 | if (ipod.nimages <= 0) { | ||
151 | emit logItem(tr("Failed to read firmware directory"),LOGERROR); | ||
152 | emit done(true); | ||
153 | return false; | ||
154 | } | ||
155 | if (getmodel(&ipod,(ipod.ipod_directory[ipod.ososimage].vers>>8)) < 0) { | ||
156 | emit logItem(tr("Unknown version number in firmware (%1)").arg( | ||
157 | ipod.ipod_directory[ipod.ososimage].vers), LOGERROR); | ||
158 | emit done(true); | ||
159 | return false; | ||
160 | } | ||
161 | |||
162 | if (ipod_reopen_rw(&ipod) < 0) { | ||
163 | emit logItem(tr("Could not open Ipod in R/W mode"), LOGERROR); | ||
164 | emit done(true); | ||
165 | return false; | ||
166 | } | ||
167 | |||
168 | if (ipod_has_bootloader(&ipod) == 0) { | ||
169 | emit logItem(tr("No bootloader detected."), LOGERROR); | ||
170 | emit done(true); | ||
171 | return false; | ||
172 | } | ||
173 | |||
174 | if (delete_bootloader(&ipod)==0) { | ||
175 | emit logItem(tr("Successfully removed bootloader"), LOGOK); | ||
176 | logInstall(LogRemove); | ||
177 | emit logProgress(1, 1); | ||
178 | emit done(false); | ||
179 | ipod_close(&ipod); | ||
180 | return true; | ||
181 | } | ||
182 | else { | ||
183 | emit logItem(tr("Removing bootloader failed."), LOGERROR); | ||
184 | emit done(true); | ||
185 | ipod_close(&ipod); | ||
186 | return false; | ||
187 | } | ||
188 | } | ||
189 | |||
190 | |||
191 | BootloaderInstallBase::BootloaderType BootloaderInstallIpod::installed(void) | ||
192 | { | ||
193 | BootloaderInstallBase::BootloaderType result = BootloaderRockbox; | ||
194 | |||
195 | if(!ipodInitialize(&ipod)) { | ||
196 | LOG_INFO() << "installed: BootloaderUnknown"; | ||
197 | result = BootloaderUnknown; | ||
198 | } | ||
199 | else { | ||
200 | read_directory(&ipod); | ||
201 | getmodel(&ipod,(ipod.ipod_directory[ipod.ososimage].vers>>8)); | ||
202 | if(!ipod_has_bootloader(&ipod)) { | ||
203 | result = BootloaderOther; | ||
204 | } | ||
205 | else { | ||
206 | LOG_INFO() << "installed: BootloaderRockbox"; | ||
207 | } | ||
208 | } | ||
209 | ipod_close(&ipod); | ||
210 | |||
211 | return result; | ||
212 | } | ||
213 | |||
214 | |||
215 | BootloaderInstallBase::Capabilities BootloaderInstallIpod::capabilities(void) | ||
216 | { | ||
217 | return (Install | Uninstall | IsRaw); | ||
218 | } | ||
219 | |||
220 | |||
221 | /** @initialize Ipod by opening its file handle and checking if its an ipod. | ||
222 | * Note: the caller has to make sure the file handle gets closed! | ||
223 | */ | ||
224 | bool BootloaderInstallIpod::ipodInitialize(struct ipod_t *ipod) | ||
225 | { | ||
226 | if(!m_blfile.isEmpty()) { | ||
227 | QString devicename = Utils::resolveDevicename(m_blfile); | ||
228 | if(devicename.isEmpty()) { | ||
229 | emit logItem(tr("Error: could not retrieve device name"), LOGERROR); | ||
230 | return false; | ||
231 | } | ||
232 | #if defined(Q_OS_WIN32) | ||
233 | sprintf(ipod->diskname, "\\\\.\\PhysicalDrive%i", devicename.toInt()); | ||
234 | #elif defined(Q_OS_MACX) | ||
235 | sprintf(ipod->diskname, "%s", | ||
236 | qPrintable(devicename.remove(QRegExp("s[0-9]+$")))); | ||
237 | #else | ||
238 | sprintf(ipod->diskname, "%s", | ||
239 | qPrintable(devicename.remove(QRegExp("[0-9]+$")))); | ||
240 | #endif | ||
241 | LOG_INFO() << "ipodpatcher: overriding scan, using" | ||
242 | << ipod->diskname; | ||
243 | } | ||
244 | else { | ||
245 | emit logItem(tr("Error: no mountpoint specified!"), LOGERROR); | ||
246 | LOG_ERROR() << "no mountpoint specified!"; | ||
247 | } | ||
248 | int result = ipod_open(ipod, 1); | ||
249 | if(result == -2) { | ||
250 | emit logItem(tr("Could not open Ipod: permission denied"), LOGERROR); | ||
251 | return false; | ||
252 | } | ||
253 | else if(result < 0) { | ||
254 | emit logItem(tr("Could not open Ipod"), LOGERROR); | ||
255 | return false; | ||
256 | } | ||
257 | |||
258 | if(read_partinfo(ipod, 1) < 0) { | ||
259 | emit logItem(tr("Error reading partition table - possibly not an Ipod"), LOGERROR); | ||
260 | ipod_close(ipod); | ||
261 | return false; | ||
262 | } | ||
263 | |||
264 | if(ipod->pinfo[0].start == 0) { | ||
265 | emit logItem(tr("No firmware partition on disk"), LOGERROR); | ||
266 | ipod_close(ipod); | ||
267 | return false; | ||
268 | } | ||
269 | read_directory(ipod); | ||
270 | return true; | ||
271 | } | ||
272 | |||
diff --git a/utils/rbutilqt/base/bootloaderinstallipod.h b/utils/rbutilqt/base/bootloaderinstallipod.h new file mode 100644 index 0000000000..ac69c608d6 --- /dev/null +++ b/utils/rbutilqt/base/bootloaderinstallipod.h | |||
@@ -0,0 +1,51 @@ | |||
1 | /*************************************************************************** | ||
2 | * __________ __ ___. | ||
3 | * Open \______ \ ____ ____ | | _\_ |__ _______ ___ | ||
4 | * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / | ||
5 | * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < | ||
6 | * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ | ||
7 | * \/ \/ \/ \/ \/ | ||
8 | * | ||
9 | * Copyright (C) 2008 by Dominik Riebeling | ||
10 | * | ||
11 | * All files in this archive are subject to the GNU General Public License. | ||
12 | * See the file COPYING in the source tree root for full license agreement. | ||
13 | * | ||
14 | * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY | ||
15 | * KIND, either express or implied. | ||
16 | * | ||
17 | ****************************************************************************/ | ||
18 | |||
19 | #ifndef BOOTLOADERINSTALLIPOD_H | ||
20 | #define BOOTLOADERINSTALLIPOD_H | ||
21 | |||
22 | #include <QtCore> | ||
23 | #include "bootloaderinstallbase.h" | ||
24 | #include "../ipodpatcher/ipodpatcher.h" | ||
25 | |||
26 | // installer class derivate for Ipod installation | ||
27 | // based on ipodpatcher. | ||
28 | class BootloaderInstallIpod : public BootloaderInstallBase | ||
29 | { | ||
30 | Q_OBJECT | ||
31 | |||
32 | public: | ||
33 | BootloaderInstallIpod(QObject *parent); | ||
34 | ~BootloaderInstallIpod(); | ||
35 | bool install(void); | ||
36 | bool uninstall(void); | ||
37 | BootloaderInstallBase::BootloaderType installed(void); | ||
38 | Capabilities capabilities(void); | ||
39 | |||
40 | private slots: | ||
41 | void installStage2(void); | ||
42 | void installStage3(bool mounted); | ||
43 | |||
44 | private: | ||
45 | bool ipodInitialize(struct ipod_t *); | ||
46 | struct ipod_t ipod; | ||
47 | }; | ||
48 | |||
49 | |||
50 | #endif | ||
51 | |||
diff --git a/utils/rbutilqt/base/bootloaderinstallmi4.cpp b/utils/rbutilqt/base/bootloaderinstallmi4.cpp new file mode 100644 index 0000000000..2657347b87 --- /dev/null +++ b/utils/rbutilqt/base/bootloaderinstallmi4.cpp | |||
@@ -0,0 +1,162 @@ | |||
1 | /*************************************************************************** | ||
2 | * __________ __ ___. | ||
3 | * Open \______ \ ____ ____ | | _\_ |__ _______ ___ | ||
4 | * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / | ||
5 | * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < | ||
6 | * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ | ||
7 | * \/ \/ \/ \/ \/ | ||
8 | * | ||
9 | * Copyright (C) 2008 by Dominik Riebeling | ||
10 | * | ||
11 | * All files in this archive are subject to the GNU General Public License. | ||
12 | * See the file COPYING in the source tree root for full license agreement. | ||
13 | * | ||
14 | * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY | ||
15 | * KIND, either express or implied. | ||
16 | * | ||
17 | ****************************************************************************/ | ||
18 | |||
19 | #include <QtCore> | ||
20 | #include <QtDebug> | ||
21 | #include <QtDebug> | ||
22 | #include "bootloaderinstallmi4.h" | ||
23 | #include "utils.h" | ||
24 | #include "Logger.h" | ||
25 | |||
26 | BootloaderInstallMi4::BootloaderInstallMi4(QObject *parent) | ||
27 | : BootloaderInstallBase(parent) | ||
28 | { | ||
29 | } | ||
30 | |||
31 | |||
32 | bool BootloaderInstallMi4::install(void) | ||
33 | { | ||
34 | emit logItem(tr("Downloading bootloader"), LOGINFO); | ||
35 | LOG_INFO() << "installing bootloader"; | ||
36 | downloadBlStart(m_blurl); | ||
37 | connect(this, &BootloaderInstallBase::downloadDone, this, &BootloaderInstallMi4::installStage2); | ||
38 | return true; | ||
39 | } | ||
40 | |||
41 | void BootloaderInstallMi4::installStage2(void) | ||
42 | { | ||
43 | emit logItem(tr("Installing Rockbox bootloader"), LOGINFO); | ||
44 | QCoreApplication::processEvents(); | ||
45 | |||
46 | // move old bootloader out of the way | ||
47 | QString fwfile(Utils::resolvePathCase(m_blfile)); | ||
48 | QFile oldbl(fwfile); | ||
49 | QString moved = QFileInfo(Utils::resolvePathCase(m_blfile)).absolutePath() | ||
50 | + "/OF.mi4"; | ||
51 | if(!QFileInfo::exists(moved)) { | ||
52 | LOG_INFO() << "renaming" << fwfile << "to" << moved; | ||
53 | oldbl.rename(moved); | ||
54 | } | ||
55 | else { | ||
56 | LOG_INFO() << "OF.mi4 already present, not renaming again."; | ||
57 | oldbl.remove(); | ||
58 | } | ||
59 | |||
60 | // place new bootloader | ||
61 | m_tempfile.open(); | ||
62 | LOG_INFO() << "renaming" << m_tempfile.fileName() | ||
63 | << "to" << fwfile; | ||
64 | m_tempfile.close(); | ||
65 | if(!Utils::resolvePathCase(fwfile).isEmpty()) { | ||
66 | emit logItem(tr("A firmware file is already present on player"), LOGERROR); | ||
67 | emit done(true); | ||
68 | return; | ||
69 | } | ||
70 | if(m_tempfile.copy(fwfile)) { | ||
71 | emit logItem(tr("Bootloader successful installed"), LOGOK); | ||
72 | } | ||
73 | else { | ||
74 | emit logItem(tr("Copying modified firmware file failed"), LOGERROR); | ||
75 | emit done(true); | ||
76 | return; | ||
77 | } | ||
78 | |||
79 | emit logItem(tr("Bootloader successful installed"), LOGOK); | ||
80 | logInstall(LogAdd); | ||
81 | |||
82 | emit done(false); | ||
83 | } | ||
84 | |||
85 | |||
86 | bool BootloaderInstallMi4::uninstall(void) | ||
87 | { | ||
88 | LOG_INFO() << "Uninstalling bootloader"; | ||
89 | |||
90 | // check if it's actually a Rockbox bootloader | ||
91 | emit logItem(tr("Checking for Rockbox bootloader"), LOGINFO); | ||
92 | if(installed() != BootloaderRockbox) { | ||
93 | emit logItem(tr("No Rockbox bootloader found"), LOGERROR); | ||
94 | emit done(true); | ||
95 | return false; | ||
96 | } | ||
97 | |||
98 | // check if OF file present | ||
99 | emit logItem(tr("Checking for original firmware file"), LOGINFO); | ||
100 | QString original = QFileInfo(Utils::resolvePathCase(m_blfile)).absolutePath() | ||
101 | + "/OF.mi4"; | ||
102 | |||
103 | if(Utils::resolvePathCase(original).isEmpty()) { | ||
104 | emit logItem(tr("Error finding original firmware file"), LOGERROR); | ||
105 | emit done(true); | ||
106 | return false; | ||
107 | } | ||
108 | |||
109 | // finally remove RB bootloader | ||
110 | QString resolved = Utils::resolvePathCase(m_blfile); | ||
111 | QFile blfile(resolved); | ||
112 | blfile.remove(); | ||
113 | |||
114 | QFile::rename(Utils::resolvePathCase(original), resolved); | ||
115 | emit logItem(tr("Rockbox bootloader successful removed"), LOGINFO); | ||
116 | logInstall(LogRemove); | ||
117 | emit logProgress(1, 1); | ||
118 | emit done(false); | ||
119 | |||
120 | return true; | ||
121 | } | ||
122 | |||
123 | |||
124 | //! check if a bootloader is installed and return its state. | ||
125 | BootloaderInstallBase::BootloaderType BootloaderInstallMi4::installed(void) | ||
126 | { | ||
127 | // for MI4 files we can check if we actually have a RB bootloader | ||
128 | // installed. | ||
129 | // RB bootloader has "RBBL" at 0x1f8 in the mi4 file. | ||
130 | |||
131 | // make sure to resolve case to prevent case issues | ||
132 | QString resolved; | ||
133 | resolved = Utils::resolvePathCase(m_blfile); | ||
134 | if(resolved.isEmpty()) { | ||
135 | LOG_INFO() << "installed: BootloaderNone"; | ||
136 | return BootloaderNone; | ||
137 | } | ||
138 | |||
139 | QFile f(resolved); | ||
140 | f.open(QIODevice::ReadOnly); | ||
141 | f.seek(0x1f8); | ||
142 | char magic[4]; | ||
143 | f.read(magic, 4); | ||
144 | f.close(); | ||
145 | |||
146 | if(!memcmp(magic, "RBBL", 4)) { | ||
147 | LOG_INFO() << "installed: BootloaderRockbox"; | ||
148 | return BootloaderRockbox; | ||
149 | } | ||
150 | else { | ||
151 | LOG_INFO() << "installed: BootloaderOther"; | ||
152 | return BootloaderOther; | ||
153 | } | ||
154 | } | ||
155 | |||
156 | |||
157 | BootloaderInstallBase::Capabilities BootloaderInstallMi4::capabilities(void) | ||
158 | { | ||
159 | LOG_INFO() << "getting capabilities"; | ||
160 | return Install | Uninstall | Backup | IsFile | CanCheckInstalled | CanCheckVersion; | ||
161 | } | ||
162 | |||
diff --git a/utils/rbutilqt/base/bootloaderinstallmi4.h b/utils/rbutilqt/base/bootloaderinstallmi4.h new file mode 100644 index 0000000000..c56669b99f --- /dev/null +++ b/utils/rbutilqt/base/bootloaderinstallmi4.h | |||
@@ -0,0 +1,48 @@ | |||
1 | /*************************************************************************** | ||
2 | * __________ __ ___. | ||
3 | * Open \______ \ ____ ____ | | _\_ |__ _______ ___ | ||
4 | * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / | ||
5 | * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < | ||
6 | * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ | ||
7 | * \/ \/ \/ \/ \/ | ||
8 | * | ||
9 | * Copyright (C) 2008 by Dominik Riebeling | ||
10 | * | ||
11 | * All files in this archive are subject to the GNU General Public License. | ||
12 | * See the file COPYING in the source tree root for full license agreement. | ||
13 | * | ||
14 | * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY | ||
15 | * KIND, either express or implied. | ||
16 | * | ||
17 | ****************************************************************************/ | ||
18 | |||
19 | #ifndef BOOTLOADERINSTALLMI4_H | ||
20 | #define BOOTLOADERINSTALLMI4_H | ||
21 | |||
22 | #include <QtCore> | ||
23 | #include "progressloggerinterface.h" | ||
24 | #include "bootloaderinstallbase.h" | ||
25 | |||
26 | |||
27 | // mi4 bootloader file based installation. | ||
28 | // Puts the bootloader file to the correct location and | ||
29 | // renames the OF to OF.mi4. | ||
30 | class BootloaderInstallMi4 : public BootloaderInstallBase | ||
31 | { | ||
32 | Q_OBJECT | ||
33 | |||
34 | public: | ||
35 | BootloaderInstallMi4(QObject *parent); | ||
36 | bool install(void); | ||
37 | bool uninstall(void); | ||
38 | BootloaderInstallBase::BootloaderType installed(void); | ||
39 | Capabilities capabilities(void); | ||
40 | |||
41 | private slots: | ||
42 | void installStage2(void); | ||
43 | |||
44 | private: | ||
45 | }; | ||
46 | |||
47 | #endif | ||
48 | |||
diff --git a/utils/rbutilqt/base/bootloaderinstallmpio.cpp b/utils/rbutilqt/base/bootloaderinstallmpio.cpp new file mode 100644 index 0000000000..863418e591 --- /dev/null +++ b/utils/rbutilqt/base/bootloaderinstallmpio.cpp | |||
@@ -0,0 +1,143 @@ | |||
1 | /*************************************************************************** | ||
2 | * __________ __ ___. | ||
3 | * Open \______ \ ____ ____ | | _\_ |__ _______ ___ | ||
4 | * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / | ||
5 | * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < | ||
6 | * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ | ||
7 | * \/ \/ \/ \/ \/ | ||
8 | * | ||
9 | * Copyright (C) 2008 by Dominik Wenger | ||
10 | * $Id: bootloaderinstallams.cpp 24778 2010-02-19 23:45:29Z funman $ | ||
11 | * | ||
12 | * All files in this archive are subject to the GNU General Public License. | ||
13 | * See the file COPYING in the source tree root for full license agreement. | ||
14 | * | ||
15 | * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY | ||
16 | * KIND, either express or implied. | ||
17 | * | ||
18 | ****************************************************************************/ | ||
19 | |||
20 | #include <QtCore> | ||
21 | #include "bootloaderinstallbase.h" | ||
22 | #include "bootloaderinstallmpio.h" | ||
23 | #include "Logger.h" | ||
24 | |||
25 | #include "../mkmpioboot/mkmpioboot.h" | ||
26 | |||
27 | BootloaderInstallMpio::BootloaderInstallMpio(QObject *parent) | ||
28 | : BootloaderInstallBase(parent) | ||
29 | { | ||
30 | } | ||
31 | |||
32 | QString BootloaderInstallMpio::ofHint() | ||
33 | { | ||
34 | return tr("Bootloader installation requires you to provide " | ||
35 | "a firmware file of the original firmware (bin file). " | ||
36 | "You need to download this file yourself due to legal " | ||
37 | "reasons. Please refer to the " | ||
38 | "<a href='http://www.rockbox.org/manual.shtml'>manual</a> and " | ||
39 | "the <a href='http://www.rockbox.org/wiki/MPIOHD200Port'>MPIOHD200Port</a> " | ||
40 | "wiki page on how to obtain this file.<br/>" | ||
41 | "Press Ok to continue and browse your computer for the firmware " | ||
42 | "file."); | ||
43 | } | ||
44 | |||
45 | bool BootloaderInstallMpio::install(void) | ||
46 | { | ||
47 | if(m_offile.isEmpty()) | ||
48 | return false; | ||
49 | |||
50 | LOG_INFO() << "installing bootloader"; | ||
51 | |||
52 | // download firmware from server | ||
53 | emit logItem(tr("Downloading bootloader file"), LOGINFO); | ||
54 | |||
55 | connect(this, &BootloaderInstallBase::downloadDone, this, &BootloaderInstallMpio::installStage2); | ||
56 | downloadBlStart(m_blurl); | ||
57 | |||
58 | return true; | ||
59 | } | ||
60 | |||
61 | void BootloaderInstallMpio::installStage2(void) | ||
62 | { | ||
63 | LOG_INFO() << "installStage2"; | ||
64 | |||
65 | int origin = 0xe0000; /* MPIO HD200 bootloader address */ | ||
66 | |||
67 | m_tempfile.open(); | ||
68 | QString bootfile = m_tempfile.fileName(); | ||
69 | m_tempfile.close(); | ||
70 | |||
71 | int ret = mkmpioboot(m_offile.toLocal8Bit().data(), | ||
72 | bootfile.toLocal8Bit().data(), m_blfile.toLocal8Bit().data(), origin); | ||
73 | |||
74 | if(ret != 0) | ||
75 | { | ||
76 | QString error; | ||
77 | switch(ret) | ||
78 | { | ||
79 | case -1: | ||
80 | error = tr("Could not open the original firmware."); | ||
81 | break; | ||
82 | case -2: | ||
83 | error = tr("Could not read the original firmware."); | ||
84 | break; | ||
85 | case -3: | ||
86 | error = tr("Loaded firmware file does not look like MPIO original firmware file."); | ||
87 | break; | ||
88 | case -4: | ||
89 | error = tr("Could not open downloaded bootloader."); | ||
90 | break; | ||
91 | case -5: | ||
92 | error = tr("Place for bootloader in OF file not empty."); | ||
93 | break; | ||
94 | case -6: | ||
95 | error = tr("Could not read the downloaded bootloader."); | ||
96 | break; | ||
97 | case -7: | ||
98 | error = tr("Bootloader checksum error."); | ||
99 | break; | ||
100 | case -8: | ||
101 | error = tr("Could not open output file."); | ||
102 | break; | ||
103 | case -9: | ||
104 | error = tr("Could not write output file."); | ||
105 | break; | ||
106 | default: | ||
107 | error = tr("Unknown error number: %1").arg(ret); | ||
108 | break; | ||
109 | } | ||
110 | |||
111 | LOG_ERROR() << "Patching original firmware failed:" << error; | ||
112 | emit logItem(tr("Patching original firmware failed: %1").arg(error), LOGERROR); | ||
113 | emit done(true); | ||
114 | return; | ||
115 | } | ||
116 | |||
117 | //end of install | ||
118 | LOG_INFO() << "install successful"; | ||
119 | emit logItem(tr("Success: modified firmware file created"), LOGINFO); | ||
120 | logInstall(LogAdd); | ||
121 | emit done(false); | ||
122 | return; | ||
123 | } | ||
124 | |||
125 | bool BootloaderInstallMpio::uninstall(void) | ||
126 | { | ||
127 | emit logItem(tr("To uninstall, perform a normal upgrade with an unmodified " | ||
128 | "original firmware"), LOGINFO); | ||
129 | logInstall(LogRemove); | ||
130 | emit done(true); | ||
131 | return false; | ||
132 | } | ||
133 | |||
134 | BootloaderInstallBase::BootloaderType BootloaderInstallMpio::installed(void) | ||
135 | { | ||
136 | return BootloaderUnknown; | ||
137 | } | ||
138 | |||
139 | BootloaderInstallBase::Capabilities BootloaderInstallMpio::capabilities(void) | ||
140 | { | ||
141 | return (Install | NeedsOf); | ||
142 | } | ||
143 | |||
diff --git a/utils/rbutilqt/base/bootloaderinstallmpio.h b/utils/rbutilqt/base/bootloaderinstallmpio.h new file mode 100644 index 0000000000..8e6c65affe --- /dev/null +++ b/utils/rbutilqt/base/bootloaderinstallmpio.h | |||
@@ -0,0 +1,43 @@ | |||
1 | /*************************************************************************** | ||
2 | * __________ __ ___. | ||
3 | * Open \______ \ ____ ____ | | _\_ |__ _______ ___ | ||
4 | * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / | ||
5 | * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < | ||
6 | * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ | ||
7 | * \/ \/ \/ \/ \/ | ||
8 | * | ||
9 | * Copyright (C) 2008 by Dominik Wenger | ||
10 | * $Id: bootloaderinstallams.h 22317 2009-08-15 13:04:21Z Domonoky $ | ||
11 | * | ||
12 | * All files in this archive are subject to the GNU General Public License. | ||
13 | * See the file COPYING in the source tree root for full license agreement. | ||
14 | * | ||
15 | * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY | ||
16 | * KIND, either express or implied. | ||
17 | * | ||
18 | ****************************************************************************/ | ||
19 | #ifndef BOOTLOADERINSTALLMPIO_H | ||
20 | #define BOOTLOADERINSTALLMPIO_H | ||
21 | |||
22 | #include <QtCore> | ||
23 | #include "bootloaderinstallbase.h" | ||
24 | |||
25 | //! bootloader installation derivate based on mkmpioboot | ||
26 | class BootloaderInstallMpio : public BootloaderInstallBase | ||
27 | { | ||
28 | Q_OBJECT | ||
29 | public: | ||
30 | BootloaderInstallMpio(QObject *parent); | ||
31 | bool install(void); | ||
32 | bool uninstall(void); | ||
33 | BootloaderInstallBase::BootloaderType installed(void); | ||
34 | Capabilities capabilities(void); | ||
35 | QString ofHint(); | ||
36 | |||
37 | private: | ||
38 | |||
39 | private slots: | ||
40 | void installStage2(void); | ||
41 | }; | ||
42 | |||
43 | #endif | ||
diff --git a/utils/rbutilqt/base/bootloaderinstalls5l.cpp b/utils/rbutilqt/base/bootloaderinstalls5l.cpp new file mode 100644 index 0000000000..63a30ff2b0 --- /dev/null +++ b/utils/rbutilqt/base/bootloaderinstalls5l.cpp | |||
@@ -0,0 +1,437 @@ | |||
1 | /*************************************************************************** | ||
2 | * __________ __ ___. | ||
3 | * Open \______ \ ____ ____ | | _\_ |__ _______ ___ | ||
4 | * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / | ||
5 | * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < | ||
6 | * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ | ||
7 | * \/ \/ \/ \/ \/ | ||
8 | * | ||
9 | * Copyright (C) 2008 by Dominik Riebeling | ||
10 | * | ||
11 | * All files in this archive are subject to the GNU General Public License. | ||
12 | * See the file COPYING in the source tree root for full license agreement. | ||
13 | * | ||
14 | * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY | ||
15 | * KIND, either express or implied. | ||
16 | * | ||
17 | ****************************************************************************/ | ||
18 | |||
19 | #include <QtCore> | ||
20 | #include "bootloaderinstallbase.h" | ||
21 | #include "bootloaderinstalls5l.h" | ||
22 | #include "Logger.h" | ||
23 | #include "utils.h" | ||
24 | #include "system.h" | ||
25 | #include "rbsettings.h" | ||
26 | #include "playerbuildinfo.h" | ||
27 | |||
28 | #include "../mks5lboot/mks5lboot.h" | ||
29 | |||
30 | |||
31 | BootloaderInstallS5l::BootloaderInstallS5l(QObject *parent) | ||
32 | : BootloaderInstallBase(parent) | ||
33 | { | ||
34 | } | ||
35 | |||
36 | |||
37 | bool BootloaderInstallS5l::install(void) | ||
38 | { | ||
39 | LOG_INFO() << "installing bootloader"; | ||
40 | doInstall = true; | ||
41 | return installStage1(); | ||
42 | } | ||
43 | |||
44 | |||
45 | bool BootloaderInstallS5l::uninstall(void) | ||
46 | { | ||
47 | LOG_INFO() << "uninstalling bootloader"; | ||
48 | doInstall = false; | ||
49 | return installStage1(); | ||
50 | } | ||
51 | |||
52 | |||
53 | bool BootloaderInstallS5l::installStage1(void) | ||
54 | { | ||
55 | LOG_INFO() << "installStage1"; | ||
56 | |||
57 | mntpoint = RbSettings::value(RbSettings::Mountpoint).toString(); | ||
58 | |||
59 | if (!Utils::mountpoints(Utils::MountpointsSupported).contains(mntpoint)) { | ||
60 | LOG_ERROR() << "iPod not mounted:" << mntpoint; | ||
61 | emit logItem(tr("Could not find mounted iPod."), LOGERROR); | ||
62 | emit done(true); | ||
63 | return false; | ||
64 | } | ||
65 | |||
66 | if (doInstall) { | ||
67 | // download firmware from server | ||
68 | emit logItem(tr("Downloading bootloader file..."), LOGINFO); | ||
69 | connect(this, &BootloaderInstallBase::downloadDone, | ||
70 | this, &BootloaderInstallS5l::installStageMkdfu); | ||
71 | downloadBlStart(m_blurl); | ||
72 | } | ||
73 | else { | ||
74 | installStageMkdfu(); | ||
75 | } | ||
76 | |||
77 | return true; | ||
78 | } | ||
79 | |||
80 | |||
81 | void BootloaderInstallS5l::installStageMkdfu(void) | ||
82 | { | ||
83 | int dfu_type; | ||
84 | QString dfu_arg; | ||
85 | char errstr[200]; | ||
86 | |||
87 | LOG_INFO() << "installStageMkdfu"; | ||
88 | |||
89 | setProgress(0); | ||
90 | aborted = false; | ||
91 | connect(this, &BootloaderInstallBase::installAborted, | ||
92 | this, &BootloaderInstallS5l::abortInstall); | ||
93 | connect(this, &BootloaderInstallBase::done, | ||
94 | this, &BootloaderInstallS5l::installDone); | ||
95 | |||
96 | if (doInstall) { | ||
97 | dfu_type = DFU_INST; | ||
98 | m_tempfile.open(); | ||
99 | dfu_arg = m_tempfile.fileName(); | ||
100 | m_tempfile.close(); | ||
101 | } | ||
102 | else { | ||
103 | dfu_type = DFU_UNINST; | ||
104 | dfu_arg = RbSettings::value(RbSettings::Platform).toString(); | ||
105 | } | ||
106 | |||
107 | // build DFU image | ||
108 | dfu_buf = mkdfu(dfu_type, dfu_arg.toLocal8Bit().data(), | ||
109 | &dfu_size, errstr, sizeof(errstr)); | ||
110 | if (!dfu_buf) { | ||
111 | LOG_ERROR() << "mkdfu() failed:" << errstr; | ||
112 | emit logItem(errstr, LOGERROR); | ||
113 | emit logItem(tr("Could not make DFU image."), LOGERROR); | ||
114 | emit done(true); | ||
115 | return; | ||
116 | } | ||
117 | |||
118 | LOG_INFO() << "preparing installStageWaitForEject"; | ||
119 | emit logItem(tr("Ejecting iPod..."), LOGINFO); | ||
120 | setProgress(10); | ||
121 | scanTimer.invalidate(); | ||
122 | installStageWaitForEject(); | ||
123 | } | ||
124 | |||
125 | |||
126 | void BootloaderInstallS5l::installStageWaitForEject(void) | ||
127 | { | ||
128 | if (!updateProgress()) | ||
129 | return; /* aborted */ | ||
130 | |||
131 | if (!scanTimer.isValid() || (scanTimer.elapsed() > 3000)) { | ||
132 | scanSuccess = Utils::ejectDevice(mntpoint); | ||
133 | if (!scanSuccess) { | ||
134 | scanSuccess = !Utils::mountpoints( | ||
135 | Utils::MountpointsSupported).contains(mntpoint); | ||
136 | } | ||
137 | scanTimer.start(); | ||
138 | } | ||
139 | if (!scanSuccess) { | ||
140 | if (!actionShown) { | ||
141 | emit logItem(tr("Action required:\n\n" | ||
142 | "Please make sure no programs are accessing " | ||
143 | "files on the device. If ejecting still fails " | ||
144 | "please use your computers eject functionality."), | ||
145 | LOGWARNING); | ||
146 | actionShown = true; | ||
147 | } | ||
148 | QTimer::singleShot(250, this, &BootloaderInstallS5l::installStageWaitForEject); | ||
149 | return; | ||
150 | } | ||
151 | emit logItem(tr("Device successfully ejected."), LOGINFO); | ||
152 | |||
153 | LOG_INFO() << "preparing installStageWaitForProcs"; | ||
154 | setProgress(40, 18); | ||
155 | scanTimer.invalidate(); | ||
156 | installStageWaitForProcs(); | ||
157 | } | ||
158 | |||
159 | |||
160 | void BootloaderInstallS5l::installStageWaitForProcs(void) | ||
161 | { | ||
162 | if (!updateProgress()) | ||
163 | return; /* aborted */ | ||
164 | |||
165 | if (!scanTimer.isValid() || (scanTimer.elapsed() > 1000)) { | ||
166 | scanSuccess = Utils::findRunningProcess(QStringList("iTunes")).isEmpty(); | ||
167 | scanTimer.start(); | ||
168 | } | ||
169 | if (!scanSuccess) { | ||
170 | if (!actionShown) { | ||
171 | emit logItem(tr("Action required:\n\n" | ||
172 | "Quit iTunes application."), LOGWARNING); | ||
173 | actionShown = true; | ||
174 | } | ||
175 | QTimer::singleShot(250, this, &BootloaderInstallS5l::installStageWaitForProcs); | ||
176 | return; | ||
177 | } | ||
178 | if (actionShown) { | ||
179 | emit logItem(tr("iTunes closed."), LOGINFO); | ||
180 | if (!updateProgress()) | ||
181 | return; /* aborted */ | ||
182 | } | ||
183 | |||
184 | QList<int> helperPids = Utils::findRunningProcess( | ||
185 | #if defined(Q_OS_WIN32) | ||
186 | QStringList("iTunesHelper"))["iTunesHelper.exe"]; | ||
187 | #else | ||
188 | QStringList("iTunesHelper"))["iTunesHelper"]; | ||
189 | #endif | ||
190 | suspendedPids = Utils::suspendProcess(helperPids, true); | ||
191 | if (suspendedPids.size() != helperPids.size()) { | ||
192 | emit logItem(tr("Could not suspend iTunesHelper. Stop it " | ||
193 | "using the Task Manager, and try again."), LOGERROR); | ||
194 | emit done(true); | ||
195 | return; | ||
196 | } | ||
197 | |||
198 | LOG_INFO() << "preparing installStageWaitForSpindown"; | ||
199 | // for Windows: skip waiting if the HDD was ejected a time ago | ||
200 | if (progressTimer.elapsed() < progressTimeout) | ||
201 | emit logItem(tr("Waiting for HDD spin-down..."), LOGINFO); | ||
202 | installStageWaitForSpindown(); | ||
203 | } | ||
204 | |||
205 | |||
206 | void BootloaderInstallS5l::installStageWaitForSpindown(void) | ||
207 | { | ||
208 | if (!updateProgress()) | ||
209 | return; /* aborted */ | ||
210 | |||
211 | if (progressTimer.elapsed() < progressTimeout) { | ||
212 | QTimer::singleShot(250, this, &BootloaderInstallS5l::installStageWaitForSpindown); | ||
213 | return; | ||
214 | } | ||
215 | |||
216 | LOG_INFO() << "preparing installStageWaitForDfu"; | ||
217 | emit logItem(tr("Waiting for DFU mode..."), LOGINFO); | ||
218 | emit logItem(tr("Action required:\n\n" | ||
219 | "Press and hold SELECT+MENU buttons, after " | ||
220 | "about 12 seconds a new action will require " | ||
221 | "you to release the buttons, DO IT QUICKLY, " | ||
222 | "otherwise the process could fail."), LOGWARNING); | ||
223 | scanTimer.invalidate(); | ||
224 | installStageWaitForDfu(); | ||
225 | } | ||
226 | |||
227 | |||
228 | void BootloaderInstallS5l::installStageWaitForDfu(void) | ||
229 | { | ||
230 | if (!updateProgress()) | ||
231 | return; /* aborted */ | ||
232 | |||
233 | if (!scanTimer.isValid() || (scanTimer.elapsed() > 2000)) { | ||
234 | scanSuccess = System::listUsbIds().contains(0x05ac1223); | ||
235 | scanTimer.start(); | ||
236 | } | ||
237 | if (!scanSuccess) { | ||
238 | QTimer::singleShot(250, this, &BootloaderInstallS5l::installStageWaitForDfu); | ||
239 | return; | ||
240 | } | ||
241 | emit logItem(tr("DFU mode detected."), LOGINFO); | ||
242 | |||
243 | emit logItem(tr("Action required:\n\n" | ||
244 | "Release SELECT+MENU buttons and wait..."), LOGWARNING); | ||
245 | |||
246 | // Once the iPod enters DFU mode, the device will reset again if | ||
247 | // SELECT+MENU remains pressed for another 8 seconds. To avoid a | ||
248 | // reset while the NOR is being written, we wait ~10 seconds | ||
249 | // before sending the DFU image. | ||
250 | LOG_INFO() << "preparing installStageSendDfu"; | ||
251 | setProgress(60, 10); | ||
252 | installStageSendDfu(); | ||
253 | } | ||
254 | |||
255 | |||
256 | void BootloaderInstallS5l::installStageSendDfu(void) | ||
257 | { | ||
258 | if (!updateProgress()) | ||
259 | return; /* aborted */ | ||
260 | |||
261 | if (progressTimer.elapsed() < progressTimeout) { | ||
262 | QTimer::singleShot(250, this, &BootloaderInstallS5l::installStageSendDfu); | ||
263 | return; | ||
264 | } | ||
265 | |||
266 | if (!System::listUsbIds().contains(0x05ac1223)) { | ||
267 | LOG_ERROR() << "device not in DFU mode"; | ||
268 | emit logItem(tr("Device is not in DFU mode. It seems that " | ||
269 | "the previous required action failed, please " | ||
270 | "try again."), LOGERROR); | ||
271 | emit done(true); | ||
272 | return; | ||
273 | } | ||
274 | |||
275 | emit logItem(tr("Transfering DFU image..."), LOGINFO); | ||
276 | if (!updateProgress()) | ||
277 | return; /* aborted */ | ||
278 | |||
279 | char errstr[200]; | ||
280 | if (!ipoddfu_send(0x1223, dfu_buf, dfu_size, errstr, sizeof(errstr))) { | ||
281 | LOG_ERROR() << "ipoddfu_send() failed:" << errstr; | ||
282 | #if defined(Q_OS_WIN32) | ||
283 | if (strstr(errstr, "DFU device not found")) | ||
284 | { | ||
285 | emit logItem(tr("No valid DFU USB driver found.\n\n" | ||
286 | "Install iTunes (or the Apple Device Driver) " | ||
287 | "and try again."), | ||
288 | LOGERROR); | ||
289 | } | ||
290 | else | ||
291 | #endif | ||
292 | { | ||
293 | emit logItem(errstr, LOGERROR); | ||
294 | emit logItem(tr("Could not transfer DFU image."), LOGERROR); | ||
295 | } | ||
296 | emit done(true); | ||
297 | return; | ||
298 | } | ||
299 | emit logItem(tr("DFU transfer completed."), LOGINFO); | ||
300 | |||
301 | LOG_INFO() << "preparing installStageWaitForRemount"; | ||
302 | emit logItem(tr("Restarting iPod, waiting for remount..."), LOGINFO); | ||
303 | setProgress(99, 45); | ||
304 | scanTimer.invalidate(); | ||
305 | installStageWaitForRemount(); | ||
306 | } | ||
307 | |||
308 | |||
309 | void BootloaderInstallS5l::installStageWaitForRemount(void) | ||
310 | { | ||
311 | if (!updateProgress()) | ||
312 | return; /* aborted */ | ||
313 | |||
314 | if (!scanTimer.isValid() || (scanTimer.elapsed() > 5000)) { | ||
315 | scanSuccess = Utils::mountpoints( | ||
316 | Utils::MountpointsSupported).contains(mntpoint); | ||
317 | scanTimer.start(); | ||
318 | } | ||
319 | if (!scanSuccess) { | ||
320 | if (!actionShown && (progressTimer.elapsed() > progressTimeout)) { | ||
321 | emit logItem(tr("Action required:\n\n" | ||
322 | "Could not remount the device, try to do it " | ||
323 | "manually. If the iPod didn't restart, force " | ||
324 | "a reset by pressing SELECT+MENU buttons " | ||
325 | "for about 5 seconds. If the problem could " | ||
326 | "not be solved then click 'Abort' to cancel."), | ||
327 | LOGWARNING); | ||
328 | actionShown = true; | ||
329 | } | ||
330 | QTimer::singleShot(250, this, &BootloaderInstallS5l::installStageWaitForRemount); | ||
331 | return; | ||
332 | } | ||
333 | emit logItem(tr("Device remounted."), LOGINFO); | ||
334 | |||
335 | if (doInstall) | ||
336 | emit logItem(tr("Bootloader successfully installed."), LOGOK); | ||
337 | else | ||
338 | emit logItem(tr("Bootloader successfully uninstalled."), LOGOK); | ||
339 | |||
340 | logInstall(doInstall ? LogAdd : LogRemove); | ||
341 | emit logProgress(1, 1); | ||
342 | emit done(false); | ||
343 | } | ||
344 | |||
345 | |||
346 | void BootloaderInstallS5l::installDone(bool status) | ||
347 | { | ||
348 | LOG_INFO() << "installDone, status:" << status; | ||
349 | if (Utils::suspendProcess(suspendedPids, false).size() != suspendedPids.size()) | ||
350 | emit logItem(tr("Could not resume iTunesHelper."), LOGWARNING); | ||
351 | } | ||
352 | |||
353 | |||
354 | void BootloaderInstallS5l::abortInstall(void) | ||
355 | { | ||
356 | LOG_INFO() << "abortInstall"; | ||
357 | aborted = true; | ||
358 | disconnect(this, &BootloaderInstallBase::installAborted, | ||
359 | this, &BootloaderInstallS5l::abortInstall); | ||
360 | } | ||
361 | |||
362 | |||
363 | bool BootloaderInstallS5l::abortDetected(void) | ||
364 | { | ||
365 | if (aborted) { | ||
366 | LOG_ERROR() << "abortDetected"; | ||
367 | if (doInstall) | ||
368 | emit logItem(tr("Install aborted by user."), LOGERROR); | ||
369 | else | ||
370 | emit logItem(tr("Uninstall aborted by user."), LOGERROR); | ||
371 | emit done(true); | ||
372 | return true; | ||
373 | } | ||
374 | return false; | ||
375 | } | ||
376 | |||
377 | |||
378 | void BootloaderInstallS5l::setProgress(int progress, int secondsTimeout) | ||
379 | { | ||
380 | progressTimer.start(); | ||
381 | progressTimeout = secondsTimeout * 1000; | ||
382 | progOrigin = progTarget; | ||
383 | progTarget = progress; | ||
384 | actionShown = false; | ||
385 | } | ||
386 | |||
387 | |||
388 | bool BootloaderInstallS5l::updateProgress(void) | ||
389 | { | ||
390 | if (progressTimeout) { | ||
391 | progCurrent = qMin(progTarget, progOrigin + | ||
392 | static_cast<int>(progressTimer.elapsed()) | ||
393 | * (progTarget - progOrigin) / progressTimeout); | ||
394 | } | ||
395 | else { | ||
396 | progCurrent = progTarget; | ||
397 | } | ||
398 | emit logProgress(progCurrent, 100); | ||
399 | QCoreApplication::sendPostedEvents(); | ||
400 | QCoreApplication::processEvents(); | ||
401 | return !abortDetected(); | ||
402 | } | ||
403 | |||
404 | |||
405 | BootloaderInstallBase::BootloaderType BootloaderInstallS5l::installed(void) | ||
406 | { | ||
407 | bool rbblInstalled; | ||
408 | |||
409 | QString device = Utils::resolveDevicename(m_blfile); | ||
410 | if (device.isEmpty()) { | ||
411 | LOG_INFO() << "installed: BootloaderUnknown"; | ||
412 | return BootloaderUnknown; | ||
413 | } | ||
414 | |||
415 | // rely on logfile | ||
416 | QString logfile = RbSettings::value(RbSettings::Mountpoint).toString() | ||
417 | + "/.rockbox/rbutil.log"; | ||
418 | QSettings s(logfile, QSettings::IniFormat, this); | ||
419 | QString section = PlayerBuildInfo::instance()->value( | ||
420 | PlayerBuildInfo::BootloaderName).toString().section('/', -1); | ||
421 | rbblInstalled = s.contains("Bootloader/" + section); | ||
422 | |||
423 | if (rbblInstalled) { | ||
424 | LOG_INFO() << "installed: BootloaderRockbox"; | ||
425 | return BootloaderRockbox; | ||
426 | } | ||
427 | else { | ||
428 | LOG_INFO() << "installed: BootloaderOther"; | ||
429 | return BootloaderOther; | ||
430 | } | ||
431 | } | ||
432 | |||
433 | |||
434 | BootloaderInstallBase::Capabilities BootloaderInstallS5l::capabilities(void) | ||
435 | { | ||
436 | return (Install | Uninstall); | ||
437 | } | ||
diff --git a/utils/rbutilqt/base/bootloaderinstalls5l.h b/utils/rbutilqt/base/bootloaderinstalls5l.h new file mode 100644 index 0000000000..e2a44a031f --- /dev/null +++ b/utils/rbutilqt/base/bootloaderinstalls5l.h | |||
@@ -0,0 +1,71 @@ | |||
1 | /*************************************************************************** | ||
2 | * __________ __ ___. | ||
3 | * Open \______ \ ____ ____ | | _\_ |__ _______ ___ | ||
4 | * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / | ||
5 | * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < | ||
6 | * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ | ||
7 | * \/ \/ \/ \/ \/ | ||
8 | * | ||
9 | * Copyright (C) 2008 by Dominik Riebeling | ||
10 | * | ||
11 | * All files in this archive are subject to the GNU General Public License. | ||
12 | * See the file COPYING in the source tree root for full license agreement. | ||
13 | * | ||
14 | * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY | ||
15 | * KIND, either express or implied. | ||
16 | * | ||
17 | ****************************************************************************/ | ||
18 | |||
19 | #ifndef BOOTLOADERINSTALLS5L_H | ||
20 | #define BOOTLOADERINSTALLS5L_H | ||
21 | |||
22 | #include <QtCore> | ||
23 | #include "bootloaderinstallbase.h" | ||
24 | |||
25 | |||
26 | //! bootloader installation derivate based on mks5lboot | ||
27 | class BootloaderInstallS5l : public BootloaderInstallBase | ||
28 | { | ||
29 | Q_OBJECT | ||
30 | |||
31 | public: | ||
32 | BootloaderInstallS5l(QObject *parent); | ||
33 | bool install(void); | ||
34 | bool uninstall(void); | ||
35 | BootloaderInstallBase::BootloaderType installed(void); | ||
36 | Capabilities capabilities(void); | ||
37 | |||
38 | private slots: | ||
39 | bool installStage1(void); | ||
40 | void installStageMkdfu(void); | ||
41 | void installStageWaitForEject(void); | ||
42 | void installStageWaitForSpindown(void); | ||
43 | void installStageWaitForProcs(void); | ||
44 | void installStageWaitForDfu(void); | ||
45 | void installStageSendDfu(void); | ||
46 | void installStageWaitForRemount(void); | ||
47 | void abortInstall(void); | ||
48 | void installDone(bool); | ||
49 | |||
50 | private: | ||
51 | bool doInstall; | ||
52 | QString mntpoint; | ||
53 | unsigned char* dfu_buf; | ||
54 | int dfu_size; | ||
55 | QList<int> suspendedPids; | ||
56 | bool aborted; | ||
57 | bool abortDetected(void); | ||
58 | QElapsedTimer scanTimer; | ||
59 | bool scanSuccess; | ||
60 | // progress | ||
61 | QElapsedTimer progressTimer; | ||
62 | int progressTimeout; | ||
63 | int progCurrent; | ||
64 | int progOrigin; | ||
65 | int progTarget; | ||
66 | bool actionShown; | ||
67 | void setProgress(int, int=0); | ||
68 | bool updateProgress(void); | ||
69 | }; | ||
70 | |||
71 | #endif | ||
diff --git a/utils/rbutilqt/base/bootloaderinstallsansa.cpp b/utils/rbutilqt/base/bootloaderinstallsansa.cpp new file mode 100644 index 0000000000..b1f0167e42 --- /dev/null +++ b/utils/rbutilqt/base/bootloaderinstallsansa.cpp | |||
@@ -0,0 +1,286 @@ | |||
1 | /*************************************************************************** | ||
2 | * __________ __ ___. | ||
3 | * Open \______ \ ____ ____ | | _\_ |__ _______ ___ | ||
4 | * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / | ||
5 | * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < | ||
6 | * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ | ||
7 | * \/ \/ \/ \/ \/ | ||
8 | * | ||
9 | * Copyright (C) 2008 by Dominik Riebeling | ||
10 | * | ||
11 | * All files in this archive are subject to the GNU General Public License. | ||
12 | * See the file COPYING in the source tree root for full license agreement. | ||
13 | * | ||
14 | * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY | ||
15 | * KIND, either express or implied. | ||
16 | * | ||
17 | ****************************************************************************/ | ||
18 | |||
19 | #include <QtCore> | ||
20 | #include "bootloaderinstallbase.h" | ||
21 | #include "bootloaderinstallsansa.h" | ||
22 | #include "Logger.h" | ||
23 | |||
24 | #include "../sansapatcher/sansapatcher.h" | ||
25 | #include "utils.h" | ||
26 | |||
27 | BootloaderInstallSansa::BootloaderInstallSansa(QObject *parent) | ||
28 | : BootloaderInstallBase(parent) | ||
29 | { | ||
30 | (void)parent; | ||
31 | // initialize sector buffer. The sector buffer is part of the sansa_t | ||
32 | // structure, so a second instance of this class will have its own buffer. | ||
33 | sansa_alloc_buffer(&sansa, BUFFER_SIZE); | ||
34 | } | ||
35 | |||
36 | |||
37 | BootloaderInstallSansa::~BootloaderInstallSansa() | ||
38 | { | ||
39 | if(sansa.sectorbuf) { | ||
40 | sansa_dealloc_buffer(&sansa); | ||
41 | } | ||
42 | } | ||
43 | |||
44 | |||
45 | /** Start bootloader installation. | ||
46 | */ | ||
47 | bool BootloaderInstallSansa::install(void) | ||
48 | { | ||
49 | if(sansa.sectorbuf == nullptr) { | ||
50 | emit logItem(tr("Error: can't allocate buffer memory!"), LOGERROR); | ||
51 | return false; | ||
52 | emit done(true); | ||
53 | } | ||
54 | |||
55 | emit logItem(tr("Searching for Sansa"), LOGINFO); | ||
56 | |||
57 | int n = sansa_scan(&sansa); | ||
58 | if(n == -1) { | ||
59 | emit logItem(tr("Permission for disc access denied!\n" | ||
60 | "This is required to install the bootloader"), | ||
61 | LOGERROR); | ||
62 | emit done(true); | ||
63 | return false; | ||
64 | } | ||
65 | if(n == 0) { | ||
66 | emit logItem(tr("No Sansa detected!"), LOGERROR); | ||
67 | emit done(true); | ||
68 | return false; | ||
69 | } | ||
70 | if(sansa.hasoldbootloader) { | ||
71 | emit logItem(tr("OLD ROCKBOX INSTALLATION DETECTED, ABORTING.\n" | ||
72 | "You must reinstall the original Sansa firmware before running\n" | ||
73 | "sansapatcher for the first time.\n" | ||
74 | "See http://www.rockbox.org/wiki/SansaE200Install\n"), | ||
75 | LOGERROR); | ||
76 | emit done(true); | ||
77 | return false; | ||
78 | } | ||
79 | emit logItem(tr("Downloading bootloader file"), LOGINFO); | ||
80 | |||
81 | downloadBlStart(m_blurl); | ||
82 | connect(this, &BootloaderInstallBase::downloadDone, this, &BootloaderInstallSansa::installStage2); | ||
83 | return true; | ||
84 | } | ||
85 | |||
86 | |||
87 | /** Finish bootloader installation. | ||
88 | */ | ||
89 | void BootloaderInstallSansa::installStage2(void) | ||
90 | { | ||
91 | unsigned char* buf = nullptr; | ||
92 | unsigned int len; | ||
93 | |||
94 | emit logItem(tr("Installing Rockbox bootloader"), LOGINFO); | ||
95 | QCoreApplication::processEvents(); | ||
96 | if(!sansaInitialize(&sansa)) { | ||
97 | emit done(true); | ||
98 | return; | ||
99 | } | ||
100 | |||
101 | if(sansa_reopen_rw(&sansa) < 0) { | ||
102 | emit logItem(tr("Could not open Sansa in R/W mode"), LOGERROR); | ||
103 | emit done(true); | ||
104 | return; | ||
105 | } | ||
106 | |||
107 | // check model -- if sansapatcher reports a c200 don't install an e200 | ||
108 | // bootloader and vice versa. | ||
109 | // The model is available in the mi4 file at offset 0x1fc and matches | ||
110 | // the targetname set by sansapatcher. | ||
111 | emit logItem(tr("Checking downloaded bootloader"), LOGINFO); | ||
112 | m_tempfile.open(); | ||
113 | QString blfile = m_tempfile.fileName(); | ||
114 | char magic[4]; | ||
115 | m_tempfile.seek(0x1fc); | ||
116 | m_tempfile.read(magic, 4); | ||
117 | m_tempfile.close(); | ||
118 | if(memcmp(sansa.targetname, magic, 4) != 0) { | ||
119 | emit logItem(tr("Bootloader mismatch! Aborting."), LOGERROR); | ||
120 | LOG_INFO("Targetname: %s, mi4 magic: %c%c%c%c", | ||
121 | sansa.targetname, magic[0], magic[1], magic[2], magic[3]); | ||
122 | emit done(true); | ||
123 | sansa_close(&sansa); | ||
124 | return; | ||
125 | } | ||
126 | |||
127 | len = sansa_read_bootloader(&sansa, blfile.toLatin1().data(), &buf); | ||
128 | if(sansa_add_bootloader(&sansa, buf, len) == 0) { | ||
129 | emit logItem(tr("Successfully installed bootloader"), LOGOK); | ||
130 | sansa_close(&sansa); | ||
131 | #if defined(Q_OS_MACX) | ||
132 | m_remountDevice = sansa.diskname; | ||
133 | connect(this, SIGNAL(remounted(bool)), this, SLOT(installStage3(bool))); | ||
134 | waitRemount(); | ||
135 | #else | ||
136 | installStage3(true); | ||
137 | #endif | ||
138 | } | ||
139 | else { | ||
140 | emit logItem(tr("Failed to install bootloader"), LOGERROR); | ||
141 | sansa_close(&sansa); | ||
142 | emit done(true); | ||
143 | return; | ||
144 | } | ||
145 | |||
146 | } | ||
147 | |||
148 | |||
149 | void BootloaderInstallSansa::installStage3(bool mounted) | ||
150 | { | ||
151 | if(mounted) { | ||
152 | logInstall(LogAdd); | ||
153 | emit logItem(tr("Bootloader Installation complete."), LOGINFO); | ||
154 | emit done(false); | ||
155 | return; | ||
156 | } | ||
157 | else { | ||
158 | emit logItem(tr("Writing log aborted"), LOGERROR); | ||
159 | emit done(true); | ||
160 | } | ||
161 | LOG_INFO() << "version installed:" | ||
162 | << m_blversion.toString(Qt::ISODate); | ||
163 | } | ||
164 | |||
165 | |||
166 | /** Uninstall the bootloader. | ||
167 | */ | ||
168 | bool BootloaderInstallSansa::uninstall(void) | ||
169 | { | ||
170 | emit logItem(tr("Uninstalling bootloader"), LOGINFO); | ||
171 | QCoreApplication::processEvents(); | ||
172 | |||
173 | if(!sansaInitialize(&sansa)) { | ||
174 | emit done(true); | ||
175 | return false; | ||
176 | } | ||
177 | |||
178 | if (sansa.hasoldbootloader) { | ||
179 | emit logItem(tr("OLD ROCKBOX INSTALLATION DETECTED, ABORTING.\n" | ||
180 | "You must reinstall the original Sansa firmware before running\n" | ||
181 | "sansapatcher for the first time.\n" | ||
182 | "See http://www.rockbox.org/wiki/SansaE200Install\n"), | ||
183 | LOGERROR); | ||
184 | emit done(true); | ||
185 | return false; | ||
186 | } | ||
187 | |||
188 | if (sansa_reopen_rw(&sansa) < 0) { | ||
189 | emit logItem(tr("Could not open Sansa in R/W mode"), LOGERROR); | ||
190 | emit done(true); | ||
191 | return false; | ||
192 | } | ||
193 | |||
194 | if (sansa_delete_bootloader(&sansa)==0) { | ||
195 | emit logItem(tr("Successfully removed bootloader"), LOGOK); | ||
196 | logInstall(LogRemove); | ||
197 | emit logProgress(1, 1); | ||
198 | emit done(false); | ||
199 | sansa_close(&sansa); | ||
200 | return true; | ||
201 | } | ||
202 | else { | ||
203 | emit logItem(tr("Removing bootloader failed."),LOGERROR); | ||
204 | emit done(true); | ||
205 | sansa_close(&sansa); | ||
206 | return false; | ||
207 | } | ||
208 | |||
209 | return false; | ||
210 | } | ||
211 | |||
212 | |||
213 | /** Check if bootloader is already installed | ||
214 | */ | ||
215 | BootloaderInstallBase::BootloaderType BootloaderInstallSansa::installed(void) | ||
216 | { | ||
217 | int num; | ||
218 | |||
219 | if(!sansaInitialize(&sansa)) { | ||
220 | return BootloaderUnknown; | ||
221 | } | ||
222 | if((num = sansa_list_images(&sansa)) == 2) { | ||
223 | sansa_close(&sansa); | ||
224 | return BootloaderRockbox; | ||
225 | } | ||
226 | else if(num == 1) { | ||
227 | sansa_close(&sansa); | ||
228 | return BootloaderOther; | ||
229 | } | ||
230 | return BootloaderUnknown; | ||
231 | |||
232 | } | ||
233 | |||
234 | bool BootloaderInstallSansa::sansaInitialize(struct sansa_t *sansa) | ||
235 | { | ||
236 | if(!m_blfile.isEmpty()) { | ||
237 | QString devicename = Utils::resolveDevicename(m_blfile); | ||
238 | if(devicename.isEmpty()) { | ||
239 | emit logItem(tr("Error: could not retrieve device name"), LOGERROR); | ||
240 | return false; | ||
241 | } | ||
242 | #if defined(Q_OS_WIN32) | ||
243 | sprintf(sansa->diskname, "\\\\.\\PhysicalDrive%i", devicename.toInt()); | ||
244 | #elif defined(Q_OS_MACX) | ||
245 | sprintf(sansa->diskname, | ||
246 | "%s", qPrintable(devicename.remove(QRegExp("s[0-9]+$")))); | ||
247 | #else | ||
248 | sprintf(sansa->diskname, | ||
249 | "%s", qPrintable(devicename.remove(QRegExp("[0-9]+$")))); | ||
250 | #endif | ||
251 | LOG_INFO() << "sansapatcher: overriding scan, using" | ||
252 | << sansa->diskname; | ||
253 | } | ||
254 | else if(sansa_scan(sansa) != 1) { | ||
255 | emit logItem(tr("Can't find Sansa"), LOGERROR); | ||
256 | return false; | ||
257 | } | ||
258 | |||
259 | if (sansa_open(sansa, 0) < 0) { | ||
260 | emit logItem(tr("Could not open Sansa"), LOGERROR); | ||
261 | return false; | ||
262 | } | ||
263 | |||
264 | if (sansa_read_partinfo(sansa,0) < 0) { | ||
265 | emit logItem(tr("Could not read partition table"), LOGERROR); | ||
266 | sansa_close(sansa); | ||
267 | return false; | ||
268 | } | ||
269 | |||
270 | int i = is_sansa(sansa); | ||
271 | if(i < 0) { | ||
272 | emit logItem(tr("Disk is not a Sansa (Error %1), aborting.").arg(i), LOGERROR); | ||
273 | sansa_close(sansa); | ||
274 | return false; | ||
275 | } | ||
276 | return true; | ||
277 | } | ||
278 | |||
279 | |||
280 | /** Get capabilities of subclass installer. | ||
281 | */ | ||
282 | BootloaderInstallBase::Capabilities BootloaderInstallSansa::capabilities(void) | ||
283 | { | ||
284 | return (Install | Uninstall | IsRaw | CanCheckInstalled); | ||
285 | } | ||
286 | |||
diff --git a/utils/rbutilqt/base/bootloaderinstallsansa.h b/utils/rbutilqt/base/bootloaderinstallsansa.h new file mode 100644 index 0000000000..45837f0ac3 --- /dev/null +++ b/utils/rbutilqt/base/bootloaderinstallsansa.h | |||
@@ -0,0 +1,51 @@ | |||
1 | /*************************************************************************** | ||
2 | * __________ __ ___. | ||
3 | * Open \______ \ ____ ____ | | _\_ |__ _______ ___ | ||
4 | * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / | ||
5 | * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < | ||
6 | * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ | ||
7 | * \/ \/ \/ \/ \/ | ||
8 | * | ||
9 | * Copyright (C) 2008 by Dominik Riebeling | ||
10 | * | ||
11 | * All files in this archive are subject to the GNU General Public License. | ||
12 | * See the file COPYING in the source tree root for full license agreement. | ||
13 | * | ||
14 | * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY | ||
15 | * KIND, either express or implied. | ||
16 | * | ||
17 | ****************************************************************************/ | ||
18 | |||
19 | #ifndef BOOTLOADERINSTALLSANSA_H | ||
20 | #define BOOTLOADERINSTALLSANSA_H | ||
21 | |||
22 | #include <QtCore> | ||
23 | #include "bootloaderinstallbase.h" | ||
24 | #include "sansapatcher.h" | ||
25 | |||
26 | |||
27 | // bootloader installation class for devices handled by sansapatcher. | ||
28 | class BootloaderInstallSansa : public BootloaderInstallBase | ||
29 | { | ||
30 | Q_OBJECT | ||
31 | |||
32 | public: | ||
33 | BootloaderInstallSansa(QObject *parent = nullptr); | ||
34 | ~BootloaderInstallSansa(); | ||
35 | bool install(void); | ||
36 | bool uninstall(void); | ||
37 | BootloaderInstallBase::BootloaderType installed(void); | ||
38 | Capabilities capabilities(void); | ||
39 | |||
40 | private: | ||
41 | bool sansaInitialize(struct sansa_t *); | ||
42 | struct sansa_t sansa; | ||
43 | |||
44 | private slots: | ||
45 | void installStage2(void); | ||
46 | void installStage3(bool); | ||
47 | }; | ||
48 | |||
49 | |||
50 | #endif | ||
51 | |||
diff --git a/utils/rbutilqt/base/bootloaderinstalltcc.cpp b/utils/rbutilqt/base/bootloaderinstalltcc.cpp new file mode 100644 index 0000000000..ffc8555733 --- /dev/null +++ b/utils/rbutilqt/base/bootloaderinstalltcc.cpp | |||
@@ -0,0 +1,165 @@ | |||
1 | /*************************************************************************** | ||
2 | * __________ __ ___. | ||
3 | * Open \______ \ ____ ____ | | _\_ |__ _______ ___ | ||
4 | * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / | ||
5 | * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < | ||
6 | * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ | ||
7 | * \/ \/ \/ \/ \/ | ||
8 | * | ||
9 | * Copyright (C) 2009 by Tomer Shalev | ||
10 | * | ||
11 | * All files in this archive are subject to the GNU General Public License. | ||
12 | * See the file COPYING in the source tree root for full license agreement. | ||
13 | * | ||
14 | * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY | ||
15 | * KIND, either express or implied. | ||
16 | * | ||
17 | * This file is a modified version of the AMS installer by Dominik Wenger | ||
18 | * | ||
19 | ****************************************************************************/ | ||
20 | |||
21 | #include <QtCore> | ||
22 | #include "bootloaderinstallbase.h" | ||
23 | #include "bootloaderinstalltcc.h" | ||
24 | #include "../mktccboot/mktccboot.h" | ||
25 | |||
26 | BootloaderInstallTcc::BootloaderInstallTcc(QObject *parent) | ||
27 | : BootloaderInstallBase(parent) | ||
28 | { | ||
29 | } | ||
30 | |||
31 | QString BootloaderInstallTcc::ofHint() | ||
32 | { | ||
33 | return tr("Bootloader installation requires you to provide " | ||
34 | "a firmware file of the original firmware (bin file). " | ||
35 | "You need to download this file yourself due to legal " | ||
36 | "reasons. Please refer to the " | ||
37 | "<a href='http://www.rockbox.org/manual.shtml'>manual</a> and the " | ||
38 | "<a href='http://www.rockbox.org/wiki/CowonD2Info'>CowonD2Info</a> " | ||
39 | "wiki page on how to obtain the file.<br/>" | ||
40 | "Press Ok to continue and browse your computer for the firmware " | ||
41 | "file."); | ||
42 | } | ||
43 | |||
44 | bool BootloaderInstallTcc::install(void) | ||
45 | { | ||
46 | if(m_offile.isEmpty()) | ||
47 | return false; | ||
48 | |||
49 | // Download firmware from server | ||
50 | emit logItem(tr("Downloading bootloader file"), LOGINFO); | ||
51 | |||
52 | connect(this, &BootloaderInstallBase::downloadDone, this, &BootloaderInstallTcc::installStage2); | ||
53 | downloadBlStart(m_blurl); | ||
54 | |||
55 | return true; | ||
56 | } | ||
57 | |||
58 | void BootloaderInstallTcc::installStage2(void) | ||
59 | { | ||
60 | unsigned char *of_buf, *boot_buf = nullptr, *patched_buf = nullptr; | ||
61 | int n, of_size, boot_size, patched_size; | ||
62 | char errstr[200]; | ||
63 | bool ret = false; | ||
64 | |||
65 | m_tempfile.open(); | ||
66 | QString bootfile = m_tempfile.fileName(); | ||
67 | m_tempfile.close(); | ||
68 | |||
69 | /* Construct path for write out. | ||
70 | * Combine path of m_blfile with filename from OF */ | ||
71 | QString outfilename = QFileInfo(m_blfile).absolutePath() + "/" + | ||
72 | QFileInfo(m_offile).fileName(); | ||
73 | |||
74 | /* Write out file */ | ||
75 | QFile out(outfilename); | ||
76 | |||
77 | /* Load original firmware file */ | ||
78 | of_buf = file_read(m_offile.toLocal8Bit().data(), &of_size); | ||
79 | if (of_buf == nullptr) | ||
80 | { | ||
81 | emit logItem(errstr, LOGERROR); | ||
82 | emit logItem(tr("Could not load %1").arg(m_offile), LOGERROR); | ||
83 | goto exit; | ||
84 | } | ||
85 | |||
86 | /* A CRC test in order to reject non OF file */ | ||
87 | if (test_firmware_tcc(of_buf, of_size)) | ||
88 | { | ||
89 | emit logItem(errstr, LOGERROR); | ||
90 | emit logItem(tr("Unknown OF file used: %1").arg(m_offile), LOGERROR); | ||
91 | goto exit; | ||
92 | } | ||
93 | |||
94 | /* Load bootloader file */ | ||
95 | boot_buf = file_read(bootfile.toLocal8Bit().data(), &boot_size); | ||
96 | if (boot_buf == nullptr) | ||
97 | { | ||
98 | emit logItem(errstr, LOGERROR); | ||
99 | emit logItem(tr("Could not load %1").arg(bootfile), LOGERROR); | ||
100 | goto exit; | ||
101 | } | ||
102 | |||
103 | /* Patch the firmware */ | ||
104 | emit logItem(tr("Patching Firmware..."), LOGINFO); | ||
105 | |||
106 | patched_buf = patch_firmware_tcc(of_buf, of_size, boot_buf, boot_size, | ||
107 | &patched_size); | ||
108 | if (patched_buf == nullptr) | ||
109 | { | ||
110 | emit logItem(errstr, LOGERROR); | ||
111 | emit logItem(tr("Could not patch firmware"), LOGERROR); | ||
112 | goto exit; | ||
113 | } | ||
114 | |||
115 | if(!out.open(QIODevice::WriteOnly | QIODevice::Truncate)) | ||
116 | { | ||
117 | emit logItem(tr("Could not open %1 for writing").arg(m_blfile), | ||
118 | LOGERROR); | ||
119 | goto exit; | ||
120 | } | ||
121 | |||
122 | n = out.write((char*)patched_buf, patched_size); | ||
123 | out.close(); | ||
124 | if (n != patched_size) | ||
125 | { | ||
126 | emit logItem(tr("Could not write firmware file"), LOGERROR); | ||
127 | goto exit; | ||
128 | } | ||
129 | |||
130 | /* End of install */ | ||
131 | emit logItem(tr("Success: modified firmware file created"), LOGINFO); | ||
132 | logInstall(LogAdd); | ||
133 | |||
134 | ret = true; | ||
135 | |||
136 | exit: | ||
137 | if (of_buf) | ||
138 | free(of_buf); | ||
139 | |||
140 | if (boot_buf) | ||
141 | free(boot_buf); | ||
142 | |||
143 | if (patched_buf) | ||
144 | free(patched_buf); | ||
145 | |||
146 | emit done(ret); | ||
147 | } | ||
148 | |||
149 | bool BootloaderInstallTcc::uninstall(void) | ||
150 | { | ||
151 | emit logItem(tr("To uninstall, perform a normal upgrade with an unmodified original firmware"), LOGINFO); | ||
152 | logInstall(LogRemove); | ||
153 | emit done(true); | ||
154 | return false; | ||
155 | } | ||
156 | |||
157 | BootloaderInstallBase::BootloaderType BootloaderInstallTcc::installed(void) | ||
158 | { | ||
159 | return BootloaderUnknown; | ||
160 | } | ||
161 | |||
162 | BootloaderInstallBase::Capabilities BootloaderInstallTcc::capabilities(void) | ||
163 | { | ||
164 | return (Install | NeedsOf); | ||
165 | } | ||
diff --git a/utils/rbutilqt/base/bootloaderinstalltcc.h b/utils/rbutilqt/base/bootloaderinstalltcc.h new file mode 100644 index 0000000000..cc1fd45802 --- /dev/null +++ b/utils/rbutilqt/base/bootloaderinstalltcc.h | |||
@@ -0,0 +1,44 @@ | |||
1 | /*************************************************************************** | ||
2 | * __________ __ ___. | ||
3 | * Open \______ \ ____ ____ | | _\_ |__ _______ ___ | ||
4 | * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / | ||
5 | * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < | ||
6 | * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ | ||
7 | * \/ \/ \/ \/ \/ | ||
8 | * | ||
9 | * Copyright (C) 2009 by Tomer Shalev | ||
10 | * | ||
11 | * All files in this archive are subject to the GNU General Public License. | ||
12 | * See the file COPYING in the source tree root for full license agreement. | ||
13 | * | ||
14 | * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY | ||
15 | * KIND, either express or implied. | ||
16 | * | ||
17 | * This file is a modified version of the AMS installer by Dominik Wenger | ||
18 | * | ||
19 | ****************************************************************************/ | ||
20 | #ifndef BOOTLOADERINSTALLTCC_H | ||
21 | #define BOOTLOADERINSTALLTCC_H | ||
22 | |||
23 | #include <QtCore> | ||
24 | #include "bootloaderinstallbase.h" | ||
25 | |||
26 | //! bootloader installation derivate based on mktccboot | ||
27 | class BootloaderInstallTcc : public BootloaderInstallBase | ||
28 | { | ||
29 | Q_OBJECT | ||
30 | public: | ||
31 | BootloaderInstallTcc(QObject *parent); | ||
32 | bool install(void); | ||
33 | bool uninstall(void); | ||
34 | BootloaderInstallBase::BootloaderType installed(void); | ||
35 | Capabilities capabilities(void); | ||
36 | QString ofHint(); | ||
37 | |||
38 | private: | ||
39 | |||
40 | private slots: | ||
41 | void installStage2(void); | ||
42 | }; | ||
43 | |||
44 | #endif | ||
diff --git a/utils/rbutilqt/base/encoderbase.cpp b/utils/rbutilqt/base/encoderbase.cpp new file mode 100644 index 0000000000..fe45eee49b --- /dev/null +++ b/utils/rbutilqt/base/encoderbase.cpp | |||
@@ -0,0 +1,86 @@ | |||
1 | /*************************************************************************** | ||
2 | * __________ __ ___. | ||
3 | * Open \______ \ ____ ____ | | _\_ |__ _______ ___ | ||
4 | * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / | ||
5 | * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < | ||
6 | * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ | ||
7 | * \/ \/ \/ \/ \/ | ||
8 | * | ||
9 | * Copyright (C) 2007 by Dominik Wenger | ||
10 | * | ||
11 | * All files in this archive are subject to the GNU General Public License. | ||
12 | * See the file COPYING in the source tree root for full license agreement. | ||
13 | * | ||
14 | * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY | ||
15 | * KIND, either express or implied. | ||
16 | * | ||
17 | ****************************************************************************/ | ||
18 | |||
19 | #include "encoderbase.h" | ||
20 | #include "utils.h" | ||
21 | #include "rbsettings.h" | ||
22 | #include "encoderrbspeex.h" | ||
23 | #include "encoderlame.h" | ||
24 | #include "encoderexe.h" | ||
25 | |||
26 | #include "Logger.h" | ||
27 | |||
28 | /********************************************************************* | ||
29 | * Encoder Base | ||
30 | **********************************************************************/ | ||
31 | QMap<QString,QString> EncoderBase::encoderList; | ||
32 | |||
33 | EncoderBase::EncoderBase(QObject *parent): EncTtsSettingInterface(parent) | ||
34 | { | ||
35 | |||
36 | } | ||
37 | |||
38 | // initialize list of encoders | ||
39 | void EncoderBase::initEncodernamesList() | ||
40 | { | ||
41 | encoderList["rbspeex"] = "Rockbox Speex Encoder"; | ||
42 | encoderList["lame"] = "Lame Mp3 Encoder"; | ||
43 | } | ||
44 | |||
45 | |||
46 | // get nice name for a specific encoder | ||
47 | QString EncoderBase::getEncoderName(QString encoder) | ||
48 | { | ||
49 | if(encoderList.isEmpty()) | ||
50 | initEncodernamesList(); | ||
51 | return encoderList.value(encoder); | ||
52 | } | ||
53 | |||
54 | |||
55 | // get a specific encoder object | ||
56 | EncoderBase* EncoderBase::getEncoder(QObject* parent,QString encoder) | ||
57 | { | ||
58 | EncoderBase* enc; | ||
59 | if(encoder == "lame") | ||
60 | { | ||
61 | enc = new EncoderLame(parent); | ||
62 | if (!enc->configOk()) | ||
63 | { | ||
64 | LOG_WARNING() << "Could not load lame dll, falling back to command " | ||
65 | "line lame. This is notably slower."; | ||
66 | delete enc; | ||
67 | enc = new EncoderExe(encoder, parent); | ||
68 | |||
69 | } | ||
70 | return enc; | ||
71 | } | ||
72 | else // rbspeex is default | ||
73 | { | ||
74 | enc = new EncoderRbSpeex(parent); | ||
75 | return enc; | ||
76 | } | ||
77 | } | ||
78 | |||
79 | |||
80 | QStringList EncoderBase::getEncoderList() | ||
81 | { | ||
82 | if(encoderList.isEmpty()) | ||
83 | initEncodernamesList(); | ||
84 | return encoderList.keys(); | ||
85 | } | ||
86 | |||
diff --git a/utils/rbutilqt/base/encoderbase.h b/utils/rbutilqt/base/encoderbase.h new file mode 100644 index 0000000000..810b38aa9e --- /dev/null +++ b/utils/rbutilqt/base/encoderbase.h | |||
@@ -0,0 +1,63 @@ | |||
1 | /*************************************************************************** | ||
2 | * __________ __ ___. | ||
3 | * Open \______ \ ____ ____ | | _\_ |__ _______ ___ | ||
4 | * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / | ||
5 | * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < | ||
6 | * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ | ||
7 | * \/ \/ \/ \/ \/ | ||
8 | * | ||
9 | * Copyright (C) 2007 by Dominik Wenger | ||
10 | * | ||
11 | * This program is free software; you can redistribute it and/or | ||
12 | * modify it under the terms of the GNU General Public License | ||
13 | * as published by the Free Software Foundation; either version 2 | ||
14 | * of the License, or (at your option) any later version. | ||
15 | * | ||
16 | * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY | ||
17 | * KIND, either express or implied. | ||
18 | * | ||
19 | ****************************************************************************/ | ||
20 | |||
21 | #ifndef ENCODERS_H | ||
22 | #define ENCODERS_H | ||
23 | |||
24 | #include <QtCore> | ||
25 | |||
26 | #include "encttssettings.h" | ||
27 | |||
28 | |||
29 | class EncoderBase : public EncTtsSettingInterface | ||
30 | { | ||
31 | Q_OBJECT | ||
32 | public: | ||
33 | EncoderBase(QObject *parent ); | ||
34 | |||
35 | //! Child class should encode a wav file | ||
36 | virtual bool encode(QString input,QString output) =0; | ||
37 | //! Child class should do startup | ||
38 | virtual bool start()=0; | ||
39 | //! Child class should stop | ||
40 | virtual bool stop()=0; | ||
41 | |||
42 | // settings | ||
43 | //! Child class should return true when configuration is ok | ||
44 | virtual bool configOk()=0; | ||
45 | //! Child class should fill in settingsList | ||
46 | virtual void generateSettings() = 0; | ||
47 | //! Child class should commit from SettingsList to permanent storage | ||
48 | virtual void saveSettings() = 0; | ||
49 | |||
50 | // static functions | ||
51 | static QString getEncoderName(QString name); | ||
52 | static EncoderBase* getEncoder(QObject* parent,QString name); | ||
53 | static QStringList getEncoderList(void); | ||
54 | |||
55 | private: | ||
56 | static void initEncodernamesList(void); | ||
57 | |||
58 | protected: | ||
59 | static QMap<QString,QString> encoderList; | ||
60 | }; | ||
61 | |||
62 | #endif | ||
63 | |||
diff --git a/utils/rbutilqt/base/encoderexe.cpp b/utils/rbutilqt/base/encoderexe.cpp new file mode 100644 index 0000000000..331b5fb2a4 --- /dev/null +++ b/utils/rbutilqt/base/encoderexe.cpp | |||
@@ -0,0 +1,78 @@ | |||
1 | /*************************************************************************** | ||
2 | * __________ __ ___. | ||
3 | * Open \______ \ ____ ____ | | _\_ |__ _______ ___ | ||
4 | * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / | ||
5 | * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < | ||
6 | * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ | ||
7 | * \/ \/ \/ \/ \/ | ||
8 | * | ||
9 | * Copyright (C) 2007 by Dominik Wenger | ||
10 | * | ||
11 | * All files in this archive are subject to the GNU General Public License. | ||
12 | * See the file COPYING in the source tree root for full license agreement. | ||
13 | * | ||
14 | * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY | ||
15 | * KIND, either express or implied. | ||
16 | * | ||
17 | ****************************************************************************/ | ||
18 | |||
19 | #include <QtCore> | ||
20 | #include "encoderexe.h" | ||
21 | #include "rbsettings.h" | ||
22 | #include "utils.h" | ||
23 | #include "Logger.h" | ||
24 | |||
25 | EncoderExe::EncoderExe(QString name, QObject *parent) : EncoderBase(parent), | ||
26 | m_name(name) | ||
27 | { | ||
28 | } | ||
29 | |||
30 | |||
31 | void EncoderExe::generateSettings() | ||
32 | { | ||
33 | QString exepath = RbSettings::subValue(m_name,RbSettings::EncoderPath).toString(); | ||
34 | if(exepath.isEmpty()) exepath = Utils::findExecutable(m_name); | ||
35 | |||
36 | insertSetting(eEXEPATH, new EncTtsSetting(this, EncTtsSetting::eSTRING, | ||
37 | tr("Path to Encoder:"), exepath, EncTtsSetting::eBROWSEBTN)); | ||
38 | insertSetting(eEXEOPTIONS, new EncTtsSetting(this, EncTtsSetting::eSTRING, | ||
39 | tr("Encoder options:"), RbSettings::subValue(m_name, RbSettings::EncoderOptions))); | ||
40 | } | ||
41 | |||
42 | void EncoderExe::saveSettings() | ||
43 | { | ||
44 | RbSettings::setSubValue(m_name, RbSettings::EncoderPath, getSetting(eEXEPATH)->current().toString()); | ||
45 | RbSettings::setSubValue(m_name, RbSettings::EncoderOptions, getSetting(eEXEOPTIONS)->current().toString()); | ||
46 | RbSettings::sync(); | ||
47 | } | ||
48 | |||
49 | bool EncoderExe::start() | ||
50 | { | ||
51 | m_EncExec = RbSettings::subValue(m_name, RbSettings::EncoderPath).toString(); | ||
52 | m_EncOpts = RbSettings::subValue(m_name, RbSettings::EncoderOptions).toString(); | ||
53 | |||
54 | QFileInfo enc(m_EncExec); | ||
55 | return enc.exists(); | ||
56 | } | ||
57 | |||
58 | bool EncoderExe::encode(QString input,QString output) | ||
59 | { | ||
60 | if (!configOk()) | ||
61 | return false; | ||
62 | |||
63 | QStringList args; | ||
64 | args << m_EncOpts; | ||
65 | args << input; | ||
66 | args << output; | ||
67 | int result = QProcess::execute(m_EncExec, args); | ||
68 | return result == 0; | ||
69 | } | ||
70 | |||
71 | |||
72 | bool EncoderExe::configOk() | ||
73 | { | ||
74 | QString path = RbSettings::subValue(m_name, RbSettings::EncoderPath).toString(); | ||
75 | |||
76 | return QFileInfo::exists(path); | ||
77 | } | ||
78 | |||
diff --git a/utils/rbutilqt/base/encoderexe.h b/utils/rbutilqt/base/encoderexe.h new file mode 100644 index 0000000000..4008689167 --- /dev/null +++ b/utils/rbutilqt/base/encoderexe.h | |||
@@ -0,0 +1,53 @@ | |||
1 | /*************************************************************************** | ||
2 | * __________ __ ___. | ||
3 | * Open \______ \ ____ ____ | | _\_ |__ _______ ___ | ||
4 | * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / | ||
5 | * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < | ||
6 | * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ | ||
7 | * \/ \/ \/ \/ \/ | ||
8 | * | ||
9 | * Copyright (C) 2007 by Dominik Wenger | ||
10 | * | ||
11 | * This program is free software; you can redistribute it and/or | ||
12 | * modify it under the terms of the GNU General Public License | ||
13 | * as published by the Free Software Foundation; either version 2 | ||
14 | * of the License, or (at your option) any later version. | ||
15 | * | ||
16 | * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY | ||
17 | * KIND, either express or implied. | ||
18 | * | ||
19 | ****************************************************************************/ | ||
20 | |||
21 | #ifndef ENCODEREXES_H | ||
22 | #define ENCODEREXES_H | ||
23 | |||
24 | #include <QtCore> | ||
25 | #include "encoderbase.h" | ||
26 | |||
27 | class EncoderExe : public EncoderBase | ||
28 | { | ||
29 | enum ESettings | ||
30 | { | ||
31 | eEXEPATH, | ||
32 | eEXEOPTIONS | ||
33 | }; | ||
34 | |||
35 | Q_OBJECT | ||
36 | public: | ||
37 | EncoderExe(QString name,QObject *parent = nullptr); | ||
38 | bool encode(QString input,QString output); | ||
39 | bool start(); | ||
40 | bool stop() {return true;} | ||
41 | |||
42 | // setting | ||
43 | bool configOk(); | ||
44 | void generateSettings(); | ||
45 | void saveSettings(); | ||
46 | |||
47 | private: | ||
48 | QString m_name; | ||
49 | QString m_EncExec; | ||
50 | QString m_EncOpts; | ||
51 | }; | ||
52 | #endif | ||
53 | |||
diff --git a/utils/rbutilqt/base/encoderlame.cpp b/utils/rbutilqt/base/encoderlame.cpp new file mode 100644 index 0000000000..1658a7092d --- /dev/null +++ b/utils/rbutilqt/base/encoderlame.cpp | |||
@@ -0,0 +1,312 @@ | |||
1 | /*************************************************************************** | ||
2 | * __________ __ ___. | ||
3 | * Open \______ \ ____ ____ | | _\_ |__ _______ ___ | ||
4 | * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / | ||
5 | * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < | ||
6 | * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ | ||
7 | * \/ \/ \/ \/ \/ | ||
8 | * | ||
9 | * Copyright (C) 2012 Dominik Riebeling | ||
10 | * | ||
11 | * All files in this archive are subject to the GNU General Public License. | ||
12 | * See the file COPYING in the source tree root for full license agreement. | ||
13 | * | ||
14 | * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY | ||
15 | * KIND, either express or implied. | ||
16 | * | ||
17 | ****************************************************************************/ | ||
18 | |||
19 | #include <QtCore> | ||
20 | #include "encoderlame.h" | ||
21 | #include "rbsettings.h" | ||
22 | #include "lame/lame.h" | ||
23 | #include "Logger.h" | ||
24 | |||
25 | /** Resolve a symbol from loaded library. | ||
26 | */ | ||
27 | #define SYMBOLRESOLVE(symbol, type) \ | ||
28 | do { m_##symbol = (type)lib.resolve(#symbol); \ | ||
29 | if(!m_##symbol) return; \ | ||
30 | LOG_INFO() << "Resolved symbol " #symbol; } \ | ||
31 | while(0) | ||
32 | |||
33 | EncoderLame::EncoderLame(QObject *parent) : EncoderBase(parent), | ||
34 | lib("libmp3lame", this), m_symbolsResolved(false) | ||
35 | { | ||
36 | lib.load(); | ||
37 | if (!lib.isLoaded()) { | ||
38 | LOG_WARNING() << "Loading mp3lame lib failed:" << lib.errorString(); | ||
39 | return; | ||
40 | } | ||
41 | |||
42 | SYMBOLRESOLVE(get_lame_short_version, const char* (*)()); | ||
43 | SYMBOLRESOLVE(lame_set_out_samplerate, int (*)(lame_global_flags*, int)); | ||
44 | SYMBOLRESOLVE(lame_set_in_samplerate, int (*)(lame_global_flags*, int)); | ||
45 | SYMBOLRESOLVE(lame_set_num_channels, int (*)(lame_global_flags*, int)); | ||
46 | SYMBOLRESOLVE(lame_set_scale, int (*)(lame_global_flags*, float)); | ||
47 | SYMBOLRESOLVE(lame_set_mode, int (*)(lame_global_flags*, MPEG_mode)); | ||
48 | SYMBOLRESOLVE(lame_set_VBR, int (*)(lame_global_flags*, vbr_mode)); | ||
49 | SYMBOLRESOLVE(lame_set_VBR_quality, int (*)(lame_global_flags*, float)); | ||
50 | SYMBOLRESOLVE(lame_set_VBR_max_bitrate_kbps, int (*)(lame_global_flags*, int)); | ||
51 | SYMBOLRESOLVE(lame_set_bWriteVbrTag, int (*)(lame_global_flags*, int)); | ||
52 | SYMBOLRESOLVE(lame_init, lame_global_flags* (*)()); | ||
53 | SYMBOLRESOLVE(lame_init_params, int (*)(lame_global_flags*)); | ||
54 | SYMBOLRESOLVE(lame_encode_buffer, int (*)(lame_global_flags*, short int*, short int*, int, unsigned char*, int)); | ||
55 | SYMBOLRESOLVE(lame_encode_flush, int (*)(lame_global_flags*, unsigned char*, int)); | ||
56 | SYMBOLRESOLVE(lame_close, int (*)(lame_global_flags*)); | ||
57 | |||
58 | m_encoderVolume = RbSettings::subValue("lame", RbSettings::EncoderVolume).toDouble(); | ||
59 | m_encoderQuality = RbSettings::subValue("lame", RbSettings::EncoderQuality).toDouble(); | ||
60 | m_symbolsResolved = true; | ||
61 | } | ||
62 | |||
63 | void EncoderLame::generateSettings() | ||
64 | { | ||
65 | // no settings for now. | ||
66 | // show lame version. | ||
67 | if(m_symbolsResolved) { | ||
68 | double quality = RbSettings::subValue("lame", | ||
69 | RbSettings::EncoderQuality).toDouble(); | ||
70 | // default quality is 0.999. | ||
71 | if(quality < 0) { | ||
72 | quality = 0.99; | ||
73 | } | ||
74 | insertSetting(LAMEVERSION, new EncTtsSetting(this, EncTtsSetting::eREADONLYSTRING, | ||
75 | tr("LAME"), QString(m_get_lame_short_version()))); | ||
76 | insertSetting(VOLUME, new EncTtsSetting(this, EncTtsSetting::eDOUBLE, | ||
77 | tr("Volume"), | ||
78 | RbSettings::subValue("lame", RbSettings::EncoderVolume).toDouble(), | ||
79 | 0.0, 2.0)); | ||
80 | insertSetting(QUALITY, new EncTtsSetting(this, EncTtsSetting::eDOUBLE, | ||
81 | tr("Quality"), quality, 0.0, 1.0)); | ||
82 | } | ||
83 | else { | ||
84 | insertSetting(LAMEVERSION, new EncTtsSetting(this, EncTtsSetting::eREADONLYSTRING, | ||
85 | tr("LAME"), tr("Could not find libmp3lame!"))); | ||
86 | } | ||
87 | } | ||
88 | |||
89 | void EncoderLame::saveSettings() | ||
90 | { | ||
91 | if(m_symbolsResolved) { | ||
92 | RbSettings::setSubValue("lame", RbSettings::EncoderVolume, | ||
93 | getSetting(VOLUME)->current().toDouble()); | ||
94 | RbSettings::setSubValue("lame", RbSettings::EncoderQuality, | ||
95 | getSetting(QUALITY)->current().toDouble()); | ||
96 | m_encoderVolume = | ||
97 | RbSettings::subValue("lame", RbSettings::EncoderVolume).toDouble(); | ||
98 | m_encoderQuality = | ||
99 | RbSettings::subValue("lame", RbSettings::EncoderQuality).toDouble(); | ||
100 | } | ||
101 | } | ||
102 | |||
103 | bool EncoderLame::start() | ||
104 | { | ||
105 | if(!m_symbolsResolved) { | ||
106 | return false; | ||
107 | } | ||
108 | // try to get config from settings | ||
109 | return true; | ||
110 | } | ||
111 | |||
112 | bool EncoderLame::encode(QString input,QString output) | ||
113 | { | ||
114 | LOG_INFO() << "Encoding" << QDir::cleanPath(input); | ||
115 | if(!m_symbolsResolved) { | ||
116 | LOG_ERROR() << "Symbols not successfully resolved, cannot run!"; | ||
117 | return false; | ||
118 | } | ||
119 | |||
120 | QFile fin(input); | ||
121 | QFile fout(output); | ||
122 | // initialize encoder | ||
123 | lame_global_flags *gfp; | ||
124 | unsigned char header[12]; | ||
125 | unsigned char chunkheader[8]; | ||
126 | unsigned int datalength = 0; | ||
127 | unsigned int channels = 0; | ||
128 | unsigned int samplerate = 0; | ||
129 | unsigned int samplesize = 0; | ||
130 | int num_samples = 0; | ||
131 | int ret; | ||
132 | unsigned char* mp3buf; | ||
133 | int mp3buflen; | ||
134 | short int* wavbuf; | ||
135 | int wavbuflen; | ||
136 | |||
137 | |||
138 | gfp = m_lame_init(); | ||
139 | m_lame_set_out_samplerate(gfp, 12000); // resample to 12kHz | ||
140 | // scale input volume | ||
141 | m_lame_set_scale(gfp, m_encoderVolume); | ||
142 | m_lame_set_mode(gfp, MONO); // mono output mode | ||
143 | m_lame_set_VBR(gfp, vbr_default); // enable default VBR mode | ||
144 | // VBR quality | ||
145 | m_lame_set_VBR_quality(gfp, m_encoderQuality); | ||
146 | m_lame_set_VBR_max_bitrate_kbps(gfp, 64); // maximum bitrate 64kbps | ||
147 | m_lame_set_bWriteVbrTag(gfp, 0); // disable LAME tag. | ||
148 | |||
149 | if(!fin.open(QIODevice::ReadOnly)) { | ||
150 | LOG_ERROR() << "Could not open input file" << input; | ||
151 | return false; | ||
152 | } | ||
153 | |||
154 | // read RIFF header | ||
155 | fin.read((char*)header, 12); | ||
156 | if(memcmp("RIFF", header, 4) != 0) { | ||
157 | LOG_ERROR() << "RIFF header not found!" | ||
158 | << header[0] << header[1] << header[2] << header[3]; | ||
159 | fin.close(); | ||
160 | return false; | ||
161 | } | ||
162 | if(memcmp("WAVE", &header[8], 4) != 0) { | ||
163 | LOG_ERROR() << "WAVE FOURCC not found!" | ||
164 | << header[8] << header[9] << header[10] << header[11]; | ||
165 | fin.close(); | ||
166 | return false; | ||
167 | } | ||
168 | |||
169 | // search for fmt chunk | ||
170 | do { | ||
171 | // read fmt | ||
172 | fin.read((char*)chunkheader, 8); | ||
173 | int chunkdatalen = chunkheader[4] | chunkheader[5]<<8 | ||
174 | | chunkheader[6]<<16 | chunkheader[7]<<24; | ||
175 | if(memcmp("fmt ", chunkheader, 4) == 0) { | ||
176 | // fmt found, read rest of chunk. | ||
177 | // NOTE: This code ignores the format tag value. | ||
178 | // Ideally this should be checked as well. However, rbspeex doesn't | ||
179 | // check the format tag either when reading wave files, so if | ||
180 | // problems arise we should notice pretty soon. Furthermore, the | ||
181 | // input format used should be known. In case some TTS uses a | ||
182 | // different wave encoding some time this needs to get adjusted. | ||
183 | if(chunkdatalen < 16) { | ||
184 | LOG_ERROR() << "fmt chunk too small!"; | ||
185 | } | ||
186 | else { | ||
187 | unsigned char *buf = new unsigned char[chunkdatalen]; | ||
188 | fin.read((char*)buf, chunkdatalen); | ||
189 | channels = buf[2] | buf[3]<<8; | ||
190 | samplerate = buf[4] | buf[5]<<8 | buf[6]<<16 | buf[7]<<24; | ||
191 | samplesize = buf[14] | buf[15]<<8; | ||
192 | delete[] buf; | ||
193 | } | ||
194 | } | ||
195 | // read data | ||
196 | else if(memcmp("data", chunkheader, 4) == 0) { | ||
197 | datalength = chunkdatalen; | ||
198 | break; | ||
199 | } | ||
200 | else { | ||
201 | // unknown chunk, just skip its data. | ||
202 | LOG_WARNING() << "unknown chunk, skipping." | ||
203 | << chunkheader[0] << chunkheader[1] | ||
204 | << chunkheader[2] << chunkheader[3]; | ||
205 | fin.seek(fin.pos() + chunkdatalen); | ||
206 | } | ||
207 | } while(!fin.atEnd()); | ||
208 | |||
209 | // check format | ||
210 | if(channels == 0 || samplerate == 0 || samplesize == 0 || datalength == 0) { | ||
211 | LOG_ERROR() << "invalid format. Channels:" << channels | ||
212 | << "Samplerate:" << samplerate << "Samplesize:" << samplesize | ||
213 | << "Data chunk length:" << datalength; | ||
214 | fin.close(); | ||
215 | return false; | ||
216 | } | ||
217 | num_samples = (datalength / channels / (samplesize/8)); | ||
218 | |||
219 | // set input format values | ||
220 | m_lame_set_in_samplerate(gfp, samplerate); | ||
221 | m_lame_set_num_channels(gfp, channels); | ||
222 | |||
223 | // initialize encoder. | ||
224 | ret = m_lame_init_params(gfp); | ||
225 | if(ret != 0) { | ||
226 | LOG_ERROR() << "lame_init_params() failed with" << ret; | ||
227 | fin.close(); | ||
228 | return false; | ||
229 | } | ||
230 | |||
231 | // we're dealing with rather small files here (100kB-ish), so don't care | ||
232 | // about the possible output size and simply allocate the same number of | ||
233 | // bytes the input file has. This wastes space but should be ok. | ||
234 | // Put an upper limit of 8MiB. | ||
235 | if(datalength > 8*1024*1024) { | ||
236 | LOG_ERROR() << "Input file too large:" << datalength; | ||
237 | fin.close(); | ||
238 | return false; | ||
239 | } | ||
240 | mp3buflen = datalength; | ||
241 | wavbuflen = datalength; | ||
242 | mp3buf = new unsigned char[mp3buflen]; | ||
243 | wavbuf = new short int[wavbuflen]; | ||
244 | #if defined(Q_OS_MACX) | ||
245 | // handle byte order -- the host might not be LE. | ||
246 | if(samplesize == 8) { | ||
247 | // no need to convert. | ||
248 | fin.read((char*)wavbuf, wavbuflen); | ||
249 | } | ||
250 | else if(samplesize == 16) { | ||
251 | // read LE 16bit words. Since the input format is either mono or | ||
252 | // interleaved there's no need to care for that. | ||
253 | unsigned int pos = 0; | ||
254 | char word[2]; | ||
255 | while(pos < datalength) { | ||
256 | fin.read(word, 2); | ||
257 | wavbuf[pos++] = (word[0]&0xff) | ((word[1]<<8)&0xff00); | ||
258 | } | ||
259 | } | ||
260 | else { | ||
261 | LOG_ERROR() << "Unknown samplesize:" << samplesize; | ||
262 | fin.close(); | ||
263 | delete[] mp3buf; | ||
264 | delete[] wavbuf; | ||
265 | return false; | ||
266 | } | ||
267 | #else | ||
268 | // all systems but OS X are considered LE. | ||
269 | fin.read((char*)wavbuf, wavbuflen); | ||
270 | #endif | ||
271 | fin.close(); | ||
272 | // encode data. | ||
273 | fout.open(QIODevice::ReadWrite); | ||
274 | ret = m_lame_encode_buffer(gfp, wavbuf, wavbuf, num_samples, mp3buf, mp3buflen); | ||
275 | if(ret < 0) { | ||
276 | LOG_ERROR() << "Error during encoding:" << ret; | ||
277 | } | ||
278 | if(fout.write((char*)mp3buf, ret) != (unsigned int)ret) { | ||
279 | LOG_ERROR() << "Writing mp3 data failed!" << ret; | ||
280 | fout.close(); | ||
281 | delete[] mp3buf; | ||
282 | delete[] wavbuf; | ||
283 | return false; | ||
284 | } | ||
285 | // flush remaining data | ||
286 | ret = m_lame_encode_flush(gfp, mp3buf, mp3buflen); | ||
287 | if(fout.write((char*)mp3buf, ret) != (unsigned int)ret) { | ||
288 | LOG_ERROR() << "Writing final mp3 data failed!"; | ||
289 | fout.close(); | ||
290 | delete[] mp3buf; | ||
291 | delete[] wavbuf; | ||
292 | return false; | ||
293 | } | ||
294 | // shut down encoder and clean up. | ||
295 | m_lame_close(gfp); | ||
296 | fout.close(); | ||
297 | delete[] mp3buf; | ||
298 | delete[] wavbuf; | ||
299 | |||
300 | return true; | ||
301 | } | ||
302 | |||
303 | /** Check if the current configuration is usable. | ||
304 | * Since we're loading a library dynamically in the constructor test if that | ||
305 | * succeeded. Otherwise the "configuration" is not usable, even though the | ||
306 | * problem is not necessarily related to configuration values set by the user. | ||
307 | */ | ||
308 | bool EncoderLame::configOk() | ||
309 | { | ||
310 | return m_symbolsResolved; | ||
311 | } | ||
312 | |||
diff --git a/utils/rbutilqt/base/encoderlame.h b/utils/rbutilqt/base/encoderlame.h new file mode 100644 index 0000000000..a5f1b2d3f4 --- /dev/null +++ b/utils/rbutilqt/base/encoderlame.h | |||
@@ -0,0 +1,72 @@ | |||
1 | /*************************************************************************** | ||
2 | * __________ __ ___. | ||
3 | * Open \______ \ ____ ____ | | _\_ |__ _______ ___ | ||
4 | * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / | ||
5 | * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < | ||
6 | * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ | ||
7 | * \/ \/ \/ \/ \/ | ||
8 | * | ||
9 | * Copyright (C) 2012 Dominik Riebeling | ||
10 | * | ||
11 | * All files in this archive are subject to the GNU General Public License. | ||
12 | * See the file COPYING in the source tree root for full license agreement. | ||
13 | * | ||
14 | * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY | ||
15 | * KIND, either express or implied. | ||
16 | * | ||
17 | ****************************************************************************/ | ||
18 | |||
19 | #ifndef ENCODERLAME_H | ||
20 | #define ENCODERLAME_H | ||
21 | |||
22 | #include <QtCore> | ||
23 | #include "encoderbase.h" | ||
24 | #include "lame/lame.h" | ||
25 | |||
26 | class EncoderLame : public EncoderBase | ||
27 | { | ||
28 | enum ESettings | ||
29 | { | ||
30 | LAMEVERSION, | ||
31 | VOLUME, | ||
32 | QUALITY, | ||
33 | }; | ||
34 | |||
35 | Q_OBJECT | ||
36 | public: | ||
37 | EncoderLame(QObject *parent = nullptr); | ||
38 | bool encode(QString input,QString output); | ||
39 | bool start(); | ||
40 | bool stop() {return true;} | ||
41 | |||
42 | // for settings view | ||
43 | bool configOk(); | ||
44 | void generateSettings(); | ||
45 | void saveSettings(); | ||
46 | |||
47 | private: | ||
48 | QLibrary lib; | ||
49 | const char*(*m_get_lame_short_version)(void); | ||
50 | int (*m_lame_set_out_samplerate)(lame_global_flags*, int); | ||
51 | int (*m_lame_set_in_samplerate)(lame_global_flags*, int); | ||
52 | int (*m_lame_set_num_channels)(lame_global_flags*, int); | ||
53 | int (*m_lame_set_scale)(lame_global_flags*, float); | ||
54 | int (*m_lame_set_mode)(lame_global_flags*, MPEG_mode); | ||
55 | int (*m_lame_set_VBR)(lame_global_flags*, vbr_mode); | ||
56 | int (*m_lame_set_VBR_quality)(lame_global_flags*, float); | ||
57 | int (*m_lame_set_VBR_max_bitrate_kbps)(lame_global_flags*, int); | ||
58 | int (*m_lame_set_bWriteVbrTag)(lame_global_flags*, int); | ||
59 | lame_global_flags*(*m_lame_init)(void); | ||
60 | int (*m_lame_init_params)(lame_global_flags*); | ||
61 | int (*m_lame_encode_buffer)(lame_global_flags*, short int[], short | ||
62 | int[], int, unsigned char*, int); | ||
63 | int (*m_lame_encode_flush)(lame_global_flags*, unsigned char*, int); | ||
64 | int (*m_lame_close)(lame_global_flags*); | ||
65 | |||
66 | bool m_symbolsResolved; | ||
67 | double m_encoderVolume; | ||
68 | double m_encoderQuality; | ||
69 | }; | ||
70 | |||
71 | #endif | ||
72 | |||
diff --git a/utils/rbutilqt/base/encoderrbspeex.cpp b/utils/rbutilqt/base/encoderrbspeex.cpp new file mode 100644 index 0000000000..2bee66028a --- /dev/null +++ b/utils/rbutilqt/base/encoderrbspeex.cpp | |||
@@ -0,0 +1,119 @@ | |||
1 | /*************************************************************************** | ||
2 | * __________ __ ___. | ||
3 | * Open \______ \ ____ ____ | | _\_ |__ _______ ___ | ||
4 | * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / | ||
5 | * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < | ||
6 | * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ | ||
7 | * \/ \/ \/ \/ \/ | ||
8 | * | ||
9 | * Copyright (C) 2007 by Dominik Wenger | ||
10 | * | ||
11 | * All files in this archive are subject to the GNU General Public License. | ||
12 | * See the file COPYING in the source tree root for full license agreement. | ||
13 | * | ||
14 | * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY | ||
15 | * KIND, either express or implied. | ||
16 | * | ||
17 | ****************************************************************************/ | ||
18 | |||
19 | #include <QtCore> | ||
20 | #include "encoderrbspeex.h" | ||
21 | #include "rbsettings.h" | ||
22 | #include "rbspeex.h" | ||
23 | #include "Logger.h" | ||
24 | |||
25 | EncoderRbSpeex::EncoderRbSpeex(QObject *parent) : EncoderBase(parent) | ||
26 | { | ||
27 | |||
28 | } | ||
29 | |||
30 | void EncoderRbSpeex::generateSettings() | ||
31 | { | ||
32 | loadSettings(); | ||
33 | insertSetting(eVOLUME, new EncTtsSetting(this, EncTtsSetting::eDOUBLE, | ||
34 | tr("Volume:"), volume, 0.0, 2.0)); | ||
35 | insertSetting(eQUALITY, new EncTtsSetting(this, EncTtsSetting::eDOUBLE, | ||
36 | tr("Quality:"), quality, 0, 10.0)); | ||
37 | insertSetting(eCOMPLEXITY, new EncTtsSetting(this, EncTtsSetting::eINT, | ||
38 | tr("Complexity:"), complexity, 0, 10)); | ||
39 | insertSetting(eNARROWBAND,new EncTtsSetting(this, EncTtsSetting::eBOOL, | ||
40 | tr("Use Narrowband:"), narrowband)); | ||
41 | } | ||
42 | |||
43 | void EncoderRbSpeex::saveSettings() | ||
44 | { | ||
45 | //save settings in user config | ||
46 | RbSettings::setSubValue("rbspeex",RbSettings::EncoderVolume, | ||
47 | getSetting(eVOLUME)->current().toDouble()); | ||
48 | RbSettings::setSubValue("rbspeex",RbSettings::EncoderQuality, | ||
49 | getSetting(eQUALITY)->current().toDouble()); | ||
50 | RbSettings::setSubValue("rbspeex",RbSettings::EncoderComplexity, | ||
51 | getSetting(eCOMPLEXITY)->current().toInt()); | ||
52 | RbSettings::setSubValue("rbspeex",RbSettings::EncoderNarrowBand, | ||
53 | getSetting(eNARROWBAND)->current().toBool()); | ||
54 | |||
55 | RbSettings::sync(); | ||
56 | } | ||
57 | |||
58 | |||
59 | void EncoderRbSpeex::loadSettings(void) | ||
60 | { | ||
61 | // try to get config from settings | ||
62 | quality = RbSettings::subValue("rbspeex", RbSettings::EncoderQuality).toDouble(); | ||
63 | if(quality < 0) { | ||
64 | quality = 8.0; | ||
65 | } | ||
66 | complexity = RbSettings::subValue("rbspeex", RbSettings::EncoderComplexity).toInt(); | ||
67 | volume = RbSettings::subValue("rbspeex", RbSettings::EncoderVolume).toDouble(); | ||
68 | narrowband = RbSettings::subValue("rbspeex", RbSettings::EncoderNarrowBand).toBool(); | ||
69 | } | ||
70 | |||
71 | |||
72 | bool EncoderRbSpeex::start() | ||
73 | { | ||
74 | |||
75 | // make sure configuration parameters are set. | ||
76 | loadSettings(); | ||
77 | return true; | ||
78 | } | ||
79 | |||
80 | bool EncoderRbSpeex::encode(QString input,QString output) | ||
81 | { | ||
82 | LOG_INFO() << "Encoding " << input << " to "<< output; | ||
83 | char errstr[512]; | ||
84 | |||
85 | FILE *fin,*fout; | ||
86 | if ((fin = fopen(input.toLocal8Bit(), "rb")) == nullptr) { | ||
87 | LOG_ERROR() << "Error: could not open input file\n"; | ||
88 | return false; | ||
89 | } | ||
90 | if ((fout = fopen(output.toLocal8Bit(), "wb")) == nullptr) { | ||
91 | LOG_ERROR() << "Error: could not open output file\n"; | ||
92 | fclose(fin); | ||
93 | return false; | ||
94 | } | ||
95 | |||
96 | int ret = encode_file(fin, fout, quality, complexity, narrowband, volume, | ||
97 | errstr, sizeof(errstr)); | ||
98 | fclose(fout); | ||
99 | fclose(fin); | ||
100 | |||
101 | if (!ret) { | ||
102 | /* Attempt to delete unfinished output */ | ||
103 | LOG_ERROR() << "Error:" << errstr; | ||
104 | QFile(output).remove(); | ||
105 | return false; | ||
106 | } | ||
107 | return true; | ||
108 | } | ||
109 | |||
110 | bool EncoderRbSpeex::configOk() | ||
111 | { | ||
112 | // check config. Make sure current settings are loaded. | ||
113 | loadSettings(); | ||
114 | if(volume <= 0 || quality <= 0 || complexity <= 0) | ||
115 | return false; | ||
116 | else | ||
117 | return true; | ||
118 | } | ||
119 | |||
diff --git a/utils/rbutilqt/base/encoderrbspeex.h b/utils/rbutilqt/base/encoderrbspeex.h new file mode 100644 index 0000000000..6ad41c38a3 --- /dev/null +++ b/utils/rbutilqt/base/encoderrbspeex.h | |||
@@ -0,0 +1,61 @@ | |||
1 | /*************************************************************************** | ||
2 | * __________ __ ___. | ||
3 | * Open \______ \ ____ ____ | | _\_ |__ _______ ___ | ||
4 | * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / | ||
5 | * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < | ||
6 | * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ | ||
7 | * \/ \/ \/ \/ \/ | ||
8 | * | ||
9 | * Copyright (C) 2007 by Dominik Wenger | ||
10 | * | ||
11 | * All files in this archive are subject to the GNU General Public License. | ||
12 | * See the file COPYING in the source tree root for full license agreement. | ||
13 | * | ||
14 | * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY | ||
15 | * KIND, either express or implied. | ||
16 | * | ||
17 | ****************************************************************************/ | ||
18 | |||
19 | #ifndef ENCODERRBSPEEX_H | ||
20 | #define ENCODERRBSPEEX_H | ||
21 | |||
22 | #include <QtCore> | ||
23 | #include "encoderbase.h" | ||
24 | |||
25 | class EncoderRbSpeex : public EncoderBase | ||
26 | { | ||
27 | enum ESettings | ||
28 | { | ||
29 | eVOLUME, | ||
30 | eQUALITY, | ||
31 | eCOMPLEXITY, | ||
32 | eNARROWBAND | ||
33 | }; | ||
34 | |||
35 | Q_OBJECT | ||
36 | public: | ||
37 | EncoderRbSpeex(QObject *parent = nullptr); | ||
38 | bool encode(QString input,QString output); | ||
39 | bool start(); | ||
40 | bool stop() {return true;} | ||
41 | |||
42 | // for settings view | ||
43 | bool configOk(); | ||
44 | void generateSettings(); | ||
45 | void saveSettings(); | ||
46 | |||
47 | private: | ||
48 | void loadSettings(void); | ||
49 | float quality; | ||
50 | float volume; | ||
51 | int complexity; | ||
52 | bool narrowband; | ||
53 | |||
54 | float defaultQuality; | ||
55 | float defaultVolume; | ||
56 | int defaultComplexity; | ||
57 | bool defaultBand; | ||
58 | }; | ||
59 | |||
60 | #endif | ||
61 | |||
diff --git a/utils/rbutilqt/base/encttssettings.cpp b/utils/rbutilqt/base/encttssettings.cpp new file mode 100644 index 0000000000..9c8c2e57fd --- /dev/null +++ b/utils/rbutilqt/base/encttssettings.cpp | |||
@@ -0,0 +1,70 @@ | |||
1 | /*************************************************************************** | ||
2 | * __________ __ ___. | ||
3 | * Open \______ \ ____ ____ | | _\_ |__ _______ ___ | ||
4 | * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / | ||
5 | * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < | ||
6 | * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ | ||
7 | * \/ \/ \/ \/ \/ | ||
8 | * | ||
9 | * Copyright (C) 2007 by Dominik Wenger | ||
10 | * $Id: encoders.h 17902 2008-06-30 22:09:45Z bluebrother $ | ||
11 | * | ||
12 | * This program is free software; you can redistribute it and/or | ||
13 | * modify it under the terms of the GNU General Public License | ||
14 | * as published by the Free Software Foundation; either version 2 | ||
15 | * of the License, or (at your option) any later version. | ||
16 | * | ||
17 | * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY | ||
18 | * KIND, either express or implied. | ||
19 | * | ||
20 | ****************************************************************************/ | ||
21 | |||
22 | #include "encttssettings.h" | ||
23 | |||
24 | |||
25 | EncTtsSetting::EncTtsSetting(QObject* parent,ESettingType type,QString name,QVariant current, EButton btn) : QObject(parent) | ||
26 | { | ||
27 | m_btn = btn; | ||
28 | m_name =name; | ||
29 | m_type =type; | ||
30 | m_currentValue = current; | ||
31 | } | ||
32 | |||
33 | EncTtsSetting::EncTtsSetting(QObject* parent,ESettingType type,QString name,QVariant current,QStringList list,EButton btn) : QObject(parent) | ||
34 | { | ||
35 | m_btn = btn; | ||
36 | m_name =name; | ||
37 | m_type =type; | ||
38 | m_currentValue = current; | ||
39 | m_list = list; | ||
40 | } | ||
41 | |||
42 | EncTtsSetting::EncTtsSetting(QObject* parent,ESettingType type,QString name,QVariant current,QVariant min,QVariant max, EButton btn) : QObject(parent) | ||
43 | { | ||
44 | m_btn = btn; | ||
45 | m_name =name; | ||
46 | m_type =type; | ||
47 | m_currentValue = current; | ||
48 | m_minValue = min; | ||
49 | m_maxValue = max; | ||
50 | } | ||
51 | |||
52 | void EncTtsSetting::setCurrent(QVariant current,bool noticeGui) | ||
53 | { | ||
54 | m_currentValue = current; | ||
55 | emit dataChanged(); | ||
56 | |||
57 | if(noticeGui) emit updateGui(); | ||
58 | } | ||
59 | |||
60 | //! insert a setting | ||
61 | void EncTtsSettingInterface::insertSetting(int id,EncTtsSetting* setting) | ||
62 | { | ||
63 | settingsList.insert(id,setting); | ||
64 | } | ||
65 | |||
66 | //! retrieve a specific setting | ||
67 | EncTtsSetting* EncTtsSettingInterface::getSetting(int id) | ||
68 | { | ||
69 | return settingsList.at(id); | ||
70 | } | ||
diff --git a/utils/rbutilqt/base/encttssettings.h b/utils/rbutilqt/base/encttssettings.h new file mode 100644 index 0000000000..1258d81d57 --- /dev/null +++ b/utils/rbutilqt/base/encttssettings.h | |||
@@ -0,0 +1,128 @@ | |||
1 | /*************************************************************************** | ||
2 | * __________ __ ___. | ||
3 | * Open \______ \ ____ ____ | | _\_ |__ _______ ___ | ||
4 | * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / | ||
5 | * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < | ||
6 | * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ | ||
7 | * \/ \/ \/ \/ \/ | ||
8 | * | ||
9 | * Copyright (C) 2007 by Dominik Wenger | ||
10 | * | ||
11 | * This program is free software; you can redistribute it and/or | ||
12 | * modify it under the terms of the GNU General Public License | ||
13 | * as published by the Free Software Foundation; either version 2 | ||
14 | * of the License, or (at your option) any later version. | ||
15 | * | ||
16 | * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY | ||
17 | * KIND, either express or implied. | ||
18 | * | ||
19 | ****************************************************************************/ | ||
20 | |||
21 | #ifndef ENCTTSSETTINGS_H | ||
22 | #define ENCTTSSETTINGS_H | ||
23 | |||
24 | #include <QtCore> | ||
25 | |||
26 | //! \brief This class stores everything needed to display a Setting. | ||
27 | //! | ||
28 | class EncTtsSetting : public QObject | ||
29 | { | ||
30 | Q_OBJECT | ||
31 | public: | ||
32 | enum ESettingType | ||
33 | { | ||
34 | eBASE, | ||
35 | eBOOL, | ||
36 | eDOUBLE, | ||
37 | eINT, | ||
38 | eSTRING, | ||
39 | eREADONLYSTRING, | ||
40 | eSTRINGLIST, | ||
41 | }; | ||
42 | enum EButton | ||
43 | { | ||
44 | eNOBTN, | ||
45 | eBROWSEBTN, | ||
46 | eREFRESHBTN | ||
47 | }; | ||
48 | |||
49 | //! constructor for a String or Bool setting | ||
50 | EncTtsSetting(QObject* parent,ESettingType type,QString name,QVariant current,EButton btn = eNOBTN); | ||
51 | //! contructor for a Stringlist setting, ie a enumeration | ||
52 | EncTtsSetting(QObject* parent,ESettingType type,QString name,QVariant current,QStringList list,EButton btn = eNOBTN); | ||
53 | //! constructor for a setting with a min-max range | ||
54 | EncTtsSetting(QObject* parent,ESettingType type,QString name,QVariant current,QVariant min,QVariant max,EButton = eNOBTN); | ||
55 | |||
56 | //! get currentValue | ||
57 | QVariant current() {return m_currentValue;} | ||
58 | //! set currentValue | ||
59 | void setCurrent(QVariant current,bool noticeGui=true); | ||
60 | |||
61 | //! get name of the Setting | ||
62 | QString name() {return m_name;} | ||
63 | //! get the type of the setting | ||
64 | ESettingType type() {return m_type;} | ||
65 | //! get what type of button this setting needs | ||
66 | EButton button() {return m_btn;} | ||
67 | //! get the minValue (only valid for a range setting, ie eDOUBLE or eINT) | ||
68 | QVariant min() {return m_minValue; } | ||
69 | //! get the maxValue (only valid for a range setting, ie eDOUBLE or eINT) | ||
70 | QVariant max() {return m_maxValue; } | ||
71 | //! get the enumerationlist (only valid for eSTRINGLIST settings) | ||
72 | QStringList list() {return m_list;} | ||
73 | //! set the enumeration list | ||
74 | void setList(QStringList list){m_list = list;} | ||
75 | |||
76 | signals: | ||
77 | //! connect to this signal if you want to get noticed when the data changes | ||
78 | void dataChanged(); | ||
79 | //! connect to this if you want to react on refresh button | ||
80 | void refresh(); | ||
81 | //! will be emited when the gui should update this setting | ||
82 | void updateGui(); | ||
83 | |||
84 | private: | ||
85 | ESettingType m_type; | ||
86 | EButton m_btn; | ||
87 | QString m_name; | ||
88 | QVariant m_currentValue; | ||
89 | QVariant m_minValue; | ||
90 | QVariant m_maxValue; | ||
91 | QStringList m_list; | ||
92 | }; | ||
93 | |||
94 | |||
95 | //! \brief this class is the Interface for Encoder and TTS engines, to display settings | ||
96 | //! It wraps nearly everything needed, only updateModel() and commitModel() needs to be reimplemented | ||
97 | //! | ||
98 | class EncTtsSettingInterface : public QObject | ||
99 | { | ||
100 | Q_OBJECT | ||
101 | public: | ||
102 | EncTtsSettingInterface(QObject* parent) : QObject(parent) {} | ||
103 | |||
104 | //! get the Settings list | ||
105 | QList<EncTtsSetting*> getSettings() {generateSettings(); return settingsList;} | ||
106 | |||
107 | //! Chlid class should commit the from SettingsList to permanent storage | ||
108 | virtual void saveSettings() = 0; | ||
109 | |||
110 | signals: | ||
111 | void busy(); // emit this if a operation takes time | ||
112 | void busyEnd(); // emit this at the end of a busy section | ||
113 | |||
114 | protected: | ||
115 | //! Child class should fill in the setttingsList | ||
116 | virtual void generateSettings() = 0; | ||
117 | |||
118 | //! insert a setting | ||
119 | void insertSetting(int id,EncTtsSetting* setting); | ||
120 | //! retrieve a specific setting | ||
121 | EncTtsSetting* getSetting(int id); | ||
122 | |||
123 | private: | ||
124 | //! The setting storage. | ||
125 | QList<EncTtsSetting*> settingsList; | ||
126 | |||
127 | }; | ||
128 | #endif | ||
diff --git a/utils/rbutilqt/base/httpget.cpp b/utils/rbutilqt/base/httpget.cpp new file mode 100644 index 0000000000..2df9501fd6 --- /dev/null +++ b/utils/rbutilqt/base/httpget.cpp | |||
@@ -0,0 +1,256 @@ | |||
1 | /*************************************************************************** | ||
2 | * __________ __ ___. | ||
3 | * Open \______ \ ____ ____ | | _\_ |__ _______ ___ | ||
4 | * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / | ||
5 | * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < | ||
6 | * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ | ||
7 | * \/ \/ \/ \/ \/ | ||
8 | * | ||
9 | * Copyright (C) 2013 by Dominik Riebeling | ||
10 | * | ||
11 | * All files in this archive are subject to the GNU General Public License. | ||
12 | * See the file COPYING in the source tree root for full license agreement. | ||
13 | * | ||
14 | * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY | ||
15 | * KIND, either express or implied. | ||
16 | * | ||
17 | ****************************************************************************/ | ||
18 | |||
19 | #include <QtNetwork> | ||
20 | |||
21 | #include <QNetworkAccessManager> | ||
22 | #include <QNetworkRequest> | ||
23 | |||
24 | #include "httpget.h" | ||
25 | #include "Logger.h" | ||
26 | |||
27 | QString HttpGet::m_globalUserAgent; //< globally set user agent for requests | ||
28 | QDir HttpGet::m_globalCache; //< global cach path value for new objects | ||
29 | QNetworkProxy HttpGet::m_globalProxy; | ||
30 | |||
31 | HttpGet::HttpGet(QObject *parent) | ||
32 | : QObject(parent), | ||
33 | m_mgr(this), | ||
34 | m_reply(nullptr), | ||
35 | m_cache(nullptr), | ||
36 | m_cachedir(m_globalCache), | ||
37 | m_outputFile(nullptr), | ||
38 | m_proxy(QNetworkProxy::NoProxy) | ||
39 | { | ||
40 | setCache(true); | ||
41 | connect(&m_mgr, &QNetworkAccessManager::finished, this, | ||
42 | static_cast<void (HttpGet::*)(QNetworkReply*)>(&HttpGet::requestFinished)); | ||
43 | m_lastServerTimestamp = QDateTime(); | ||
44 | } | ||
45 | |||
46 | |||
47 | /** @brief set cache path | ||
48 | * @param d new directory to use as cache path | ||
49 | */ | ||
50 | void HttpGet::setCache(const QDir& d) | ||
51 | { | ||
52 | if(m_cache && m_cachedir == d.absolutePath()) | ||
53 | return; | ||
54 | m_cachedir.setPath(d.absolutePath()); | ||
55 | setCache(true); | ||
56 | } | ||
57 | |||
58 | |||
59 | /** @brief enable / disable cache useage | ||
60 | * @param c set cache usage | ||
61 | */ | ||
62 | void HttpGet::setCache(bool c) | ||
63 | { | ||
64 | // don't change cache if it's already (un)set. | ||
65 | if(c && m_cache) return; | ||
66 | if(!c && !m_cache) return; | ||
67 | // don't delete the old cache directly, it might still be in use. Just | ||
68 | // instruct it to delete itself later. | ||
69 | if(m_cache) m_cache->deleteLater(); | ||
70 | m_cache = nullptr; | ||
71 | |||
72 | QString path = m_cachedir.absolutePath(); | ||
73 | |||
74 | if(!c || m_cachedir.absolutePath().isEmpty()) { | ||
75 | LOG_INFO() << "disabling download cache"; | ||
76 | } | ||
77 | else { | ||
78 | // append the cache path to make it unique in case the path points to | ||
79 | // the system temporary path. In that case using it directly might | ||
80 | // cause problems. Extra path also used in configure dialog. | ||
81 | path += "/rbutil-cache"; | ||
82 | LOG_INFO() << "setting cache folder to" << path; | ||
83 | m_cache = new QNetworkDiskCache(this); | ||
84 | m_cache->setCacheDirectory(path); | ||
85 | } | ||
86 | m_mgr.setCache(m_cache); | ||
87 | } | ||
88 | |||
89 | |||
90 | /** @brief read all downloaded data into a buffer | ||
91 | * @return data | ||
92 | */ | ||
93 | QByteArray HttpGet::readAll() | ||
94 | { | ||
95 | return m_data; | ||
96 | } | ||
97 | |||
98 | |||
99 | /** @brief Set and enable Proxy to use. | ||
100 | * @param proxy Proxy URL. | ||
101 | */ | ||
102 | void HttpGet::setProxy(const QUrl &proxy) | ||
103 | { | ||
104 | LOG_INFO() << "Proxy set to" << proxy; | ||
105 | m_proxy.setType(QNetworkProxy::HttpProxy); | ||
106 | m_proxy.setHostName(proxy.host()); | ||
107 | m_proxy.setPort(proxy.port()); | ||
108 | m_proxy.setUser(proxy.userName()); | ||
109 | m_proxy.setPassword(proxy.password()); | ||
110 | m_mgr.setProxy(m_proxy); | ||
111 | } | ||
112 | |||
113 | |||
114 | /** @brief Enable or disable use of previously set proxy. | ||
115 | * @param enable Enable proxy. | ||
116 | */ | ||
117 | void HttpGet::setProxy(bool enable) | ||
118 | { | ||
119 | if(enable) m_mgr.setProxy(m_proxy); | ||
120 | else m_mgr.setProxy(QNetworkProxy::NoProxy); | ||
121 | } | ||
122 | |||
123 | |||
124 | /** @brief Set output file. | ||
125 | * | ||
126 | * Set filename for storing the downloaded file to. If no file is set the | ||
127 | * downloaded file will not be stored to disk but kept in memory. The result | ||
128 | * can then be retrieved using readAll(). | ||
129 | * | ||
130 | * @param file Output file. | ||
131 | */ | ||
132 | void HttpGet::setFile(QFile *file) | ||
133 | { | ||
134 | m_outputFile = file; | ||
135 | } | ||
136 | |||
137 | |||
138 | void HttpGet::abort() | ||
139 | { | ||
140 | if(m_reply) m_reply->abort(); | ||
141 | } | ||
142 | |||
143 | |||
144 | void HttpGet::requestFinished(QNetworkReply* reply) | ||
145 | { | ||
146 | m_lastStatusCode | ||
147 | = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); | ||
148 | LOG_INFO() << "Request finished, status code:" << m_lastStatusCode; | ||
149 | m_lastServerTimestamp | ||
150 | = reply->header(QNetworkRequest::LastModifiedHeader).toDateTime().toLocalTime(); | ||
151 | LOG_INFO() << "Data from cache:" | ||
152 | << reply->attribute(QNetworkRequest::SourceIsFromCacheAttribute).toBool(); | ||
153 | m_lastRequestCached = | ||
154 | reply->attribute(QNetworkRequest::SourceIsFromCacheAttribute).toBool(); | ||
155 | if(reply->attribute(QNetworkRequest::RedirectionTargetAttribute).isValid()) { | ||
156 | // handle relative URLs using QUrl::resolved() | ||
157 | QUrl org = reply->request().url(); | ||
158 | QUrl url = QUrl(org).resolved( | ||
159 | reply->attribute(QNetworkRequest::RedirectionTargetAttribute).toUrl()); | ||
160 | // reconstruct query | ||
161 | #if QT_VERSION < 0x050000 | ||
162 | QList<QPair<QByteArray, QByteArray> > qitms = org.encodedQueryItems(); | ||
163 | for(int i = 0; i < qitms.size(); ++i) | ||
164 | url.addEncodedQueryItem(qitms.at(i).first, qitms.at(i).second); | ||
165 | #else | ||
166 | url.setQuery(org.query()); | ||
167 | #endif | ||
168 | LOG_INFO() << "Redirected to" << url; | ||
169 | startRequest(url); | ||
170 | return; | ||
171 | } | ||
172 | else if(m_lastStatusCode == 200 || | ||
173 | (reply->url().scheme() == "file" && reply->error() == 0)) { | ||
174 | // callers might not be aware if the request is file:// so fake 200. | ||
175 | m_lastStatusCode = 200; | ||
176 | m_data = reply->readAll(); | ||
177 | if(m_outputFile && m_outputFile->open(QIODevice::WriteOnly)) { | ||
178 | m_outputFile->write(m_data); | ||
179 | m_outputFile->close(); | ||
180 | } | ||
181 | emit done(false); | ||
182 | } | ||
183 | else { | ||
184 | m_data.clear(); | ||
185 | emit done(true); | ||
186 | } | ||
187 | reply->deleteLater(); | ||
188 | m_reply = nullptr; | ||
189 | } | ||
190 | |||
191 | |||
192 | void HttpGet::downloadProgress(qint64 received, qint64 total) | ||
193 | { | ||
194 | emit dataReadProgress((int)received, (int)total); | ||
195 | } | ||
196 | |||
197 | |||
198 | void HttpGet::startRequest(QUrl url) | ||
199 | { | ||
200 | LOG_INFO() << "Request started"; | ||
201 | QNetworkRequest req(url); | ||
202 | if(!m_globalUserAgent.isEmpty()) | ||
203 | req.setRawHeader("User-Agent", m_globalUserAgent.toLatin1()); | ||
204 | |||
205 | m_reply = m_mgr.get(req); | ||
206 | #if QT_VERSION < 0x050f00 | ||
207 | connect(m_reply, | ||
208 | static_cast<void (QNetworkReply::*)(QNetworkReply::NetworkError)>(&QNetworkReply::error), | ||
209 | this, &HttpGet::networkError); | ||
210 | #else | ||
211 | connect(m_reply, &QNetworkReply::errorOccurred, this, &HttpGet::networkError); | ||
212 | #endif | ||
213 | connect(m_reply, &QNetworkReply::downloadProgress, this, &HttpGet::downloadProgress); | ||
214 | } | ||
215 | |||
216 | |||
217 | void HttpGet::networkError(QNetworkReply::NetworkError error) | ||
218 | { | ||
219 | LOG_ERROR() << "NetworkError occured:" << error << m_reply->errorString(); | ||
220 | m_lastErrorString = m_reply->errorString(); | ||
221 | } | ||
222 | |||
223 | |||
224 | /** @brief Retrieve the file pointed to by url. | ||
225 | * | ||
226 | * Note: This also handles file:// URLs. Be aware that QUrl requires file:// | ||
227 | * URLs to be absolute, i.e. file://filename.txt doesn't work. Use | ||
228 | * QDir::absoluteFilePath() to convert to an absolute path first. | ||
229 | * | ||
230 | * @param url URL to download. | ||
231 | */ | ||
232 | void HttpGet::getFile(const QUrl &url) | ||
233 | { | ||
234 | LOG_INFO() << "Get URI" << url.toString(); | ||
235 | m_data.clear(); | ||
236 | startRequest(url); | ||
237 | } | ||
238 | |||
239 | |||
240 | /** @brief Retrieve string representation for most recent error. | ||
241 | * @return Error string. | ||
242 | */ | ||
243 | QString HttpGet::errorString(void) | ||
244 | { | ||
245 | return m_lastErrorString; | ||
246 | } | ||
247 | |||
248 | |||
249 | /** @brief Return last HTTP response code. | ||
250 | * @return Response code. | ||
251 | */ | ||
252 | int HttpGet::httpResponse(void) | ||
253 | { | ||
254 | return m_lastStatusCode; | ||
255 | } | ||
256 | |||
diff --git a/utils/rbutilqt/base/httpget.h b/utils/rbutilqt/base/httpget.h new file mode 100644 index 0000000000..dfd7b87c89 --- /dev/null +++ b/utils/rbutilqt/base/httpget.h | |||
@@ -0,0 +1,111 @@ | |||
1 | /*************************************************************************** | ||
2 | * __________ __ ___. | ||
3 | * Open \______ \ ____ ____ | | _\_ |__ _______ ___ | ||
4 | * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / | ||
5 | * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < | ||
6 | * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ | ||
7 | * \/ \/ \/ \/ \/ | ||
8 | * | ||
9 | * Copyright (C) 2007 by Dominik Riebeling | ||
10 | * | ||
11 | * This program is free software; you can redistribute it and/or | ||
12 | * modify it under the terms of the GNU General Public License | ||
13 | * as published by the Free Software Foundation; either version 2 | ||
14 | * of the License, or (at your option) any later version. | ||
15 | * | ||
16 | * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY | ||
17 | * KIND, either express or implied. | ||
18 | * | ||
19 | ****************************************************************************/ | ||
20 | |||
21 | |||
22 | #ifndef HTTPGET_H | ||
23 | #define HTTPGET_H | ||
24 | |||
25 | #include <QtCore> | ||
26 | #include <QtNetwork> | ||
27 | #include <QNetworkAccessManager> | ||
28 | #include "Logger.h" | ||
29 | |||
30 | class HttpGet : public QObject | ||
31 | { | ||
32 | Q_OBJECT | ||
33 | |||
34 | public: | ||
35 | HttpGet(QObject *parent = nullptr); | ||
36 | |||
37 | void getFile(const QUrl &url); | ||
38 | void setProxy(const QUrl &url); | ||
39 | void setProxy(bool); | ||
40 | QString errorString(void); | ||
41 | void setFile(QFile*); | ||
42 | void setCache(const QDir&); | ||
43 | void setCache(bool); | ||
44 | int httpResponse(void); | ||
45 | QByteArray readAll(void); | ||
46 | bool isCached() | ||
47 | { return m_lastRequestCached; } | ||
48 | QDateTime timestamp(void) | ||
49 | { return m_lastServerTimestamp; } | ||
50 | //< set global cache path | ||
51 | static void setGlobalCache(const QDir& d) | ||
52 | { | ||
53 | LOG_INFO() << "Global cache set to" << d.absolutePath(); | ||
54 | m_globalCache = d; | ||
55 | } | ||
56 | //< set global proxy value | ||
57 | static void setGlobalProxy(const QUrl& p) | ||
58 | { | ||
59 | LOG_INFO() << "setting global proxy" << p; | ||
60 | if(!p.isValid() || p.isEmpty()) { | ||
61 | HttpGet::m_globalProxy.setType(QNetworkProxy::NoProxy); | ||
62 | } | ||
63 | else { | ||
64 | HttpGet::m_globalProxy.setType(QNetworkProxy::HttpProxy); | ||
65 | HttpGet::m_globalProxy.setHostName(p.host()); | ||
66 | HttpGet::m_globalProxy.setPort(p.port()); | ||
67 | HttpGet::m_globalProxy.setUser(p.userName()); | ||
68 | HttpGet::m_globalProxy.setPassword(p.password()); | ||
69 | } | ||
70 | QNetworkProxy::setApplicationProxy(QNetworkProxy::NoProxy); | ||
71 | QNetworkProxy::setApplicationProxy(HttpGet::m_globalProxy); | ||
72 | } | ||
73 | //< set global user agent string | ||
74 | static void setGlobalUserAgent(const QString& u) | ||
75 | { m_globalUserAgent = u; } | ||
76 | |||
77 | public slots: | ||
78 | void abort(void); | ||
79 | |||
80 | signals: | ||
81 | void done(bool); | ||
82 | void dataReadProgress(int, int); | ||
83 | void requestFinished(int, bool); | ||
84 | void headerFinished(void); | ||
85 | |||
86 | private slots: | ||
87 | void requestFinished(QNetworkReply* reply); | ||
88 | void startRequest(QUrl url); | ||
89 | void downloadProgress(qint64 received, qint64 total); | ||
90 | void networkError(QNetworkReply::NetworkError error); | ||
91 | |||
92 | private: | ||
93 | static QString m_globalUserAgent; | ||
94 | static QNetworkProxy m_globalProxy; | ||
95 | QNetworkAccessManager m_mgr; | ||
96 | QNetworkReply *m_reply; | ||
97 | QNetworkDiskCache *m_cache; | ||
98 | QDir m_cachedir; | ||
99 | static QDir m_globalCache; //< global cache path value | ||
100 | QByteArray m_data; | ||
101 | QFile *m_outputFile; | ||
102 | int m_lastStatusCode; | ||
103 | QString m_lastErrorString; | ||
104 | QDateTime m_lastServerTimestamp; | ||
105 | bool m_lastRequestCached; | ||
106 | QNetworkProxy m_proxy; | ||
107 | }; | ||
108 | |||
109 | |||
110 | #endif | ||
111 | |||
diff --git a/utils/rbutilqt/base/mspackutil.cpp b/utils/rbutilqt/base/mspackutil.cpp new file mode 100644 index 0000000000..b794272199 --- /dev/null +++ b/utils/rbutilqt/base/mspackutil.cpp | |||
@@ -0,0 +1,164 @@ | |||
1 | /*************************************************************************** | ||
2 | * __________ __ ___. | ||
3 | * Open \______ \ ____ ____ | | _\_ |__ _______ ___ | ||
4 | * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / | ||
5 | * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < | ||
6 | * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ | ||
7 | * \/ \/ \/ \/ \/ | ||
8 | * | ||
9 | * Copyright (C) 2013 Amaury Pouly | ||
10 | * | ||
11 | * All files in this archive are subject to the GNU General Public License. | ||
12 | * See the file COPYING in the source tree root for full license agreement. | ||
13 | * | ||
14 | * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY | ||
15 | * KIND, either express or implied. | ||
16 | * | ||
17 | ****************************************************************************/ | ||
18 | |||
19 | #include <QtCore> | ||
20 | #include "Logger.h" | ||
21 | #include "mspackutil.h" | ||
22 | #include "progressloggerinterface.h" | ||
23 | |||
24 | MsPackUtil::MsPackUtil(QObject* parent) | ||
25 | :ArchiveUtil(parent) | ||
26 | { | ||
27 | m_cabd = mspack_create_cab_decompressor(nullptr); | ||
28 | m_cabinet = nullptr; | ||
29 | if(!m_cabd) | ||
30 | LOG_ERROR() << "CAB decompressor creation failed!"; | ||
31 | } | ||
32 | |||
33 | MsPackUtil::~MsPackUtil() | ||
34 | { | ||
35 | close(); | ||
36 | if(m_cabd) | ||
37 | mspack_destroy_cab_decompressor(m_cabd); | ||
38 | } | ||
39 | |||
40 | bool MsPackUtil::open(QString& mspackfile) | ||
41 | { | ||
42 | close(); | ||
43 | |||
44 | if(m_cabd == nullptr) | ||
45 | { | ||
46 | LOG_ERROR() << "No CAB decompressor available: cannot open file!"; | ||
47 | return false; | ||
48 | } | ||
49 | m_cabinet = m_cabd->search(m_cabd, QFile::encodeName(mspackfile).constData()); | ||
50 | return m_cabinet != nullptr; | ||
51 | } | ||
52 | |||
53 | bool MsPackUtil::close(void) | ||
54 | { | ||
55 | if(m_cabd && m_cabinet) | ||
56 | m_cabd->close(m_cabd, m_cabinet); | ||
57 | m_cabinet = nullptr; | ||
58 | return true; | ||
59 | } | ||
60 | |||
61 | bool MsPackUtil::extractArchive(const QString& dest, QString file) | ||
62 | { | ||
63 | LOG_INFO() << "extractArchive" << dest << file; | ||
64 | if(!m_cabinet) | ||
65 | { | ||
66 | LOG_ERROR() << "CAB file not open!"; | ||
67 | return false; | ||
68 | } | ||
69 | |||
70 | // construct the filename when extracting a single file from an archive. | ||
71 | // if the given destination is a full path use it as output name, | ||
72 | // otherwise use it as path to place the file as named in the archive. | ||
73 | QString singleoutfile; | ||
74 | if(!file.isEmpty() && QFileInfo(dest).isDir()) | ||
75 | singleoutfile = dest + "/" + file; | ||
76 | else if(!file.isEmpty()) | ||
77 | singleoutfile = dest; | ||
78 | struct mscabd_file *f = m_cabinet->files; | ||
79 | if(f == nullptr) | ||
80 | { | ||
81 | LOG_WARNING() << "CAB doesn't contain file" << file; | ||
82 | return true; | ||
83 | } | ||
84 | bool found = false; | ||
85 | while(f) | ||
86 | { | ||
87 | QString name = QFile::decodeName(f->filename); | ||
88 | name.replace("\\", "/"); | ||
89 | if(name.at(0) == '/') | ||
90 | name.remove(0, 1); | ||
91 | if(name == file || file.isEmpty()) | ||
92 | { | ||
93 | QString path; | ||
94 | if(!singleoutfile.isEmpty()) | ||
95 | path = singleoutfile; | ||
96 | else | ||
97 | path = dest + "/" + name; | ||
98 | // make sure the output path exists | ||
99 | if(!QDir().mkpath(QFileInfo(path).absolutePath())) | ||
100 | { | ||
101 | emit logItem(tr("Creating output path failed"), LOGERROR); | ||
102 | LOG_ERROR() << "creating output path failed for:" << path; | ||
103 | emit logProgress(1, 1); | ||
104 | return false; | ||
105 | } | ||
106 | int ret = m_cabd->extract(m_cabd, f, QFile::encodeName(path).constData()); | ||
107 | if(ret != MSPACK_ERR_OK) | ||
108 | { | ||
109 | emit logItem(tr("Error during CAB operation"), LOGERROR); | ||
110 | LOG_ERROR() << "mspack error: " << ret | ||
111 | << "(" << errorStringMsPack(ret) << ")"; | ||
112 | emit logProgress(1, 1); | ||
113 | return false; | ||
114 | } | ||
115 | found = true; | ||
116 | } | ||
117 | f = f->next; | ||
118 | } | ||
119 | emit logProgress(1, 1); | ||
120 | |||
121 | return found; | ||
122 | } | ||
123 | |||
124 | QStringList MsPackUtil::files(void) | ||
125 | { | ||
126 | QStringList list; | ||
127 | if(!m_cabinet) | ||
128 | { | ||
129 | LOG_WARNING() << "CAB file not open!"; | ||
130 | return list; | ||
131 | } | ||
132 | struct mscabd_file *file = m_cabinet->files; | ||
133 | while(file) | ||
134 | { | ||
135 | QString name = QFile::decodeName(file->filename); | ||
136 | name.replace("\\", "/"); | ||
137 | if(name.at(0) == '/') | ||
138 | name.remove(0, 1); | ||
139 | list.append(name); | ||
140 | file = file->next; | ||
141 | } | ||
142 | |||
143 | return list; | ||
144 | } | ||
145 | |||
146 | QString MsPackUtil::errorStringMsPack(int error) const | ||
147 | { | ||
148 | switch(error) | ||
149 | { | ||
150 | case MSPACK_ERR_OK: return "Ok"; | ||
151 | case MSPACK_ERR_ARGS: return "Bad arguments"; | ||
152 | case MSPACK_ERR_OPEN: return "Open error"; | ||
153 | case MSPACK_ERR_READ: return "Read error"; | ||
154 | case MSPACK_ERR_WRITE: return "Write error"; | ||
155 | case MSPACK_ERR_SEEK: return "Seek error"; | ||
156 | case MSPACK_ERR_NOMEMORY: return "Out of memory"; | ||
157 | case MSPACK_ERR_SIGNATURE: return "Bad signature"; | ||
158 | case MSPACK_ERR_DATAFORMAT: return "Bad data format"; | ||
159 | case MSPACK_ERR_CHECKSUM: return "Checksum error"; | ||
160 | case MSPACK_ERR_CRUNCH: return "Compression error"; | ||
161 | case MSPACK_ERR_DECRUNCH: return "Decompression error"; | ||
162 | default: return "Unknown"; | ||
163 | } | ||
164 | } | ||
diff --git a/utils/rbutilqt/base/mspackutil.h b/utils/rbutilqt/base/mspackutil.h new file mode 100644 index 0000000000..1cb4350b13 --- /dev/null +++ b/utils/rbutilqt/base/mspackutil.h | |||
@@ -0,0 +1,51 @@ | |||
1 | /*************************************************************************** | ||
2 | * __________ __ ___. | ||
3 | * Open \______ \ ____ ____ | | _\_ |__ _______ ___ | ||
4 | * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / | ||
5 | * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < | ||
6 | * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ | ||
7 | * \/ \/ \/ \/ \/ | ||
8 | * | ||
9 | * Copyright (C) 2013 Amaury Pouly | ||
10 | * | ||
11 | * All files in this archive are subject to the GNU General Public License. | ||
12 | * See the file COPYING in the source tree root for full license agreement. | ||
13 | * | ||
14 | * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY | ||
15 | * KIND, either express or implied. | ||
16 | * | ||
17 | ****************************************************************************/ | ||
18 | |||
19 | #ifndef MSPACKUTIL_H | ||
20 | #define MSPACKUTIL_H | ||
21 | |||
22 | #include <QtCore> | ||
23 | #include "archiveutil.h" | ||
24 | #include "mspack/mspack.h" | ||
25 | |||
26 | class MsPackUtil : public ArchiveUtil | ||
27 | { | ||
28 | Q_OBJECT | ||
29 | |||
30 | public: | ||
31 | // archive types can be ORed | ||
32 | MsPackUtil(QObject* parent); | ||
33 | ~MsPackUtil(); | ||
34 | bool open(QString& mspackfile); | ||
35 | virtual bool close(void); | ||
36 | virtual bool extractArchive(const QString& dest, QString file = ""); | ||
37 | virtual QStringList files(void); | ||
38 | |||
39 | signals: | ||
40 | void logProgress(int, int); | ||
41 | void logItem(QString, int); | ||
42 | |||
43 | private: | ||
44 | QString errorStringMsPack(int error) const; | ||
45 | struct mscab_decompressor* m_cabd; | ||
46 | struct mscabd_cabinet *m_cabinet; | ||
47 | |||
48 | }; | ||
49 | #endif | ||
50 | |||
51 | |||
diff --git a/utils/rbutilqt/base/playerbuildinfo.cpp b/utils/rbutilqt/base/playerbuildinfo.cpp new file mode 100644 index 0000000000..f118a9fd7a --- /dev/null +++ b/utils/rbutilqt/base/playerbuildinfo.cpp | |||
@@ -0,0 +1,362 @@ | |||
1 | /*************************************************************************** | ||
2 | * __________ __ ___. | ||
3 | * Open \______ \ ____ ____ | | _\_ |__ _______ ___ | ||
4 | * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / | ||
5 | * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < | ||
6 | * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ | ||
7 | * \/ \/ \/ \/ \/ | ||
8 | * | ||
9 | * Copyright (C) 2020 by Dominik Riebeling | ||
10 | * | ||
11 | * All files in this archive are subject to the GNU General Public License. | ||
12 | * See the file COPYING in the source tree root for full license agreement. | ||
13 | * | ||
14 | * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY | ||
15 | * KIND, either express or implied. | ||
16 | * | ||
17 | ****************************************************************************/ | ||
18 | |||
19 | #include "playerbuildinfo.h" | ||
20 | #include "rbsettings.h" | ||
21 | #include "Logger.h" | ||
22 | |||
23 | PlayerBuildInfo* PlayerBuildInfo::infoInstance = nullptr; | ||
24 | |||
25 | PlayerBuildInfo* PlayerBuildInfo::instance() | ||
26 | { | ||
27 | if (infoInstance == nullptr) { | ||
28 | infoInstance = new PlayerBuildInfo(); | ||
29 | } | ||
30 | return infoInstance; | ||
31 | } | ||
32 | |||
33 | // server infos | ||
34 | const static struct { | ||
35 | PlayerBuildInfo::BuildInfo item; | ||
36 | const char* name; | ||
37 | } ServerInfoList[] = { | ||
38 | { PlayerBuildInfo::BuildVoiceLangs, "voices/:version:" }, | ||
39 | { PlayerBuildInfo::BuildVersion, ":build:/:target:" }, | ||
40 | { PlayerBuildInfo::BuildUrl, ":build:/build_url" }, | ||
41 | { PlayerBuildInfo::BuildVoiceUrl, ":build:/voice_url" }, | ||
42 | { PlayerBuildInfo::BuildManualUrl, ":build:/manual_url" }, | ||
43 | { PlayerBuildInfo::BuildSourceUrl, ":build:/source_url" }, | ||
44 | { PlayerBuildInfo::BuildFontUrl, ":build:/font_url" }, | ||
45 | |||
46 | // other URLs -- those are not directly related to the build, but handled here. | ||
47 | { PlayerBuildInfo::DoomUrl, "other/doom_url" }, | ||
48 | { PlayerBuildInfo::Duke3DUrl, "other/duke3d_url" }, | ||
49 | { PlayerBuildInfo::PuzzFontsUrl, "other/puzzfonts_url" }, | ||
50 | { PlayerBuildInfo::QuakeUrl, "other/quake_url" }, | ||
51 | { PlayerBuildInfo::Wolf3DUrl, "other/wolf3d_url" }, | ||
52 | { PlayerBuildInfo::XWorldUrl, "other/xworld_url" }, | ||
53 | { PlayerBuildInfo::MidiPatchsetUrl, "other/patcheset_url" }, | ||
54 | }; | ||
55 | |||
56 | const static struct { | ||
57 | PlayerBuildInfo::DeviceInfo item; | ||
58 | const char* name; | ||
59 | } PlayerInfoList[] = { | ||
60 | { PlayerBuildInfo::BuildStatus, "status/:target:" }, | ||
61 | { PlayerBuildInfo::DisplayName, ":target:/name" }, | ||
62 | { PlayerBuildInfo::BootloaderMethod, ":target:/bootloadermethod" }, | ||
63 | { PlayerBuildInfo::BootloaderName, ":target:/bootloadername" }, | ||
64 | { PlayerBuildInfo::BootloaderFile, ":target:/bootloaderfile" }, | ||
65 | { PlayerBuildInfo::BootloaderFilter, ":target:/bootloaderfilter" }, | ||
66 | { PlayerBuildInfo::Encoder, ":target:/encoder" }, | ||
67 | { PlayerBuildInfo::Brand, ":target:/brand" }, | ||
68 | { PlayerBuildInfo::PlayerPicture, ":target:/playerpic" }, | ||
69 | { PlayerBuildInfo::TargetNamesAll, "_targets/all" }, | ||
70 | { PlayerBuildInfo::TargetNamesEnabled, "_targets/enabled" }, | ||
71 | { PlayerBuildInfo::LanguageInfo, "languages/:target:" }, | ||
72 | { PlayerBuildInfo::LanguageList, "_languages/list" }, | ||
73 | { PlayerBuildInfo::UsbIdErrorList, "_usb/error" }, | ||
74 | { PlayerBuildInfo::UsbIdTargetList, "_usb/target" }, | ||
75 | }; | ||
76 | |||
77 | const static struct { | ||
78 | PlayerBuildInfo::SystemUrl item; | ||
79 | const char* name; | ||
80 | } PlayerSystemUrls[] = { | ||
81 | { PlayerBuildInfo::BootloaderUrl, "bootloader/download_url" }, | ||
82 | { PlayerBuildInfo::BuildInfoUrl, "build_info_url" }, | ||
83 | { PlayerBuildInfo::GenlangUrl, "genlang_url" }, | ||
84 | { PlayerBuildInfo::ThemesUrl, "themes_url" }, | ||
85 | { PlayerBuildInfo::ThemesInfoUrl, "themes_info_url" }, | ||
86 | { PlayerBuildInfo::RbutilUrl, "rbutil_url" }, | ||
87 | }; | ||
88 | |||
89 | PlayerBuildInfo::PlayerBuildInfo() : | ||
90 | serverInfo(nullptr), | ||
91 | playerInfo(":/ini/rbutil.ini", QSettings::IniFormat) | ||
92 | { | ||
93 | |||
94 | } | ||
95 | |||
96 | void PlayerBuildInfo::setBuildInfo(QString file) | ||
97 | { | ||
98 | if (serverInfo) | ||
99 | delete serverInfo; | ||
100 | LOG_INFO() << "updated:" << file; | ||
101 | serverInfo = new QSettings(file, QSettings::IniFormat); | ||
102 | } | ||
103 | |||
104 | QVariant PlayerBuildInfo::value(BuildInfo item, BuildType type) | ||
105 | { | ||
106 | // locate setting item in server info file | ||
107 | int i = 0; | ||
108 | while(ServerInfoList[i].item != item) | ||
109 | i++; | ||
110 | |||
111 | // split of variant for target. | ||
112 | // we can have an optional variant part in the target string. | ||
113 | // For build info we don't use that. | ||
114 | QString target = RbSettings::value(RbSettings::CurrentPlatform).toString().split('.').at(0); | ||
115 | |||
116 | QString s = ServerInfoList[i].name; | ||
117 | s.replace(":target:", target); | ||
118 | QString v; | ||
119 | switch(type) { | ||
120 | case TypeRelease: | ||
121 | v = "release"; | ||
122 | break; | ||
123 | case TypeCandidate: | ||
124 | v = "release-candidate"; | ||
125 | break; | ||
126 | case TypeDaily: | ||
127 | v = "daily"; | ||
128 | break; | ||
129 | case TypeDevel: | ||
130 | v = "development"; | ||
131 | break; | ||
132 | } | ||
133 | |||
134 | QVariant result = QString(); | ||
135 | if (!serverInfo) | ||
136 | return result; | ||
137 | QStringList version = serverInfo->value(v + "/" + target, "").toStringList(); | ||
138 | s.replace(":build:", v); | ||
139 | s.replace(":version:", version.at(0)); | ||
140 | |||
141 | // get value from server build-info | ||
142 | // we need to get a version string, otherwise the data is invalid. | ||
143 | // For invalid data return an empty string. | ||
144 | if(version.at(0).isEmpty()) { | ||
145 | LOG_INFO() << s << "(version invalid)"; | ||
146 | return result; | ||
147 | } | ||
148 | if(!s.isEmpty()) | ||
149 | result = serverInfo->value(s); | ||
150 | |||
151 | // depending on the actual value we need more replacements. | ||
152 | switch(item) { | ||
153 | case BuildVersion: | ||
154 | result = result.toStringList().at(0); | ||
155 | break; | ||
156 | |||
157 | case BuildUrl: | ||
158 | if(version.size() > 1) { | ||
159 | // version info has an URL appended. Takes precendence. | ||
160 | result = version.at(1); | ||
161 | } | ||
162 | break; | ||
163 | |||
164 | case BuildVoiceLangs: | ||
165 | if (type == TypeDaily) | ||
166 | s = "voices/daily"; | ||
167 | result = serverInfo->value(s); | ||
168 | break; | ||
169 | |||
170 | case BuildManualUrl: | ||
171 | { | ||
172 | // special case: if playerInfo has a non-empty manualname entry for the | ||
173 | // target, use that as target for the manual name. | ||
174 | QString manualtarget = playerInfo.value(target + "/manualname", "").toString(); | ||
175 | if(!manualtarget.isEmpty()) | ||
176 | target = manualtarget; | ||
177 | break; | ||
178 | } | ||
179 | |||
180 | default: | ||
181 | break; | ||
182 | } | ||
183 | // if the value is a string we can replace some patterns. | ||
184 | // if we cannot convert it (f.e. for a QStringList) we leave as-is, since | ||
185 | // the conversion would return an empty type. | ||
186 | if (result.canConvert(QMetaType::QString)) | ||
187 | result = result.toString() | ||
188 | .replace("%TARGET%", target) | ||
189 | .replace("%VERSION%", version.at(0)); | ||
190 | |||
191 | LOG_INFO() << "B:" << s << result; | ||
192 | return result; | ||
193 | } | ||
194 | |||
195 | QVariant PlayerBuildInfo::value(DeviceInfo item, QString target) | ||
196 | { | ||
197 | // locate setting item in server info file | ||
198 | int i = 0; | ||
199 | while(PlayerInfoList[i].item != item) | ||
200 | i++; | ||
201 | |||
202 | // split of variant for target. | ||
203 | // we can have an optional variant part in the target string. | ||
204 | // For device info we use this. | ||
205 | if (target.isEmpty()) | ||
206 | target = RbSettings::value(RbSettings::CurrentPlatform).toString(); | ||
207 | |||
208 | QVariant result = QString(); | ||
209 | |||
210 | QString s = PlayerInfoList[i].name; | ||
211 | s.replace(":target:", target); | ||
212 | |||
213 | switch(item) { | ||
214 | case BuildStatus: | ||
215 | { | ||
216 | // build status is the only value that doesn't depend on the version | ||
217 | // but the selected target instead. | ||
218 | bool ok = false; | ||
219 | if (serverInfo) | ||
220 | result = serverInfo->value(s).toInt(&ok); | ||
221 | if (!ok) | ||
222 | result = -1; | ||
223 | break; | ||
224 | } | ||
225 | case TargetNamesAll: | ||
226 | // list of all internal target names. Doesn't depend on the passed target. | ||
227 | result = targetNames(true); | ||
228 | break; | ||
229 | case TargetNamesEnabled: | ||
230 | // list of all non-disabled target names. Doesn't depend on the passed target. | ||
231 | result = targetNames(false); | ||
232 | break; | ||
233 | |||
234 | case LanguageList: | ||
235 | // Return a map (language, display string). | ||
236 | { | ||
237 | // need to use (QString, QVariant) here, so we can put the map into | ||
238 | // a QVariant by itself. | ||
239 | QMap<QString, QVariant> m; | ||
240 | |||
241 | playerInfo.beginGroup("languages"); | ||
242 | QStringList a = playerInfo.childKeys(); | ||
243 | |||
244 | for(int i = 0; i < a.size(); i++) { | ||
245 | QStringList v = playerInfo.value(a.at(i)).toStringList(); | ||
246 | m[v.at(0)] = v.at(1); | ||
247 | } | ||
248 | playerInfo.endGroup(); | ||
249 | result = m; | ||
250 | } | ||
251 | break; | ||
252 | |||
253 | default: | ||
254 | result = playerInfo.value(s); | ||
255 | break; | ||
256 | } | ||
257 | |||
258 | LOG_INFO() << "T:" << s << result; | ||
259 | return result; | ||
260 | } | ||
261 | |||
262 | QVariant PlayerBuildInfo::value(DeviceInfo item, unsigned int match) | ||
263 | { | ||
264 | QStringList result; | ||
265 | int i = 0; | ||
266 | while(PlayerInfoList[i].item != item) | ||
267 | i++; | ||
268 | QString s = PlayerInfoList[i].name; | ||
269 | |||
270 | switch(item) { | ||
271 | case UsbIdErrorList: | ||
272 | { | ||
273 | // go through all targets and find the one indicated by the usb id "target". | ||
274 | // return list of matching players (since it could be more than one) | ||
275 | QStringList targets = targetNames(true); | ||
276 | for(int i = 0; i < targets.size(); i++) { | ||
277 | QStringList usbids = playerInfo.value(targets.at(i) + "/usberror").toStringList(); | ||
278 | for(int j = 0; j < usbids.size(); j++) { | ||
279 | if(usbids.at(j).toUInt(nullptr, 0) == match) { | ||
280 | result << targets.at(i); | ||
281 | } | ||
282 | } | ||
283 | } | ||
284 | break; | ||
285 | } | ||
286 | |||
287 | case UsbIdTargetList: | ||
288 | { | ||
289 | QStringList targets = targetNames(true); | ||
290 | for(int i = 0; i < targets.size(); i++) { | ||
291 | QStringList usbids = playerInfo.value(targets.at(i) + "/usbid").toStringList(); | ||
292 | for(int j = 0; j < usbids.size(); j++) { | ||
293 | if(usbids.at(j).toUInt(nullptr, 0) == match) { | ||
294 | result << targets.at(i); | ||
295 | } | ||
296 | } | ||
297 | } | ||
298 | break; | ||
299 | } | ||
300 | |||
301 | default: | ||
302 | break; | ||
303 | } | ||
304 | LOG_INFO() << "T:" << s << result; | ||
305 | return result; | ||
306 | } | ||
307 | |||
308 | QVariant PlayerBuildInfo::value(SystemUrl item) | ||
309 | { | ||
310 | // locate setting item in server info file | ||
311 | int i = 0; | ||
312 | while(PlayerSystemUrls[i].item != item) | ||
313 | i++; | ||
314 | |||
315 | QVariant result = playerInfo.value(PlayerSystemUrls[i].name); | ||
316 | LOG_INFO() << "U:" << PlayerSystemUrls[i].name << result; | ||
317 | return result; | ||
318 | } | ||
319 | |||
320 | |||
321 | QString PlayerBuildInfo::statusAsString(QString platform) | ||
322 | { | ||
323 | QString result; | ||
324 | switch(value(BuildStatus, platform).toInt()) | ||
325 | { | ||
326 | case STATUS_RETIRED: | ||
327 | result = tr("Stable (Retired)"); | ||
328 | break; | ||
329 | case STATUS_UNUSABLE: | ||
330 | result = tr("Unusable"); | ||
331 | break; | ||
332 | case STATUS_UNSTABLE: | ||
333 | result = tr("Unstable"); | ||
334 | break; | ||
335 | case STATUS_STABLE: | ||
336 | result = tr("Stable"); | ||
337 | break; | ||
338 | default: | ||
339 | result = tr("Unknown"); | ||
340 | break; | ||
341 | } | ||
342 | |||
343 | return result; | ||
344 | } | ||
345 | |||
346 | |||
347 | QStringList PlayerBuildInfo::targetNames(bool all) | ||
348 | { | ||
349 | QStringList result; | ||
350 | playerInfo.beginGroup("platforms"); | ||
351 | QStringList a = playerInfo.childKeys(); | ||
352 | playerInfo.endGroup(); | ||
353 | for(int i = 0; i < a.size(); i++) | ||
354 | { | ||
355 | QString target = playerInfo.value("platforms/" + a.at(i), "null").toString(); | ||
356 | if(playerInfo.value(target + "/status").toString() != "disabled" || all) { | ||
357 | result.append(target); | ||
358 | } | ||
359 | } | ||
360 | return result; | ||
361 | } | ||
362 | |||
diff --git a/utils/rbutilqt/base/playerbuildinfo.h b/utils/rbutilqt/base/playerbuildinfo.h new file mode 100644 index 0000000000..6a88f750ed --- /dev/null +++ b/utils/rbutilqt/base/playerbuildinfo.h | |||
@@ -0,0 +1,123 @@ | |||
1 | /*************************************************************************** | ||
2 | * __________ __ ___. | ||
3 | * Open \______ \ ____ ____ | | _\_ |__ _______ ___ | ||
4 | * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / | ||
5 | * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < | ||
6 | * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ | ||
7 | * \/ \/ \/ \/ \/ | ||
8 | * | ||
9 | * Copyright (C) 2020 by Dominik Riebeling | ||
10 | * | ||
11 | * All files in this archive are subject to the GNU General Public License. | ||
12 | * See the file COPYING in the source tree root for full license agreement. | ||
13 | * | ||
14 | * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY | ||
15 | * KIND, either express or implied. | ||
16 | * | ||
17 | ****************************************************************************/ | ||
18 | |||
19 | #ifndef PLAYERBUILDINFO_H | ||
20 | #define PLAYERBUILDINFO_H | ||
21 | |||
22 | #include <QSettings> | ||
23 | |||
24 | #define STATUS_RETIRED 0 | ||
25 | #define STATUS_UNUSABLE 1 | ||
26 | #define STATUS_UNSTABLE 2 | ||
27 | #define STATUS_STABLE 3 | ||
28 | |||
29 | // Provide information about both player and builds. | ||
30 | // For build info data retrieved from the build server has to be passed. | ||
31 | class PlayerBuildInfo : public QObject | ||
32 | { | ||
33 | Q_OBJECT | ||
34 | |||
35 | public: | ||
36 | |||
37 | enum BuildType { | ||
38 | TypeRelease, | ||
39 | TypeCandidate, | ||
40 | TypeDaily, | ||
41 | TypeDevel, | ||
42 | }; | ||
43 | enum BuildInfo { | ||
44 | BuildUrl, | ||
45 | BuildVersion, | ||
46 | BuildManualUrl, | ||
47 | BuildVoiceUrl, | ||
48 | BuildVoiceLangs, | ||
49 | BuildSourceUrl, | ||
50 | BuildFontUrl, | ||
51 | |||
52 | DoomUrl, | ||
53 | Duke3DUrl, | ||
54 | PuzzFontsUrl, | ||
55 | QuakeUrl, | ||
56 | Wolf3DUrl, | ||
57 | XWorldUrl, | ||
58 | MidiPatchsetUrl, | ||
59 | }; | ||
60 | enum DeviceInfo { | ||
61 | BuildStatus, | ||
62 | |||
63 | DisplayName, | ||
64 | BootloaderMethod, | ||
65 | BootloaderName, | ||
66 | BootloaderFile, | ||
67 | BootloaderFilter, | ||
68 | Encoder, | ||
69 | Brand, | ||
70 | PlayerPicture, | ||
71 | |||
72 | TargetNamesAll, | ||
73 | TargetNamesEnabled, | ||
74 | LanguageInfo, | ||
75 | LanguageList, | ||
76 | UsbIdErrorList, | ||
77 | UsbIdTargetList, | ||
78 | }; | ||
79 | |||
80 | enum SystemUrl { | ||
81 | BootloaderUrl, | ||
82 | BuildInfoUrl, | ||
83 | GenlangUrl, | ||
84 | ThemesUrl, | ||
85 | ThemesInfoUrl, | ||
86 | RbutilUrl, | ||
87 | }; | ||
88 | |||
89 | static PlayerBuildInfo* instance(); | ||
90 | |||
91 | //! Update with build information from server | ||
92 | void setBuildInfo(QString file); | ||
93 | |||
94 | // Get information about a device. This data does not depend on the build type. | ||
95 | QVariant value(DeviceInfo item, QString target = ""); | ||
96 | |||
97 | // Get information about a device. Make a numeric match | ||
98 | // (only sensible implementation for USB IDs) | ||
99 | QVariant value(DeviceInfo item, unsigned int match); | ||
100 | |||
101 | // Get build information for currently selected player. | ||
102 | QVariant value(BuildInfo item, BuildType type); | ||
103 | |||
104 | // Get fixed download URL information | ||
105 | QVariant value(SystemUrl item); | ||
106 | |||
107 | QString statusAsString(QString target = ""); | ||
108 | |||
109 | protected: | ||
110 | explicit PlayerBuildInfo(); | ||
111 | |||
112 | private: | ||
113 | //! Return a list with all target names (as used internally). | ||
114 | //! @all false filter out all targets with status = disabled. | ||
115 | QStringList targetNames(bool all); | ||
116 | |||
117 | static PlayerBuildInfo* infoInstance; | ||
118 | QSettings* serverInfo; | ||
119 | QSettings playerInfo; | ||
120 | |||
121 | }; | ||
122 | |||
123 | #endif | ||
diff --git a/utils/rbutilqt/base/progressloggerinterface.h b/utils/rbutilqt/base/progressloggerinterface.h new file mode 100644 index 0000000000..ae4270ae6b --- /dev/null +++ b/utils/rbutilqt/base/progressloggerinterface.h | |||
@@ -0,0 +1,60 @@ | |||
1 | /*************************************************************************** | ||
2 | * __________ __ ___. | ||
3 | * Open \______ \ ____ ____ | | _\_ |__ _______ ___ | ||
4 | * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / | ||
5 | * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < | ||
6 | * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ | ||
7 | * \/ \/ \/ \/ \/ | ||
8 | * | ||
9 | * Copyright (C) 2007 by Dominik Wenger | ||
10 | * | ||
11 | * This program is free software; you can redistribute it and/or | ||
12 | * modify it under the terms of the GNU General Public License | ||
13 | * as published by the Free Software Foundation; either version 2 | ||
14 | * of the License, or (at your option) any later version. | ||
15 | * | ||
16 | * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY | ||
17 | * KIND, either express or implied. | ||
18 | * | ||
19 | ****************************************************************************/ | ||
20 | |||
21 | #ifndef PROGRESSLOGGERINTERFACE_H | ||
22 | #define PROGRESSLOGGERINTERFACE_H | ||
23 | |||
24 | #include <QtCore> | ||
25 | |||
26 | enum { | ||
27 | LOGNOICON, LOGOK, LOGINFO, LOGWARNING, LOGERROR | ||
28 | }; | ||
29 | |||
30 | |||
31 | |||
32 | class ProgressloggerInterface : public QObject | ||
33 | { | ||
34 | Q_OBJECT | ||
35 | |||
36 | public: | ||
37 | ProgressloggerInterface(QObject* parent) : QObject(parent) {} | ||
38 | |||
39 | virtual void setProgressValue(int value)=0; | ||
40 | virtual void setProgressMax(int max)=0; | ||
41 | virtual int getProgressMax()=0; | ||
42 | |||
43 | signals: | ||
44 | void aborted(); | ||
45 | |||
46 | |||
47 | public slots: | ||
48 | virtual void addItem(const QString &text, int flag)=0; //! add a string to the list, with icon | ||
49 | |||
50 | virtual void close()=0; | ||
51 | virtual void show()=0; | ||
52 | virtual void setRunning()=0; | ||
53 | virtual void setFinished()=0; | ||
54 | |||
55 | private: | ||
56 | |||
57 | }; | ||
58 | |||
59 | #endif | ||
60 | |||
diff --git a/utils/rbutilqt/base/rbsettings.cpp b/utils/rbutilqt/base/rbsettings.cpp new file mode 100644 index 0000000000..8eaa01f7f6 --- /dev/null +++ b/utils/rbutilqt/base/rbsettings.cpp | |||
@@ -0,0 +1,207 @@ | |||
1 | /*************************************************************************** | ||
2 | * __________ __ ___. | ||
3 | * Open \______ \ ____ ____ | | _\_ |__ _______ ___ | ||
4 | * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / | ||
5 | * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < | ||
6 | * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ | ||
7 | * \/ \/ \/ \/ \/ | ||
8 | * | ||
9 | * Copyright (C) 2007 by Dominik Wenger | ||
10 | * | ||
11 | * All files in this archive are subject to the GNU General Public License. | ||
12 | * See the file COPYING in the source tree root for full license agreement. | ||
13 | * | ||
14 | * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY | ||
15 | * KIND, either express or implied. | ||
16 | * | ||
17 | ****************************************************************************/ | ||
18 | |||
19 | #include "rbsettings.h" | ||
20 | #include "playerbuildinfo.h" | ||
21 | #include <QSettings> | ||
22 | #include "Logger.h" | ||
23 | |||
24 | #if defined(Q_OS_LINUX) | ||
25 | #include <unistd.h> | ||
26 | #endif | ||
27 | |||
28 | // user settings | ||
29 | const static struct { | ||
30 | RbSettings::UserSettings setting; | ||
31 | const char* name; | ||
32 | const char* def; | ||
33 | } UserSettingsList[] = { | ||
34 | { RbSettings::RbutilVersion, "rbutil_version", "" }, | ||
35 | { RbSettings::ShowChangelog, "show_changelog", "false" }, | ||
36 | { RbSettings::CurrentPlatform, "platform", "" }, | ||
37 | { RbSettings::Mountpoint, "mountpoint", "" }, | ||
38 | { RbSettings::CachePath, "cachepath", "" }, | ||
39 | { RbSettings::Build, "build", "" }, | ||
40 | { RbSettings::ProxyType, "proxytype", "" }, | ||
41 | { RbSettings::Proxy, "proxy", "" }, | ||
42 | { RbSettings::OfPath, "ofpath", "" }, | ||
43 | { RbSettings::Platform, "platform", "" }, | ||
44 | { RbSettings::Language, "lang", "" }, | ||
45 | { RbSettings::BackupPath, "backuppath", "" }, | ||
46 | { RbSettings::InstallRockbox, "install_rockbox", "true" }, | ||
47 | { RbSettings::InstallFonts, "install_fonts", "true" }, | ||
48 | { RbSettings::InstallThemes, "install_themes", "false" }, | ||
49 | { RbSettings::InstallPluginData, "install_plugin_data", "true" }, | ||
50 | { RbSettings::InstallVoice, "install_voice", "false" }, | ||
51 | { RbSettings::InstallManual, "install_manual", "false" }, | ||
52 | #if defined(Q_OS_WIN32) | ||
53 | { RbSettings::Tts, "tts", "sapi" }, | ||
54 | #elif defined(Q_OS_MACX) | ||
55 | { RbSettings::Tts, "tts", "carbon" }, | ||
56 | #else | ||
57 | { RbSettings::Tts, "tts", "espeak" }, | ||
58 | #endif | ||
59 | { RbSettings::UseTtsCorrections, "use_tts_corrections", "true" }, | ||
60 | { RbSettings::TalkFolders, "talk_folders", "" }, | ||
61 | { RbSettings::TalkProcessFiles, "talk_process_files", "true" }, | ||
62 | { RbSettings::TalkProcessFolders, "talk_process_folders", "true" }, | ||
63 | { RbSettings::TalkRecursive, "talk_recursive", "true" }, | ||
64 | { RbSettings::TalkSkipExisting, "talk_skip_existing", "true" }, | ||
65 | { RbSettings::TalkStripExtensions, "talk_strip_extensions","true" }, | ||
66 | { RbSettings::TalkIgnoreFiles, "talk_ignore_files", "false" }, | ||
67 | { RbSettings::TalkIgnoreWildcards, "talk_ignore_wildcards","" }, | ||
68 | { RbSettings::VoiceLanguage, "voicelanguage", "" }, | ||
69 | { RbSettings::TtsLanguage, ":tts:/language", "" }, | ||
70 | { RbSettings::TtsOptions, ":tts:/options", "" }, | ||
71 | { RbSettings::TtsPitch, ":tts:/pitch", "0" }, | ||
72 | { RbSettings::TtsPath, ":tts:/path", "" }, | ||
73 | { RbSettings::TtsVoice, ":tts:/voice", "" }, | ||
74 | { RbSettings::EncoderPath, ":encoder:/encoderpath", "" }, | ||
75 | { RbSettings::EncoderOptions, ":encoder:/encoderoptions", "" }, | ||
76 | { RbSettings::CacheDisabled, "cachedisable", "false" }, | ||
77 | { RbSettings::TtsUseSapi4, "sapi/useSapi4", "false" }, | ||
78 | { RbSettings::EncoderNarrowBand, ":encoder:/narrowband", "false" }, | ||
79 | { RbSettings::WavtrimThreshold, "wavtrimthreshold", "500"}, | ||
80 | { RbSettings::TtsSpeed, ":tts:/speed", "175" }, | ||
81 | { RbSettings::EncoderComplexity, ":encoder:/complexity", "10" }, | ||
82 | { RbSettings::EncoderQuality, ":encoder:/quality", "-1.0" }, | ||
83 | { RbSettings::EncoderVolume, ":encoder:/volume", "1.0" }, | ||
84 | }; | ||
85 | |||
86 | //! pointer to setting object to NULL | ||
87 | QSettings* RbSettings::userSettings = nullptr; | ||
88 | |||
89 | void RbSettings::ensureRbSettingsExists() | ||
90 | { | ||
91 | if(userSettings == nullptr) | ||
92 | { | ||
93 | // portable installation: | ||
94 | // check for a configuration file in the program folder. | ||
95 | QFileInfo config; | ||
96 | config.setFile(QCoreApplication::instance()->applicationDirPath() | ||
97 | + "/RockboxUtility.ini"); | ||
98 | if(config.isFile()) | ||
99 | { | ||
100 | userSettings = new QSettings(QCoreApplication::instance()->applicationDirPath() | ||
101 | + "/RockboxUtility.ini", QSettings::IniFormat, nullptr); | ||
102 | LOG_INFO() << "configuration: portable"; | ||
103 | } | ||
104 | else | ||
105 | { | ||
106 | userSettings = new QSettings(QSettings::IniFormat, | ||
107 | QSettings::UserScope, "rockbox.org", "RockboxUtility",nullptr); | ||
108 | LOG_INFO() << "configuration: system"; | ||
109 | } | ||
110 | } | ||
111 | } | ||
112 | |||
113 | void RbSettings::sync() | ||
114 | { | ||
115 | ensureRbSettingsExists(); | ||
116 | |||
117 | userSettings->sync(); | ||
118 | #if defined(Q_OS_LINUX) | ||
119 | // when using sudo it runs rbutil with uid 0 but unfortunately without a | ||
120 | // proper login shell, thus the configuration file gets placed in the | ||
121 | // calling users $HOME. This in turn will cause issues if trying to | ||
122 | // run rbutil later as user. Try to detect this case via the environment | ||
123 | // variable SUDO_UID and SUDO_GID and if set chown the user config file. | ||
124 | if(getuid() == 0) | ||
125 | { | ||
126 | char* realuser = getenv("SUDO_UID"); | ||
127 | char* realgroup = getenv("SUDO_GID"); | ||
128 | if(realuser != nullptr && realgroup != nullptr) | ||
129 | { | ||
130 | int realuid = atoi(realuser); | ||
131 | int realgid = atoi(realgroup); | ||
132 | // chown is attribute warn_unused_result, but in case this fails | ||
133 | // we can't do anything useful about it. Notifying the user | ||
134 | // is somewhat pointless. Add hack to suppress compiler warning. | ||
135 | if(chown(qPrintable(userSettings->fileName()), realuid, realgid)) | ||
136 | { } | ||
137 | } | ||
138 | } | ||
139 | #endif | ||
140 | } | ||
141 | |||
142 | QString RbSettings::userSettingFilename() | ||
143 | { | ||
144 | ensureRbSettingsExists(); | ||
145 | return userSettings->fileName(); | ||
146 | } | ||
147 | |||
148 | QVariant RbSettings::value(enum UserSettings setting) | ||
149 | { | ||
150 | QString empty; | ||
151 | return subValue(empty, setting); | ||
152 | } | ||
153 | |||
154 | QVariant RbSettings::subValue(QString sub, enum UserSettings setting) | ||
155 | { | ||
156 | ensureRbSettingsExists(); | ||
157 | |||
158 | // locate setting item | ||
159 | int i = 0; | ||
160 | while(UserSettingsList[i].setting != setting) | ||
161 | i++; | ||
162 | |||
163 | QString s = constructSettingPath(UserSettingsList[i].name, sub); | ||
164 | LOG_INFO() << "GET U:" << s << userSettings->value(s).toString(); | ||
165 | return userSettings->value(s, UserSettingsList[i].def); | ||
166 | } | ||
167 | |||
168 | void RbSettings::setValue(enum UserSettings setting , QVariant value) | ||
169 | { | ||
170 | QString empty; | ||
171 | return setSubValue(empty, setting, value); | ||
172 | } | ||
173 | |||
174 | void RbSettings::setSubValue(QString sub, enum UserSettings setting, QVariant value) | ||
175 | { | ||
176 | ensureRbSettingsExists(); | ||
177 | |||
178 | // locate setting item | ||
179 | int i = 0; | ||
180 | while(UserSettingsList[i].setting != setting) | ||
181 | i++; | ||
182 | |||
183 | QString s = constructSettingPath(UserSettingsList[i].name, sub); | ||
184 | userSettings->setValue(s, value); | ||
185 | LOG_INFO() << "SET U:" << s << userSettings->value(s).toString(); | ||
186 | } | ||
187 | |||
188 | QString RbSettings::constructSettingPath(QString path, QString substitute) | ||
189 | { | ||
190 | // anything to substitute? | ||
191 | if(path.contains(':')) { | ||
192 | QString platform = userSettings->value("platform").toString(); | ||
193 | if(!substitute.isEmpty()) { | ||
194 | path.replace(":tts:", substitute); | ||
195 | path.replace(":encoder:", substitute); | ||
196 | } | ||
197 | else { | ||
198 | path.replace(":tts:", userSettings->value("tts").toString()); | ||
199 | path.replace(":encoder:", PlayerBuildInfo::instance()->value( | ||
200 | PlayerBuildInfo::Encoder, platform).toString()); | ||
201 | } | ||
202 | path.replace(":platform:", platform); | ||
203 | } | ||
204 | |||
205 | return path; | ||
206 | } | ||
207 | |||
diff --git a/utils/rbutilqt/base/rbsettings.h b/utils/rbutilqt/base/rbsettings.h new file mode 100644 index 0000000000..ef0c4eb134 --- /dev/null +++ b/utils/rbutilqt/base/rbsettings.h | |||
@@ -0,0 +1,105 @@ | |||
1 | /*************************************************************************** | ||
2 | * __________ __ ___. | ||
3 | * Open \______ \ ____ ____ | | _\_ |__ _______ ___ | ||
4 | * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / | ||
5 | * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < | ||
6 | * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ | ||
7 | * \/ \/ \/ \/ \/ | ||
8 | * | ||
9 | * Copyright (C) 2007 by Dominik Wenger | ||
10 | * | ||
11 | * This program is free software; you can redistribute it and/or | ||
12 | * modify it under the terms of the GNU General Public License | ||
13 | * as published by the Free Software Foundation; either version 2 | ||
14 | * of the License, or (at your option) any later version. | ||
15 | * | ||
16 | * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY | ||
17 | * KIND, either express or implied. | ||
18 | * | ||
19 | ****************************************************************************/ | ||
20 | |||
21 | #ifndef RBSETTINGS_H | ||
22 | #define RBSETTINGS_H | ||
23 | |||
24 | #include <QtCore> | ||
25 | |||
26 | class RbSettings : public QObject | ||
27 | { | ||
28 | Q_OBJECT | ||
29 | public: | ||
30 | |||
31 | //! All user settings | ||
32 | enum UserSettings { | ||
33 | RbutilVersion, | ||
34 | ShowChangelog, | ||
35 | CurrentPlatform, | ||
36 | Mountpoint, | ||
37 | CachePath, | ||
38 | Build, | ||
39 | ProxyType, | ||
40 | Proxy, | ||
41 | OfPath, | ||
42 | Platform, | ||
43 | Language, | ||
44 | BackupPath, | ||
45 | InstallRockbox, | ||
46 | InstallFonts, | ||
47 | InstallThemes, | ||
48 | InstallPluginData, | ||
49 | InstallVoice, | ||
50 | InstallManual, | ||
51 | Tts, | ||
52 | UseTtsCorrections, | ||
53 | TalkFolders, | ||
54 | TalkProcessFiles, | ||
55 | TalkProcessFolders, | ||
56 | TalkRecursive, | ||
57 | TalkSkipExisting, | ||
58 | TalkStripExtensions, | ||
59 | TalkIgnoreFiles, | ||
60 | TalkIgnoreWildcards, | ||
61 | VoiceLanguage, | ||
62 | TtsLanguage, | ||
63 | TtsOptions, | ||
64 | TtsPath, | ||
65 | TtsVoice, | ||
66 | TtsPitch, | ||
67 | EncoderPath, | ||
68 | EncoderOptions, | ||
69 | WavtrimThreshold, | ||
70 | EncoderComplexity, | ||
71 | TtsSpeed, | ||
72 | CacheDisabled, | ||
73 | TtsUseSapi4, | ||
74 | EncoderNarrowBand, | ||
75 | EncoderQuality, | ||
76 | EncoderVolume, | ||
77 | }; | ||
78 | |||
79 | //! call this to flush the user Settings | ||
80 | static void sync(); | ||
81 | //! returns the filename of the usersettings file | ||
82 | static QString userSettingFilename(); | ||
83 | //! get a value from user settings | ||
84 | static QVariant value(enum UserSettings setting); | ||
85 | //! set a user setting value | ||
86 | static void setValue(enum UserSettings setting , QVariant value); | ||
87 | //! get a user setting from a subvalue (ie for encoders and tts engines) | ||
88 | static QVariant subValue(QString sub, enum UserSettings setting); | ||
89 | //! set a user setting from a subvalue (ie for encoders and tts engines) | ||
90 | static void setSubValue(QString sub, enum UserSettings setting, QVariant value); | ||
91 | |||
92 | private: | ||
93 | //! you shouldnt call this, its a fully static calls | ||
94 | RbSettings() {} | ||
95 | //! create the setting objects if neccessary | ||
96 | static void ensureRbSettingsExists(); | ||
97 | //! create a settings path, substitute platform, tts and encoder | ||
98 | static QString constructSettingPath(QString path, QString substitute = QString()); | ||
99 | |||
100 | //! pointers to our setting object | ||
101 | static QSettings *userSettings; | ||
102 | }; | ||
103 | |||
104 | #endif | ||
105 | |||
diff --git a/utils/rbutilqt/base/rockboxinfo.cpp b/utils/rbutilqt/base/rockboxinfo.cpp new file mode 100644 index 0000000000..f34adbfc2f --- /dev/null +++ b/utils/rbutilqt/base/rockboxinfo.cpp | |||
@@ -0,0 +1,83 @@ | |||
1 | /*************************************************************************** | ||
2 | * __________ __ ___. | ||
3 | * Open \______ \ ____ ____ | | _\_ |__ _______ ___ | ||
4 | * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / | ||
5 | * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < | ||
6 | * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ | ||
7 | * \/ \/ \/ \/ \/ | ||
8 | * | ||
9 | * Copyright (C) 2007 by Dominik Wenger | ||
10 | * | ||
11 | * All files in this archive are subject to the GNU General Public License. | ||
12 | * See the file COPYING in the source tree root for full license agreement. | ||
13 | * | ||
14 | * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY | ||
15 | * KIND, either express or implied. | ||
16 | * | ||
17 | ****************************************************************************/ | ||
18 | |||
19 | #include "rockboxinfo.h" | ||
20 | |||
21 | #include <QtCore> | ||
22 | #include <QDebug> | ||
23 | #include "Logger.h" | ||
24 | |||
25 | RockboxInfo::RockboxInfo(QString mountpoint, QString fname) | ||
26 | { | ||
27 | LOG_INFO() << "Getting version info from rockbox-info.txt"; | ||
28 | QFile file(mountpoint + "/" + fname); | ||
29 | m_success = false; | ||
30 | m_voicefmt = 400; // default value for compatibility | ||
31 | if(!file.exists()) | ||
32 | return; | ||
33 | |||
34 | if(!file.open(QIODevice::ReadOnly | QIODevice::Text)) | ||
35 | return; | ||
36 | |||
37 | // read file contents | ||
38 | QRegExp hash("^Version:\\s+(r?)([0-9a-fM]+)"); | ||
39 | QRegExp version("^Version:\\s+(\\S.*)"); | ||
40 | QRegExp release("^Version:\\s+([0-9\\.]+)\\s*$"); | ||
41 | QRegExp target("^Target:\\s+(\\S.*)"); | ||
42 | QRegExp features("^Features:\\s+(\\S.*)"); | ||
43 | QRegExp targetid("^Target id:\\s+(\\S.*)"); | ||
44 | QRegExp memory("^Memory:\\s+(\\S.*)"); | ||
45 | QRegExp voicefmt("^Voice format:\\s+(\\S.*)"); | ||
46 | while (!file.atEnd()) | ||
47 | { | ||
48 | QString line = file.readLine().trimmed(); | ||
49 | |||
50 | if(version.indexIn(line) >= 0) { | ||
51 | m_version = version.cap(1); | ||
52 | } | ||
53 | if(release.indexIn(line) >= 0) { | ||
54 | m_release = release.cap(1); | ||
55 | } | ||
56 | if(hash.indexIn(line) >= 0) { | ||
57 | // git hashes are usually at least 7 characters. | ||
58 | // svn revisions are expected to be at least 4 digits. | ||
59 | if(hash.cap(2).size() > 3) | ||
60 | m_revision = hash.cap(2); | ||
61 | } | ||
62 | else if(target.indexIn(line) >= 0) { | ||
63 | m_target = target.cap(1); | ||
64 | } | ||
65 | else if(features.indexIn(line) >= 0) { | ||
66 | m_features = features.cap(1); | ||
67 | } | ||
68 | else if(targetid.indexIn(line) >= 0) { | ||
69 | m_targetid = targetid.cap(1); | ||
70 | } | ||
71 | else if(memory.indexIn(line) >= 0) { | ||
72 | m_ram = memory.cap(1).toInt(); | ||
73 | } | ||
74 | else if(voicefmt.indexIn(line) >= 0) { | ||
75 | m_voicefmt = voicefmt.cap(1).toInt(); | ||
76 | } | ||
77 | } | ||
78 | |||
79 | file.close(); | ||
80 | m_success = true; | ||
81 | return; | ||
82 | } | ||
83 | |||
diff --git a/utils/rbutilqt/base/rockboxinfo.h b/utils/rbutilqt/base/rockboxinfo.h new file mode 100644 index 0000000000..af5cf227f7 --- /dev/null +++ b/utils/rbutilqt/base/rockboxinfo.h | |||
@@ -0,0 +1,54 @@ | |||
1 | /*************************************************************************** | ||
2 | * __________ __ ___. | ||
3 | * Open \______ \ ____ ____ | | _\_ |__ _______ ___ | ||
4 | * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / | ||
5 | * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < | ||
6 | * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ | ||
7 | * \/ \/ \/ \/ \/ | ||
8 | * | ||
9 | * Copyright (C) 2007 by Dominik Wenger | ||
10 | * | ||
11 | * This program is free software; you can redistribute it and/or | ||
12 | * modify it under the terms of the GNU General Public License | ||
13 | * as published by the Free Software Foundation; either version 2 | ||
14 | * of the License, or (at your option) any later version. | ||
15 | * | ||
16 | * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY | ||
17 | * KIND, either express or implied. | ||
18 | * | ||
19 | ****************************************************************************/ | ||
20 | |||
21 | |||
22 | #ifndef ROCKBOXINFO_H | ||
23 | #define ROCKBOXINFO_H | ||
24 | |||
25 | #include <QString> | ||
26 | |||
27 | class RockboxInfo | ||
28 | { | ||
29 | public: | ||
30 | RockboxInfo(QString mountpoint, QString fname = "/.rockbox/rockbox-info.txt"); | ||
31 | |||
32 | QString version() {return m_version;} | ||
33 | QString features(){return m_features;} | ||
34 | QString targetID() {return m_targetid;} | ||
35 | QString target() {return m_target;} | ||
36 | int ram() { return m_ram; } | ||
37 | int voicefmt() { return m_voicefmt; } | ||
38 | bool success() { return m_success; } | ||
39 | QString revision(void) { return m_revision; } | ||
40 | QString release(void) { return m_release; } | ||
41 | private: | ||
42 | QString m_version; | ||
43 | QString m_revision; | ||
44 | QString m_release; | ||
45 | QString m_features; | ||
46 | QString m_targetid; | ||
47 | QString m_target; | ||
48 | int m_ram; | ||
49 | int m_voicefmt; | ||
50 | bool m_success; | ||
51 | }; | ||
52 | |||
53 | #endif | ||
54 | |||
diff --git a/utils/rbutilqt/base/system.cpp b/utils/rbutilqt/base/system.cpp new file mode 100644 index 0000000000..dafab971ec --- /dev/null +++ b/utils/rbutilqt/base/system.cpp | |||
@@ -0,0 +1,519 @@ | |||
1 | /*************************************************************************** | ||
2 | * __________ __ ___. | ||
3 | * Open \______ \ ____ ____ | | _\_ |__ _______ ___ | ||
4 | * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / | ||
5 | * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < | ||
6 | * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ | ||
7 | * \/ \/ \/ \/ \/ | ||
8 | * | ||
9 | * Copyright (C) 2007 by Dominik Wenger | ||
10 | * | ||
11 | * All files in this archive are subject to the GNU General Public License. | ||
12 | * See the file COPYING in the source tree root for full license agreement. | ||
13 | * | ||
14 | * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY | ||
15 | * KIND, either express or implied. | ||
16 | * | ||
17 | ****************************************************************************/ | ||
18 | |||
19 | |||
20 | #include "system.h" | ||
21 | |||
22 | #include <QtCore> | ||
23 | #include <QDebug> | ||
24 | |||
25 | #include <cstdlib> | ||
26 | #include <stdio.h> | ||
27 | |||
28 | // Windows Includes | ||
29 | #if defined(Q_OS_WIN32) | ||
30 | #if defined(UNICODE) | ||
31 | #define _UNICODE | ||
32 | #endif | ||
33 | #include <windows.h> | ||
34 | #include <tchar.h> | ||
35 | #include <lm.h> | ||
36 | #include <windows.h> | ||
37 | #include <setupapi.h> | ||
38 | #endif | ||
39 | |||
40 | // Linux and Mac includes | ||
41 | #if defined(Q_OS_LINUX) || defined(Q_OS_MACX) | ||
42 | #include <sys/utsname.h> | ||
43 | #include <unistd.h> | ||
44 | #include <pwd.h> | ||
45 | #endif | ||
46 | |||
47 | // Linux includes | ||
48 | #if defined(Q_OS_LINUX) | ||
49 | #include <libusb-1.0/libusb.h> | ||
50 | #include <mntent.h> | ||
51 | #endif | ||
52 | |||
53 | // Mac includes | ||
54 | #if defined(Q_OS_MACX) | ||
55 | #include <sys/param.h> | ||
56 | #include <sys/ucred.h> | ||
57 | #include <sys/mount.h> | ||
58 | |||
59 | #include <CoreFoundation/CoreFoundation.h> | ||
60 | #include <SystemConfiguration/SystemConfiguration.h> | ||
61 | #include <CoreServices/CoreServices.h> | ||
62 | #include <IOKit/IOKitLib.h> | ||
63 | #include <IOKit/usb/IOUSBLib.h> | ||
64 | #endif | ||
65 | |||
66 | #include "utils.h" | ||
67 | #include "rbsettings.h" | ||
68 | #include "Logger.h" | ||
69 | |||
70 | /** @brief detect permission of user (only Windows at moment). | ||
71 | * @return enum userlevel. | ||
72 | */ | ||
73 | #if defined(Q_OS_WIN32) | ||
74 | enum System::userlevel System::userPermissions(void) | ||
75 | { | ||
76 | LPUSER_INFO_1 buf = NULL; | ||
77 | wchar_t userbuf[UNLEN]; | ||
78 | DWORD usersize = UNLEN; | ||
79 | BOOL status; | ||
80 | enum userlevel result = ERR; | ||
81 | |||
82 | status = GetUserNameW(userbuf, &usersize); | ||
83 | if(!status) | ||
84 | return ERR; | ||
85 | |||
86 | if(NetUserGetInfo(NULL, userbuf, (DWORD)1, (LPBYTE*)&buf) == NERR_Success) { | ||
87 | switch(buf->usri1_priv) { | ||
88 | case USER_PRIV_GUEST: | ||
89 | result = GUEST; | ||
90 | break; | ||
91 | case USER_PRIV_USER: | ||
92 | result = USER; | ||
93 | break; | ||
94 | case USER_PRIV_ADMIN: | ||
95 | result = ADMIN; | ||
96 | break; | ||
97 | default: | ||
98 | result = ERR; | ||
99 | break; | ||
100 | } | ||
101 | } | ||
102 | if(buf != NULL) | ||
103 | NetApiBufferFree(buf); | ||
104 | |||
105 | return result; | ||
106 | } | ||
107 | |||
108 | /** @brief detects user permissions (only Windows at moment). | ||
109 | * @return a user readable string with the permission. | ||
110 | */ | ||
111 | QString System::userPermissionsString(void) | ||
112 | { | ||
113 | QString result; | ||
114 | int perm = userPermissions(); | ||
115 | switch(perm) { | ||
116 | case GUEST: | ||
117 | result = tr("Guest"); | ||
118 | break; | ||
119 | case ADMIN: | ||
120 | result = tr("Admin"); | ||
121 | break; | ||
122 | case USER: | ||
123 | result = tr("User"); | ||
124 | break; | ||
125 | default: | ||
126 | result = tr("Error"); | ||
127 | break; | ||
128 | } | ||
129 | return result; | ||
130 | } | ||
131 | #endif | ||
132 | |||
133 | |||
134 | /** @brief detects current Username. | ||
135 | * @return string with Username. | ||
136 | */ | ||
137 | QString System::userName(void) | ||
138 | { | ||
139 | #if defined(Q_OS_WIN32) | ||
140 | wchar_t userbuf[UNLEN]; | ||
141 | DWORD usersize = UNLEN; | ||
142 | |||
143 | if(GetUserNameW(userbuf, &usersize) == 0) | ||
144 | return QString(); | ||
145 | |||
146 | return QString::fromWCharArray(userbuf); | ||
147 | #endif | ||
148 | #if defined(Q_OS_LINUX) || defined(Q_OS_MACX) | ||
149 | struct passwd *user; | ||
150 | user = getpwuid(geteuid()); | ||
151 | return QString(user->pw_name); | ||
152 | #endif | ||
153 | } | ||
154 | |||
155 | |||
156 | /** @brief detects the OS Version | ||
157 | * @return String with OS Version. | ||
158 | */ | ||
159 | QString System::osVersionString(void) | ||
160 | { | ||
161 | QString result; | ||
162 | #if defined(Q_OS_WIN32) | ||
163 | SYSTEM_INFO sysinfo; | ||
164 | OSVERSIONINFO osvi; | ||
165 | ZeroMemory(&osvi, sizeof(OSVERSIONINFO)); | ||
166 | osvi.dwOSVersionInfoSize = sizeof(OSVERSIONINFO); | ||
167 | GetVersionEx(&osvi); | ||
168 | GetSystemInfo(&sysinfo); | ||
169 | |||
170 | result = QString("Windows version %1.%2, ").arg(osvi.dwMajorVersion).arg(osvi.dwMinorVersion); | ||
171 | if(osvi.szCSDVersion) | ||
172 | result += QString("build %1 (%2)").arg(osvi.dwBuildNumber) | ||
173 | .arg(QString::fromWCharArray(osvi.szCSDVersion)); | ||
174 | else | ||
175 | result += QString("build %1").arg(osvi.dwBuildNumber); | ||
176 | result += QString("<br/>CPU: %1, %2 processor(s)").arg(sysinfo.dwProcessorType) | ||
177 | .arg(sysinfo.dwNumberOfProcessors); | ||
178 | #endif | ||
179 | #if defined(Q_OS_LINUX) || defined(Q_OS_MACX) | ||
180 | struct utsname u; | ||
181 | int ret; | ||
182 | ret = uname(&u); | ||
183 | |||
184 | #if defined(Q_OS_MACX) | ||
185 | SInt32 cores; | ||
186 | Gestalt(gestaltCountOfCPUs, &cores); | ||
187 | #else | ||
188 | long cores = sysconf(_SC_NPROCESSORS_ONLN); | ||
189 | #endif | ||
190 | if(ret != -1) { | ||
191 | result = QString("CPU: %1, %2 processor(s)").arg(u.machine).arg(cores); | ||
192 | result += QString("<br/>System: %2<br/>Release: %3<br/>Version: %4") | ||
193 | .arg(u.sysname).arg(u.release).arg(u.version); | ||
194 | } | ||
195 | else { | ||
196 | result = QString("(Error when retrieving system information)"); | ||
197 | } | ||
198 | #if defined(Q_OS_MACX) | ||
199 | SInt32 major; | ||
200 | SInt32 minor; | ||
201 | SInt32 bugfix; | ||
202 | Gestalt(gestaltSystemVersionMajor, &major); | ||
203 | Gestalt(gestaltSystemVersionMinor, &minor); | ||
204 | Gestalt(gestaltSystemVersionBugFix, &bugfix); | ||
205 | |||
206 | result += QString("<br/>OS X %1.%2.%3 ").arg(major).arg(minor).arg(bugfix); | ||
207 | // 1: 86k, 2: ppc, 10: i386 | ||
208 | SInt32 arch; | ||
209 | Gestalt(gestaltSysArchitecture, &arch); | ||
210 | switch(arch) { | ||
211 | case 1: | ||
212 | result.append("(86k)"); | ||
213 | break; | ||
214 | case 2: | ||
215 | result.append("(ppc)"); | ||
216 | break; | ||
217 | case 10: | ||
218 | result.append("(x86)"); | ||
219 | break; | ||
220 | default: | ||
221 | result.append("(unknown)"); | ||
222 | break; | ||
223 | } | ||
224 | #endif | ||
225 | #endif | ||
226 | result += QString("<br/>Qt version %1").arg(qVersion()); | ||
227 | return result; | ||
228 | } | ||
229 | |||
230 | QList<uint32_t> System::listUsbIds(void) | ||
231 | { | ||
232 | return listUsbDevices().keys(); | ||
233 | } | ||
234 | |||
235 | /** @brief detect devices based on usb pid / vid. | ||
236 | * @return list with usb VID / PID values. | ||
237 | */ | ||
238 | QMultiMap<uint32_t, QString> System::listUsbDevices(void) | ||
239 | { | ||
240 | QMultiMap<uint32_t, QString> usbids; | ||
241 | // usb pid detection | ||
242 | LOG_INFO() << "Searching for USB devices"; | ||
243 | #if defined(Q_OS_LINUX) | ||
244 | libusb_device **devs; | ||
245 | if(libusb_init(nullptr) != 0) { | ||
246 | LOG_ERROR() << "Initializing libusb-1 failed."; | ||
247 | return usbids; | ||
248 | } | ||
249 | |||
250 | if(libusb_get_device_list(nullptr, &devs) < 1) { | ||
251 | LOG_ERROR() << "Error getting device list."; | ||
252 | return usbids; | ||
253 | } | ||
254 | libusb_device *dev; | ||
255 | int i = 0; | ||
256 | while((dev = devs[i++]) != nullptr) { | ||
257 | QString name; | ||
258 | unsigned char buf[256]; | ||
259 | uint32_t id; | ||
260 | struct libusb_device_descriptor descriptor; | ||
261 | if(libusb_get_device_descriptor(dev, &descriptor) == 0) { | ||
262 | id = descriptor.idVendor << 16 | descriptor.idProduct; | ||
263 | |||
264 | libusb_device_handle *dh; | ||
265 | if(libusb_open(dev, &dh) == 0) { | ||
266 | libusb_get_string_descriptor_ascii(dh, descriptor.iManufacturer, buf, 256); | ||
267 | name += QString::fromLatin1((char*)buf) + " "; | ||
268 | libusb_get_string_descriptor_ascii(dh, descriptor.iProduct, buf, 256); | ||
269 | name += QString::fromLatin1((char*)buf); | ||
270 | libusb_close(dh); | ||
271 | } | ||
272 | if(name.isEmpty()) | ||
273 | name = tr("(no description available)"); | ||
274 | if(id) { | ||
275 | usbids.insert(id, name); | ||
276 | LOG_INFO("USB: 0x%08x, %s", id, name.toLocal8Bit().data()); | ||
277 | } | ||
278 | } | ||
279 | } | ||
280 | |||
281 | libusb_free_device_list(devs, 1); | ||
282 | libusb_exit(nullptr); | ||
283 | #endif | ||
284 | |||
285 | #if defined(Q_OS_MACX) | ||
286 | kern_return_t result = KERN_FAILURE; | ||
287 | CFMutableDictionaryRef usb_matching_dictionary; | ||
288 | io_iterator_t usb_iterator = IO_OBJECT_NULL; | ||
289 | usb_matching_dictionary = IOServiceMatching(kIOUSBDeviceClassName); | ||
290 | result = IOServiceGetMatchingServices(kIOMasterPortDefault, usb_matching_dictionary, | ||
291 | &usb_iterator); | ||
292 | if(result) { | ||
293 | LOG_ERROR() << "USB: IOKit: Could not get matching services."; | ||
294 | return usbids; | ||
295 | } | ||
296 | |||
297 | io_object_t usbCurrentObj; | ||
298 | while((usbCurrentObj = IOIteratorNext(usb_iterator))) { | ||
299 | uint32_t id; | ||
300 | QString name; | ||
301 | /* get vendor ID */ | ||
302 | CFTypeRef vidref = NULL; | ||
303 | int vid = 0; | ||
304 | vidref = IORegistryEntryCreateCFProperty(usbCurrentObj, CFSTR("idVendor"), | ||
305 | kCFAllocatorDefault, 0); | ||
306 | CFNumberGetValue((CFNumberRef)vidref, kCFNumberIntType, &vid); | ||
307 | CFRelease(vidref); | ||
308 | |||
309 | /* get product ID */ | ||
310 | CFTypeRef pidref = NULL; | ||
311 | int pid = 0; | ||
312 | pidref = IORegistryEntryCreateCFProperty(usbCurrentObj, CFSTR("idProduct"), | ||
313 | kCFAllocatorDefault, 0); | ||
314 | CFNumberGetValue((CFNumberRef)pidref, kCFNumberIntType, &pid); | ||
315 | CFRelease(pidref); | ||
316 | id = vid << 16 | pid; | ||
317 | |||
318 | /* get product vendor */ | ||
319 | char vendor_buf[256]; | ||
320 | CFIndex vendor_buflen = 256; | ||
321 | CFTypeRef vendor_name_ref = NULL; | ||
322 | |||
323 | vendor_name_ref = IORegistryEntrySearchCFProperty(usbCurrentObj, | ||
324 | kIOServicePlane, CFSTR("USB Vendor Name"), | ||
325 | kCFAllocatorDefault, 0); | ||
326 | if(vendor_name_ref != NULL) { | ||
327 | CFStringGetCString((CFStringRef)vendor_name_ref, vendor_buf, vendor_buflen, | ||
328 | kCFStringEncodingUTF8); | ||
329 | name += QString::fromUtf8(vendor_buf) + " "; | ||
330 | CFRelease(vendor_name_ref); | ||
331 | } | ||
332 | else { | ||
333 | name += QObject::tr("(unknown vendor name) "); | ||
334 | } | ||
335 | |||
336 | /* get product name */ | ||
337 | char product_buf[256]; | ||
338 | CFIndex product_buflen = 256; | ||
339 | CFTypeRef product_name_ref = NULL; | ||
340 | |||
341 | product_name_ref = IORegistryEntrySearchCFProperty(usbCurrentObj, | ||
342 | kIOServicePlane, CFSTR("USB Product Name"), | ||
343 | kCFAllocatorDefault, 0); | ||
344 | if(product_name_ref != NULL) { | ||
345 | CFStringGetCString((CFStringRef)product_name_ref, product_buf, product_buflen, | ||
346 | kCFStringEncodingUTF8); | ||
347 | name += QString::fromUtf8(product_buf); | ||
348 | CFRelease(product_name_ref); | ||
349 | } | ||
350 | else { | ||
351 | name += QObject::tr("(unknown product name)"); | ||
352 | } | ||
353 | |||
354 | if(id) { | ||
355 | usbids.insertMulti(id, name); | ||
356 | LOG_INFO() << "USB:" << QString("0x%1").arg(id, 8, 16) << name; | ||
357 | } | ||
358 | |||
359 | } | ||
360 | IOObjectRelease(usb_iterator); | ||
361 | #endif | ||
362 | |||
363 | #if defined(Q_OS_WIN32) | ||
364 | HDEVINFO deviceInfo; | ||
365 | SP_DEVINFO_DATA infoData; | ||
366 | DWORD i; | ||
367 | |||
368 | // Iterate over all devices | ||
369 | // by doing it this way it's unneccessary to use GUIDs which might be not | ||
370 | // present in current MinGW. It also seemed to be more reliably than using | ||
371 | // a GUID. | ||
372 | // See KB259695 for an example. | ||
373 | deviceInfo = SetupDiGetClassDevs(NULL, NULL, NULL, DIGCF_ALLCLASSES | DIGCF_PRESENT); | ||
374 | |||
375 | infoData.cbSize = sizeof(SP_DEVINFO_DATA); | ||
376 | |||
377 | for(i = 0; SetupDiEnumDeviceInfo(deviceInfo, i, &infoData); i++) { | ||
378 | DWORD data; | ||
379 | LPTSTR buffer = NULL; | ||
380 | DWORD buffersize = 0; | ||
381 | QString description; | ||
382 | |||
383 | // get device descriptor first | ||
384 | // for some reason not doing so results in bad things (tm) | ||
385 | while(!SetupDiGetDeviceRegistryProperty(deviceInfo, &infoData, | ||
386 | SPDRP_DEVICEDESC, &data, (PBYTE)buffer, buffersize, &buffersize)) { | ||
387 | if(GetLastError() == ERROR_INSUFFICIENT_BUFFER) { | ||
388 | if(buffer) free(buffer); | ||
389 | // double buffer size to avoid problems as per KB888609 | ||
390 | buffer = (LPTSTR)malloc(buffersize * 2); | ||
391 | } | ||
392 | else { | ||
393 | break; | ||
394 | } | ||
395 | } | ||
396 | if(!buffer) { | ||
397 | LOG_WARNING() << "Got no device description" | ||
398 | << "(SetupDiGetDeviceRegistryProperty), item" << i; | ||
399 | continue; | ||
400 | } | ||
401 | description = QString::fromWCharArray(buffer); | ||
402 | |||
403 | // now get the hardware id, which contains PID and VID. | ||
404 | while(!SetupDiGetDeviceRegistryProperty(deviceInfo, &infoData, | ||
405 | SPDRP_HARDWAREID, &data, (PBYTE)buffer, buffersize, &buffersize)) { | ||
406 | if(GetLastError() == ERROR_INSUFFICIENT_BUFFER) { | ||
407 | if(buffer) free(buffer); | ||
408 | // double buffer size to avoid problems as per KB888609 | ||
409 | buffer = (LPTSTR)malloc(buffersize * 2); | ||
410 | } | ||
411 | else { | ||
412 | break; | ||
413 | } | ||
414 | } | ||
415 | |||
416 | if(buffer) { | ||
417 | // convert buffer text to upper case to avoid depending on the case of | ||
418 | // the keys (W7 uses different casing than XP at least), in addition | ||
419 | // XP may use "Vid_" and "Pid_". | ||
420 | QString data = QString::fromWCharArray(buffer).toUpper(); | ||
421 | QRegExp rex("USB\\\\VID_([0-9A-F]{4})&PID_([0-9A-F]{4}).*"); | ||
422 | if(rex.indexIn(data) >= 0) { | ||
423 | uint32_t id; | ||
424 | id = rex.cap(1).toUInt(0, 16) << 16 | rex.cap(2).toUInt(0, 16); | ||
425 | usbids.insert(id, description); | ||
426 | LOG_INFO() << "USB:" << QString("0x%1").arg(id, 8, 16); | ||
427 | } | ||
428 | free(buffer); | ||
429 | } | ||
430 | } | ||
431 | SetupDiDestroyDeviceInfoList(deviceInfo); | ||
432 | |||
433 | #endif | ||
434 | return usbids; | ||
435 | } | ||
436 | |||
437 | |||
438 | /** @brief detects current system proxy | ||
439 | * @return QUrl with proxy or empty | ||
440 | */ | ||
441 | QUrl System::systemProxy(void) | ||
442 | { | ||
443 | #if defined(Q_OS_LINUX) | ||
444 | return QUrl(getenv("http_proxy")); | ||
445 | #elif defined(Q_OS_WIN32) | ||
446 | HKEY hk; | ||
447 | wchar_t proxyval[80]; | ||
448 | DWORD buflen = 80; | ||
449 | long ret; | ||
450 | DWORD enable; | ||
451 | DWORD enalen = sizeof(DWORD); | ||
452 | |||
453 | ret = RegOpenKeyEx(HKEY_CURRENT_USER, | ||
454 | _TEXT("Software\\Microsoft\\Windows\\CurrentVersion\\Internet Settings"), | ||
455 | 0, KEY_QUERY_VALUE, &hk); | ||
456 | if(ret != ERROR_SUCCESS) return QUrl(""); | ||
457 | |||
458 | ret = RegQueryValueEx(hk, _TEXT("ProxyServer"), NULL, NULL, (LPBYTE)proxyval, &buflen); | ||
459 | if(ret != ERROR_SUCCESS) return QUrl(""); | ||
460 | |||
461 | ret = RegQueryValueEx(hk, _TEXT("ProxyEnable"), NULL, NULL, (LPBYTE)&enable, &enalen); | ||
462 | if(ret != ERROR_SUCCESS) return QUrl(""); | ||
463 | |||
464 | RegCloseKey(hk); | ||
465 | |||
466 | //LOG_INFO() << QString::fromWCharArray(proxyval) << QString("%1").arg(enable); | ||
467 | if(enable != 0) | ||
468 | return QUrl("http://" + QString::fromWCharArray(proxyval)); | ||
469 | else | ||
470 | return QUrl(""); | ||
471 | #elif defined(Q_OS_MACX) | ||
472 | |||
473 | CFDictionaryRef dictref; | ||
474 | CFStringRef stringref; | ||
475 | CFNumberRef numberref; | ||
476 | int enable = 0; | ||
477 | int port = 0; | ||
478 | unsigned int bufsize = 0; | ||
479 | char *buf; | ||
480 | QUrl proxy; | ||
481 | |||
482 | dictref = SCDynamicStoreCopyProxies(NULL); | ||
483 | if(dictref == NULL) | ||
484 | return proxy; | ||
485 | numberref = (CFNumberRef)CFDictionaryGetValue(dictref, kSCPropNetProxiesHTTPEnable); | ||
486 | if(numberref != NULL) | ||
487 | CFNumberGetValue(numberref, kCFNumberIntType, &enable); | ||
488 | if(enable == 1) { | ||
489 | // get proxy string | ||
490 | stringref = (CFStringRef)CFDictionaryGetValue(dictref, kSCPropNetProxiesHTTPProxy); | ||
491 | if(stringref != NULL) { | ||
492 | // get number of characters. CFStringGetLength uses UTF-16 code pairs | ||
493 | bufsize = CFStringGetLength(stringref) * 2 + 1; | ||
494 | buf = (char*)malloc(sizeof(char) * bufsize); | ||
495 | if(buf == NULL) { | ||
496 | LOG_ERROR() << "can't allocate memory for proxy string!"; | ||
497 | CFRelease(dictref); | ||
498 | return QUrl(""); | ||
499 | } | ||
500 | CFStringGetCString(stringref, buf, bufsize, kCFStringEncodingUTF16); | ||
501 | numberref = (CFNumberRef)CFDictionaryGetValue(dictref, kSCPropNetProxiesHTTPPort); | ||
502 | if(numberref != NULL) | ||
503 | CFNumberGetValue(numberref, kCFNumberIntType, &port); | ||
504 | proxy.setScheme("http"); | ||
505 | proxy.setHost(QString::fromUtf16((unsigned short*)buf)); | ||
506 | proxy.setPort(port); | ||
507 | |||
508 | free(buf); | ||
509 | } | ||
510 | } | ||
511 | CFRelease(dictref); | ||
512 | |||
513 | return proxy; | ||
514 | #else | ||
515 | return QUrl(""); | ||
516 | #endif | ||
517 | } | ||
518 | |||
519 | |||
diff --git a/utils/rbutilqt/base/system.h b/utils/rbutilqt/base/system.h new file mode 100644 index 0000000000..61f0a6f1a9 --- /dev/null +++ b/utils/rbutilqt/base/system.h | |||
@@ -0,0 +1,53 @@ | |||
1 | /*************************************************************************** | ||
2 | * __________ __ ___. | ||
3 | * Open \______ \ ____ ____ | | _\_ |__ _______ ___ | ||
4 | * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / | ||
5 | * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < | ||
6 | * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ | ||
7 | * \/ \/ \/ \/ \/ | ||
8 | * | ||
9 | * Copyright (C) 2007 by Dominik Wenger | ||
10 | * | ||
11 | * This program is free software; you can redistribute it and/or | ||
12 | * modify it under the terms of the GNU General Public License | ||
13 | * as published by the Free Software Foundation; either version 2 | ||
14 | * of the License, or (at your option) any later version. | ||
15 | * | ||
16 | * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY | ||
17 | * KIND, either express or implied. | ||
18 | * | ||
19 | ****************************************************************************/ | ||
20 | |||
21 | |||
22 | #ifndef SYSTEM_H | ||
23 | #define SYSTEM_H | ||
24 | |||
25 | #include <QtCore/QObject> | ||
26 | |||
27 | #include <inttypes.h> | ||
28 | |||
29 | #include <QMultiMap> | ||
30 | #include <QString> | ||
31 | #include <QUrl> | ||
32 | |||
33 | class System : public QObject | ||
34 | { | ||
35 | public: | ||
36 | System() {} | ||
37 | |||
38 | #if defined(Q_OS_WIN32) | ||
39 | enum userlevel { ERR, GUEST, USER, ADMIN }; | ||
40 | static enum userlevel userPermissions(void); | ||
41 | static QString userPermissionsString(void); | ||
42 | #endif | ||
43 | |||
44 | static QString userName(void); | ||
45 | static QString osVersionString(void); | ||
46 | static QList<uint32_t> listUsbIds(void); | ||
47 | static QMultiMap<uint32_t, QString> listUsbDevices(void); | ||
48 | |||
49 | static QUrl systemProxy(void); | ||
50 | |||
51 | }; | ||
52 | #endif | ||
53 | |||
diff --git a/utils/rbutilqt/base/talkfile.cpp b/utils/rbutilqt/base/talkfile.cpp new file mode 100644 index 0000000000..cef53c852b --- /dev/null +++ b/utils/rbutilqt/base/talkfile.cpp | |||
@@ -0,0 +1,300 @@ | |||
1 | /*************************************************************************** | ||
2 | * __________ __ ___. | ||
3 | * Open \______ \ ____ ____ | | _\_ |__ _______ ___ | ||
4 | * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / | ||
5 | * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < | ||
6 | * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ | ||
7 | * \/ \/ \/ \/ \/ | ||
8 | * | ||
9 | * Copyright (C) 2007 by Dominik Wenger | ||
10 | * | ||
11 | * All files in this archive are subject to the GNU General Public License. | ||
12 | * See the file COPYING in the source tree root for full license agreement. | ||
13 | * | ||
14 | * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY | ||
15 | * KIND, either express or implied. | ||
16 | * | ||
17 | ****************************************************************************/ | ||
18 | |||
19 | #include "talkfile.h" | ||
20 | #include "rbsettings.h" | ||
21 | #include "Logger.h" | ||
22 | |||
23 | TalkFileCreator::TalkFileCreator(QObject* parent): QObject(parent) | ||
24 | { | ||
25 | |||
26 | } | ||
27 | |||
28 | //! \brief Creates Talkfiles. | ||
29 | //! | ||
30 | //! \param logger A pointer to a Loggerobject | ||
31 | bool TalkFileCreator::createTalkFiles() | ||
32 | { | ||
33 | m_abort = false; | ||
34 | QString errStr; | ||
35 | |||
36 | emit logItem(tr("Starting Talk file generation for folder %1") | ||
37 | .arg(m_dir), LOGINFO); | ||
38 | emit logProgress(0,0); | ||
39 | QCoreApplication::processEvents(); | ||
40 | |||
41 | // read in Maps of paths - file/dirnames | ||
42 | emit logItem(tr("Reading Filelist..."),LOGINFO); | ||
43 | if(createTalkList(m_mountpoint + "/" + m_dir) == false) | ||
44 | { | ||
45 | emit logItem(tr("Talk file creation aborted"),LOGERROR); | ||
46 | doAbort(); | ||
47 | return false; | ||
48 | } | ||
49 | QCoreApplication::processEvents(); | ||
50 | |||
51 | // generate entries | ||
52 | TalkGenerator generator(this); | ||
53 | // no string corrections yet: do not set language for TalkGenerator. | ||
54 | connect(&generator, &TalkGenerator::done, this, &TalkFileCreator::done); | ||
55 | connect(&generator, &TalkGenerator::logItem, this, &TalkFileCreator::logItem); | ||
56 | connect(&generator, &TalkGenerator::logProgress, this, &TalkFileCreator::logProgress); | ||
57 | connect(this, &TalkFileCreator::aborted, &generator, &TalkGenerator::abort); | ||
58 | |||
59 | if(generator.process(&m_talkList) == TalkGenerator::eERROR) | ||
60 | { | ||
61 | doAbort(); | ||
62 | return false; | ||
63 | } | ||
64 | |||
65 | // Copying talk files | ||
66 | emit logItem(tr("Copying Talkfiles..."),LOGINFO); | ||
67 | if(copyTalkFiles(&errStr) == false) | ||
68 | { | ||
69 | emit logItem(errStr,LOGERROR); | ||
70 | doAbort(); | ||
71 | return false; | ||
72 | } | ||
73 | |||
74 | // Deleting left overs | ||
75 | if( !cleanup()) | ||
76 | return false; | ||
77 | |||
78 | emit logItem(tr("Finished creating Talk files"),LOGOK); | ||
79 | emit logProgress(1,1); | ||
80 | emit done(false); | ||
81 | |||
82 | return true; | ||
83 | } | ||
84 | |||
85 | //! \brief Strips everything after and including the last dot in a string. If there is no dot, nothing is changed | ||
86 | //! | ||
87 | //! \param filename The filename from which to strip the Extension | ||
88 | //! \returns the modified string | ||
89 | QString TalkFileCreator::stripExtension(QString filename) | ||
90 | { | ||
91 | // only strip extension if there is a dot in the filename and there are chars before the dot | ||
92 | if(filename.lastIndexOf(".") != -1 && filename.left(filename.lastIndexOf(".")) != "") | ||
93 | return filename.left(filename.lastIndexOf(".")); | ||
94 | else | ||
95 | return filename; | ||
96 | } | ||
97 | |||
98 | //! \brief Does needed Tasks when we need to abort. Cleans up Files. Stops the Logger, Stops TTS and Encoder | ||
99 | //! | ||
100 | void TalkFileCreator::doAbort() | ||
101 | { | ||
102 | cleanup(); | ||
103 | emit logProgress(0,1); | ||
104 | emit done(true); | ||
105 | } | ||
106 | //! \brief creates a list of what to generate | ||
107 | //! | ||
108 | //! \param startDir The directory from which to start scanning | ||
109 | bool TalkFileCreator::createTalkList(QDir startDir) | ||
110 | { | ||
111 | LOG_INFO() << "generating list of files" << startDir; | ||
112 | m_talkList.clear(); | ||
113 | |||
114 | // create Iterator | ||
115 | QDirIterator::IteratorFlags flags = QDirIterator::NoIteratorFlags; | ||
116 | if(m_recursive) | ||
117 | flags = QDirIterator::Subdirectories; | ||
118 | |||
119 | QDirIterator it(startDir,flags); | ||
120 | |||
121 | //create temp directory | ||
122 | QDir tempDir(QDir::tempPath()+ "/talkfiles/"); | ||
123 | if(!tempDir.exists()) | ||
124 | tempDir.mkpath(QDir::tempPath()+ "/talkfiles/"); | ||
125 | |||
126 | // read in Maps of paths - file/dirnames | ||
127 | while (it.hasNext()) | ||
128 | { | ||
129 | it.next(); | ||
130 | if(m_abort) | ||
131 | { | ||
132 | return false; | ||
133 | } | ||
134 | |||
135 | QFileInfo fileInf = it.fileInfo(); | ||
136 | |||
137 | // its a dir | ||
138 | if(fileInf.isDir()) | ||
139 | { | ||
140 | QDir dir = fileInf.dir(); | ||
141 | |||
142 | // insert into List | ||
143 | if(!dir.dirName().isEmpty() && m_talkFolders) | ||
144 | { | ||
145 | // check if we should ignore it | ||
146 | if(m_generateOnlyNew && QFileInfo::exists(dir.path() + "/_dirname.talk")) | ||
147 | { | ||
148 | continue; | ||
149 | } | ||
150 | |||
151 | //generate entry | ||
152 | TalkGenerator::TalkEntry entry; | ||
153 | entry.toSpeak = dir.dirName(); | ||
154 | entry.wavfilename = QDir::tempPath() + "/talkfiles/" | ||
155 | + QCryptographicHash::hash(entry.toSpeak.toUtf8(), | ||
156 | QCryptographicHash::Md5).toHex() + ".wav"; | ||
157 | entry.talkfilename = QDir::tempPath() + "/talkfiles/" | ||
158 | + QCryptographicHash::hash(entry.toSpeak.toUtf8(), | ||
159 | QCryptographicHash::Md5).toHex() + ".talk"; | ||
160 | entry.target = dir.path() + "/_dirname.talk"; | ||
161 | entry.voiced = false; | ||
162 | entry.encoded = false; | ||
163 | LOG_INFO() << "toSpeak:" << entry.toSpeak | ||
164 | << "target:" << entry.target | ||
165 | << "intermediates:" << entry.wavfilename << entry.talkfilename; | ||
166 | m_talkList.append(entry); | ||
167 | } | ||
168 | } | ||
169 | else // its a File | ||
170 | { | ||
171 | // insert into List | ||
172 | if( !fileInf.fileName().isEmpty() && !fileInf.fileName().endsWith(".talk") && m_talkFiles) | ||
173 | { | ||
174 | //test if we should ignore this file | ||
175 | bool match = false; | ||
176 | for(int i=0; i < m_ignoreFiles.size();i++) | ||
177 | { | ||
178 | QRegExp rx(m_ignoreFiles[i].trimmed()); | ||
179 | rx.setPatternSyntax(QRegExp::Wildcard); | ||
180 | if(rx.exactMatch(fileInf.fileName())) | ||
181 | match = true; | ||
182 | } | ||
183 | if(match) | ||
184 | continue; | ||
185 | |||
186 | // check if we should ignore it | ||
187 | if(m_generateOnlyNew && QFileInfo::exists(fileInf.path() + "/" + fileInf.fileName() + ".talk")) | ||
188 | { | ||
189 | continue; | ||
190 | } | ||
191 | |||
192 | //generate entry | ||
193 | TalkGenerator::TalkEntry entry; | ||
194 | if(m_stripExtensions) | ||
195 | entry.toSpeak = stripExtension(fileInf.fileName()); | ||
196 | else | ||
197 | entry.toSpeak = fileInf.fileName(); | ||
198 | entry.wavfilename = QDir::tempPath() + "/talkfiles/" | ||
199 | + QCryptographicHash::hash(entry.toSpeak.toUtf8(), | ||
200 | QCryptographicHash::Md5).toHex() + ".wav"; | ||
201 | entry.talkfilename = QDir::tempPath() + "/talkfiles/" | ||
202 | + QCryptographicHash::hash(entry.toSpeak.toUtf8(), | ||
203 | QCryptographicHash::Md5).toHex() + ".talk"; | ||
204 | entry.target = fileInf.path() + "/" + fileInf.fileName() + ".talk"; | ||
205 | entry.voiced = false; | ||
206 | entry.encoded = false; | ||
207 | LOG_INFO() << "toSpeak:" << entry.toSpeak | ||
208 | << "target:" << entry.target | ||
209 | << "intermediates:" | ||
210 | << entry.wavfilename << entry.talkfilename; | ||
211 | m_talkList.append(entry); | ||
212 | } | ||
213 | } | ||
214 | QCoreApplication::processEvents(); | ||
215 | } | ||
216 | LOG_INFO() << "list created, entries:" << m_talkList.size(); | ||
217 | return true; | ||
218 | } | ||
219 | |||
220 | |||
221 | //! \brief copys Talkfiles from the temp dir to the target. Progress and installlog is handled inside | ||
222 | //! | ||
223 | //! \param errString Pointer to a QString where the error cause is written. | ||
224 | //! \returns true on success, false on error or user abort | ||
225 | bool TalkFileCreator::copyTalkFiles(QString* errString) | ||
226 | { | ||
227 | int progressMax = m_talkList.size(); | ||
228 | int m_progress = 0; | ||
229 | emit logProgress(m_progress,progressMax); | ||
230 | |||
231 | QSettings installlog(m_mountpoint + "/.rockbox/rbutil.log", QSettings::IniFormat, nullptr); | ||
232 | installlog.beginGroup("talkfiles"); | ||
233 | |||
234 | for(int i=0; i < m_talkList.size(); i++) | ||
235 | { | ||
236 | if(m_abort) | ||
237 | { | ||
238 | *errString = tr("File copy aborted"); | ||
239 | return false; | ||
240 | } | ||
241 | |||
242 | // skip not encoded files | ||
243 | if(m_talkList[i].encoded == false) | ||
244 | { | ||
245 | emit logProgress(++m_progress,progressMax); | ||
246 | continue; // this file was skipped in one of the previous steps | ||
247 | } | ||
248 | // remove target if it exists, and if we should overwrite it | ||
249 | if(QFile::exists(m_talkList[i].target)) | ||
250 | QFile::remove(m_talkList[i].target); | ||
251 | |||
252 | // copying | ||
253 | LOG_INFO() << "copying" << m_talkList[i].talkfilename | ||
254 | << "to" << m_talkList[i].target; | ||
255 | if(!QFile::copy(m_talkList[i].talkfilename,m_talkList[i].target)) | ||
256 | { | ||
257 | *errString = tr("Copying of %1 to %2 failed").arg(m_talkList[i].talkfilename).arg(m_talkList[i].target); | ||
258 | return false; | ||
259 | } | ||
260 | |||
261 | // add to installlog | ||
262 | QString now = QDate::currentDate().toString("yyyyMMdd"); | ||
263 | installlog.setValue(m_talkList[i].target.remove(0,m_mountpoint.length()),now); | ||
264 | |||
265 | emit logProgress(++m_progress,progressMax); | ||
266 | QCoreApplication::processEvents(); | ||
267 | } | ||
268 | installlog.endGroup(); | ||
269 | installlog.sync(); | ||
270 | return true; | ||
271 | } | ||
272 | |||
273 | |||
274 | //! \brief Cleans up Files potentially left in the temp dir | ||
275 | //! | ||
276 | bool TalkFileCreator::cleanup() | ||
277 | { | ||
278 | emit logItem(tr("Cleaning up..."),LOGINFO); | ||
279 | |||
280 | for(int i=0; i < m_talkList.size(); i++) | ||
281 | { | ||
282 | if(QFile::exists(m_talkList[i].wavfilename)) | ||
283 | QFile::remove(m_talkList[i].wavfilename); | ||
284 | if(QFile::exists(m_talkList[i].talkfilename)) | ||
285 | QFile::remove(m_talkList[i].talkfilename); | ||
286 | |||
287 | QCoreApplication::processEvents(); | ||
288 | } | ||
289 | emit logItem(tr("Finished"),LOGINFO); | ||
290 | return true; | ||
291 | } | ||
292 | |||
293 | //! \brief slot, which is connected to the abort of the Logger. Sets a flag, so Creating Talkfiles ends at the next possible position | ||
294 | //! | ||
295 | void TalkFileCreator::abort() | ||
296 | { | ||
297 | m_abort = true; | ||
298 | emit aborted(); | ||
299 | } | ||
300 | |||
diff --git a/utils/rbutilqt/base/talkfile.h b/utils/rbutilqt/base/talkfile.h new file mode 100644 index 0000000000..11dd95ca14 --- /dev/null +++ b/utils/rbutilqt/base/talkfile.h | |||
@@ -0,0 +1,83 @@ | |||
1 | /*************************************************************************** | ||
2 | * __________ __ ___. | ||
3 | * Open \______ \ ____ ____ | | _\_ |__ _______ ___ | ||
4 | * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / | ||
5 | * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < | ||
6 | * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ | ||
7 | * \/ \/ \/ \/ \/ | ||
8 | * | ||
9 | * Copyright (C) 2007 by Dominik Wenger | ||
10 | * | ||
11 | * This program is free software; you can redistribute it and/or | ||
12 | * modify it under the terms of the GNU General Public License | ||
13 | * as published by the Free Software Foundation; either version 2 | ||
14 | * of the License, or (at your option) any later version. | ||
15 | * | ||
16 | * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY | ||
17 | * KIND, either express or implied. | ||
18 | * | ||
19 | ****************************************************************************/ | ||
20 | |||
21 | |||
22 | #ifndef TALKFILE_H | ||
23 | #define TALKFILE_H | ||
24 | |||
25 | #include <QtCore> | ||
26 | #include "progressloggerinterface.h" | ||
27 | |||
28 | #include "talkgenerator.h" | ||
29 | |||
30 | class TalkFileCreator :public QObject | ||
31 | { | ||
32 | Q_OBJECT | ||
33 | |||
34 | public: | ||
35 | TalkFileCreator(QObject* parent); | ||
36 | |||
37 | bool createTalkFiles(); | ||
38 | |||
39 | void setDir(QString dir) {m_dir = dir;} | ||
40 | void setMountPoint(QString mountpoint) {m_mountpoint = mountpoint;} | ||
41 | |||
42 | void setGenerateOnlyNew(bool ov) {m_generateOnlyNew = ov;} | ||
43 | void setRecursive(bool ov) {m_recursive = ov;} | ||
44 | void setStripExtensions(bool ov) {m_stripExtensions = ov;} | ||
45 | void setTalkFolders(bool ov) {m_talkFolders = ov;} | ||
46 | void setTalkFiles(bool ov) {m_talkFiles = ov;} | ||
47 | void setIgnoreFiles(QStringList wildcards) {m_ignoreFiles = wildcards;} | ||
48 | public slots: | ||
49 | void abort(); | ||
50 | |||
51 | signals: | ||
52 | void done(bool); | ||
53 | void aborted(); | ||
54 | void logItem(QString, int); //! set logger item | ||
55 | void logProgress(int, int); //! set progress bar. | ||
56 | |||
57 | private: | ||
58 | bool cleanup(); | ||
59 | QString stripExtension(QString filename); | ||
60 | void doAbort(); | ||
61 | void resetProgress(int max); | ||
62 | bool copyTalkFiles(QString* errString); | ||
63 | |||
64 | bool createTalkList(QDir startDir); | ||
65 | |||
66 | QString m_dir; | ||
67 | QString m_mountpoint; | ||
68 | |||
69 | bool m_generateOnlyNew; | ||
70 | bool m_recursive; | ||
71 | bool m_stripExtensions; | ||
72 | bool m_talkFolders; | ||
73 | bool m_talkFiles; | ||
74 | QStringList m_ignoreFiles; | ||
75 | |||
76 | bool m_abort; | ||
77 | |||
78 | QList<TalkGenerator::TalkEntry> m_talkList; | ||
79 | }; | ||
80 | |||
81 | |||
82 | #endif | ||
83 | |||
diff --git a/utils/rbutilqt/base/talkgenerator.cpp b/utils/rbutilqt/base/talkgenerator.cpp new file mode 100644 index 0000000000..9139ceb274 --- /dev/null +++ b/utils/rbutilqt/base/talkgenerator.cpp | |||
@@ -0,0 +1,337 @@ | |||
1 | /*************************************************************************** | ||
2 | * __________ __ ___. | ||
3 | * Open \______ \ ____ ____ | | _\_ |__ _______ ___ | ||
4 | * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / | ||
5 | * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < | ||
6 | * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ | ||
7 | * \/ \/ \/ \/ \/ | ||
8 | * | ||
9 | * Copyright (C) 2007 by Dominik Wenger | ||
10 | * | ||
11 | * All files in this archive are subject to the GNU General Public License. | ||
12 | * See the file COPYING in the source tree root for full license agreement. | ||
13 | * | ||
14 | * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY | ||
15 | * KIND, either express or implied. | ||
16 | * | ||
17 | ****************************************************************************/ | ||
18 | |||
19 | #include "talkgenerator.h" | ||
20 | #include "rbsettings.h" | ||
21 | #include "playerbuildinfo.h" | ||
22 | #include "wavtrim.h" | ||
23 | #include "Logger.h" | ||
24 | |||
25 | TalkGenerator::TalkGenerator(QObject* parent): QObject(parent) | ||
26 | { | ||
27 | |||
28 | } | ||
29 | |||
30 | //! \brief Creates Talkfiles. | ||
31 | //! | ||
32 | TalkGenerator::Status TalkGenerator::process(QList<TalkEntry>* list,int wavtrimth) | ||
33 | { | ||
34 | m_abort = false; | ||
35 | QString errStr; | ||
36 | bool warnings = false; | ||
37 | |||
38 | //tts | ||
39 | emit logItem(tr("Starting TTS Engine"), LOGINFO); | ||
40 | m_tts = TTSBase::getTTS(this, RbSettings::value(RbSettings::Tts).toString()); | ||
41 | if(!m_tts) | ||
42 | { | ||
43 | LOG_ERROR() << "getting the TTS object failed!"; | ||
44 | emit logItem(tr("Init of TTS engine failed"), LOGERROR); | ||
45 | emit done(true); | ||
46 | return eERROR; | ||
47 | } | ||
48 | if(!m_tts->start(&errStr)) | ||
49 | { | ||
50 | emit logItem(errStr.trimmed(),LOGERROR); | ||
51 | emit logItem(tr("Init of TTS engine failed"), LOGERROR); | ||
52 | emit done(true); | ||
53 | return eERROR; | ||
54 | } | ||
55 | QCoreApplication::processEvents(); | ||
56 | |||
57 | // Encoder | ||
58 | emit logItem(tr("Starting Encoder Engine"),LOGINFO); | ||
59 | m_enc = EncoderBase::getEncoder(this, PlayerBuildInfo::instance()->value( | ||
60 | PlayerBuildInfo::Encoder).toString()); | ||
61 | if(!m_enc->start()) | ||
62 | { | ||
63 | emit logItem(tr("Init of Encoder engine failed"),LOGERROR); | ||
64 | emit done(true); | ||
65 | m_tts->stop(); | ||
66 | return eERROR; | ||
67 | } | ||
68 | QCoreApplication::processEvents(); | ||
69 | |||
70 | emit logProgress(0,0); | ||
71 | |||
72 | // Voice entries | ||
73 | emit logItem(tr("Voicing entries..."),LOGINFO); | ||
74 | Status voiceStatus= voiceList(list,wavtrimth); | ||
75 | if(voiceStatus == eERROR) | ||
76 | { | ||
77 | m_tts->stop(); | ||
78 | m_enc->stop(); | ||
79 | emit done(true); | ||
80 | return eERROR; | ||
81 | } | ||
82 | else if( voiceStatus == eWARNING) | ||
83 | warnings = true; | ||
84 | |||
85 | QCoreApplication::processEvents(); | ||
86 | |||
87 | // Encoding Entries | ||
88 | emit logItem(tr("Encoding files..."),LOGINFO); | ||
89 | Status encoderStatus = encodeList(list); | ||
90 | if( encoderStatus == eERROR) | ||
91 | { | ||
92 | m_tts->stop(); | ||
93 | m_enc->stop(); | ||
94 | emit done(true); | ||
95 | return eERROR; | ||
96 | } | ||
97 | else if( voiceStatus == eWARNING) | ||
98 | warnings = true; | ||
99 | |||
100 | QCoreApplication::processEvents(); | ||
101 | |||
102 | m_tts->stop(); | ||
103 | m_enc->stop(); | ||
104 | emit logProgress(1,1); | ||
105 | |||
106 | if(warnings) | ||
107 | return eWARNING; | ||
108 | return eOK; | ||
109 | } | ||
110 | |||
111 | //! \brief Voices a List of string | ||
112 | //! | ||
113 | TalkGenerator::Status TalkGenerator::voiceList(QList<TalkEntry>* list,int wavtrimth) | ||
114 | { | ||
115 | int progressMax = list->size(); | ||
116 | int m_progress = 0; | ||
117 | emit logProgress(m_progress,progressMax); | ||
118 | |||
119 | QStringList errors; | ||
120 | QStringList duplicates; | ||
121 | |||
122 | bool warnings = false; | ||
123 | for(int i=0; i < list->size(); i++) | ||
124 | { | ||
125 | if(m_abort) | ||
126 | { | ||
127 | emit logItem(tr("Voicing aborted"), LOGERROR); | ||
128 | return eERROR; | ||
129 | } | ||
130 | |||
131 | // skip duplicated wav entrys | ||
132 | if(!duplicates.contains(list->at(i).wavfilename)) | ||
133 | duplicates.append(list->at(i).wavfilename); | ||
134 | else | ||
135 | { | ||
136 | LOG_INFO() << "duplicate skipped"; | ||
137 | (*list)[i].voiced = true; | ||
138 | emit logProgress(++m_progress,progressMax); | ||
139 | continue; | ||
140 | } | ||
141 | |||
142 | // skip already voiced entrys | ||
143 | if(list->at(i).voiced == true) | ||
144 | { | ||
145 | emit logProgress(++m_progress,progressMax); | ||
146 | continue; | ||
147 | } | ||
148 | // skip entry whith empty text | ||
149 | if(list->at(i).toSpeak == "") | ||
150 | { | ||
151 | emit logProgress(++m_progress,progressMax); | ||
152 | continue; | ||
153 | } | ||
154 | |||
155 | // voice entry | ||
156 | QString error; | ||
157 | LOG_INFO() << "voicing: " << list->at(i).toSpeak | ||
158 | << "to" << list->at(i).wavfilename; | ||
159 | TTSStatus status = m_tts->voice(list->at(i).toSpeak, | ||
160 | list->at(i).wavfilename, &error); | ||
161 | if(status == Warning) | ||
162 | { | ||
163 | warnings = true; | ||
164 | emit logItem(tr("Voicing of %1 failed: %2").arg(list->at(i).toSpeak).arg(error), | ||
165 | LOGWARNING); | ||
166 | } | ||
167 | else if (status == FatalError) | ||
168 | { | ||
169 | emit logItem(tr("Voicing of %1 failed: %2").arg(list->at(i).toSpeak).arg(error), | ||
170 | LOGERROR); | ||
171 | return eERROR; | ||
172 | } | ||
173 | else | ||
174 | (*list)[i].voiced = true; | ||
175 | |||
176 | // wavtrim if needed | ||
177 | if(wavtrimth != -1) | ||
178 | { | ||
179 | char buffer[255]; | ||
180 | if(wavtrim(list->at(i).wavfilename.toLocal8Bit().data(), | ||
181 | wavtrimth, buffer, 255)) | ||
182 | { | ||
183 | LOG_ERROR() << "wavtrim returned error on" | ||
184 | << list->at(i).wavfilename; | ||
185 | return eERROR; | ||
186 | } | ||
187 | } | ||
188 | |||
189 | emit logProgress(++m_progress,progressMax); | ||
190 | QCoreApplication::processEvents(); | ||
191 | } | ||
192 | if(warnings) | ||
193 | return eWARNING; | ||
194 | else | ||
195 | return eOK; | ||
196 | } | ||
197 | |||
198 | |||
199 | //! \brief Encodes a List of strings | ||
200 | //! | ||
201 | TalkGenerator::Status TalkGenerator::encodeList(QList<TalkEntry>* list) | ||
202 | { | ||
203 | QStringList duplicates; | ||
204 | |||
205 | int progressMax = list->size(); | ||
206 | int m_progress = 0; | ||
207 | emit logProgress(m_progress,progressMax); | ||
208 | |||
209 | for(int i=0; i < list->size(); i++) | ||
210 | { | ||
211 | if(m_abort) | ||
212 | { | ||
213 | emit logItem(tr("Encoding aborted"), LOGERROR); | ||
214 | return eERROR; | ||
215 | } | ||
216 | |||
217 | //skip non-voiced entrys | ||
218 | if(list->at(i).voiced == false) | ||
219 | { | ||
220 | LOG_WARNING() << "non voiced entry detected:" | ||
221 | << list->at(i).toSpeak; | ||
222 | emit logProgress(++m_progress,progressMax); | ||
223 | continue; | ||
224 | } | ||
225 | //skip duplicates | ||
226 | if(!duplicates.contains(list->at(i).talkfilename)) | ||
227 | duplicates.append(list->at(i).talkfilename); | ||
228 | else | ||
229 | { | ||
230 | LOG_INFO() << "duplicate skipped"; | ||
231 | (*list)[i].encoded = true; | ||
232 | emit logProgress(++m_progress,progressMax); | ||
233 | continue; | ||
234 | } | ||
235 | |||
236 | //encode entry | ||
237 | LOG_INFO() << "encoding " << list->at(i).wavfilename | ||
238 | << "to" << list->at(i).talkfilename; | ||
239 | if(!m_enc->encode(list->at(i).wavfilename,list->at(i).talkfilename)) | ||
240 | { | ||
241 | emit logItem(tr("Encoding of %1 failed").arg( | ||
242 | QFileInfo(list->at(i).wavfilename).baseName()), LOGERROR); | ||
243 | return eERROR; | ||
244 | } | ||
245 | (*list)[i].encoded = true; | ||
246 | emit logProgress(++m_progress,progressMax); | ||
247 | QCoreApplication::processEvents(); | ||
248 | } | ||
249 | return eOK; | ||
250 | } | ||
251 | |||
252 | //! \brief slot, which is connected to the abort of the Logger. | ||
253 | //Sets a flag, so Creating Talkfiles ends at the next possible position | ||
254 | //! | ||
255 | void TalkGenerator::abort() | ||
256 | { | ||
257 | m_abort = true; | ||
258 | } | ||
259 | |||
260 | QString TalkGenerator::correctString(QString s) | ||
261 | { | ||
262 | QString corrected = s; | ||
263 | int i = 0; | ||
264 | int max = m_corrections.size(); | ||
265 | while(i < max) { | ||
266 | corrected = corrected.replace(QRegExp(m_corrections.at(i).search, | ||
267 | m_corrections.at(i).modifier.contains("i") | ||
268 | ? Qt::CaseInsensitive : Qt::CaseSensitive), | ||
269 | m_corrections.at(i).replace); | ||
270 | i++; | ||
271 | } | ||
272 | |||
273 | if(corrected != s) | ||
274 | LOG_INFO() << "corrected string" << s << "to" << corrected; | ||
275 | |||
276 | return corrected; | ||
277 | m_abort = true; | ||
278 | } | ||
279 | |||
280 | void TalkGenerator::setLang(QString name) | ||
281 | { | ||
282 | m_lang = name; | ||
283 | |||
284 | // re-initialize corrections list | ||
285 | m_corrections.clear(); | ||
286 | QFile correctionsFile(":/builtin/voice-corrections.txt"); | ||
287 | correctionsFile.open(QIODevice::ReadOnly); | ||
288 | |||
289 | QString engine = RbSettings::value(RbSettings::Tts).toString(); | ||
290 | TTSBase* tts = TTSBase::getTTS(this,RbSettings::value(RbSettings::Tts).toString()); | ||
291 | if(!tts) | ||
292 | { | ||
293 | LOG_ERROR() << "getting the TTS object failed!"; | ||
294 | return; | ||
295 | } | ||
296 | QString vendor = tts->voiceVendor(); | ||
297 | delete tts; | ||
298 | |||
299 | if(m_lang.isEmpty()) | ||
300 | m_lang = "english"; | ||
301 | LOG_INFO() << "building string corrections list for" | ||
302 | << m_lang << engine << vendor; | ||
303 | QTextStream stream(&correctionsFile); | ||
304 | while(!stream.atEnd()) { | ||
305 | QString line = stream.readLine(); | ||
306 | if(line.startsWith(" ") || line.length() < 10) | ||
307 | continue; | ||
308 | // separator is first character | ||
309 | QString separator = line.at(0); | ||
310 | line.remove(0, 1); | ||
311 | QStringList items = line.split(separator); | ||
312 | // we need to have at least 6 separate entries. | ||
313 | if(items.size() < 6) | ||
314 | continue; | ||
315 | |||
316 | QRegExp re_lang(items.at(0)); | ||
317 | QRegExp re_engine(items.at(1)); | ||
318 | QRegExp re_vendor(items.at(2)); | ||
319 | if(!re_lang.exactMatch(m_lang)) { | ||
320 | continue; | ||
321 | } | ||
322 | if(!re_vendor.exactMatch(vendor)) { | ||
323 | continue; | ||
324 | } | ||
325 | if(!re_engine.exactMatch(engine)) { | ||
326 | continue; | ||
327 | } | ||
328 | struct CorrectionItems co; | ||
329 | co.search = items.at(3); | ||
330 | co.replace = items.at(4); | ||
331 | // Qt uses backslash for back references, Perl uses dollar sign. | ||
332 | co.replace.replace(QRegExp("\\$(\\d+)"), "\\\\1"); | ||
333 | co.modifier = items.at(5); | ||
334 | m_corrections.append(co); | ||
335 | } | ||
336 | correctionsFile.close(); | ||
337 | } | ||
diff --git a/utils/rbutilqt/base/talkgenerator.h b/utils/rbutilqt/base/talkgenerator.h new file mode 100644 index 0000000000..3e2f9394fb --- /dev/null +++ b/utils/rbutilqt/base/talkgenerator.h | |||
@@ -0,0 +1,91 @@ | |||
1 | /*************************************************************************** | ||
2 | * __________ __ ___. | ||
3 | * Open \______ \ ____ ____ | | _\_ |__ _______ ___ | ||
4 | * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / | ||
5 | * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < | ||
6 | * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ | ||
7 | * \/ \/ \/ \/ \/ | ||
8 | * | ||
9 | * Copyright (C) 2007 by Dominik Wenger | ||
10 | * | ||
11 | * This program is free software; you can redistribute it and/or | ||
12 | * modify it under the terms of the GNU General Public License | ||
13 | * as published by the Free Software Foundation; either version 2 | ||
14 | * of the License, or (at your option) any later version. | ||
15 | * | ||
16 | * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY | ||
17 | * KIND, either express or implied. | ||
18 | * | ||
19 | ****************************************************************************/ | ||
20 | |||
21 | |||
22 | #ifndef TALKGENERATOR_H | ||
23 | #define TALKGENERATOR_H | ||
24 | |||
25 | #include <QtCore> | ||
26 | #include "progressloggerinterface.h" | ||
27 | |||
28 | #include "encoderbase.h" | ||
29 | #include "ttsbase.h" | ||
30 | |||
31 | //! \brief Talk generator, generates .wav and .talk files out of a list. | ||
32 | class TalkGenerator :public QObject | ||
33 | { | ||
34 | Q_OBJECT | ||
35 | public: | ||
36 | enum Status | ||
37 | { | ||
38 | eOK, | ||
39 | eWARNING, | ||
40 | eERROR | ||
41 | }; | ||
42 | |||
43 | struct TalkEntry | ||
44 | { | ||
45 | QString toSpeak; | ||
46 | QString wavfilename; | ||
47 | QString talkfilename; | ||
48 | QString target; | ||
49 | bool voiced; | ||
50 | bool encoded; | ||
51 | }; | ||
52 | |||
53 | TalkGenerator(QObject* parent); | ||
54 | |||
55 | Status process(QList<TalkEntry>* list,int wavtrimth = -1); | ||
56 | QString correctString(QString s); | ||
57 | |||
58 | public slots: | ||
59 | void abort(); | ||
60 | void setLang(QString name); | ||
61 | |||
62 | signals: | ||
63 | void done(bool); | ||
64 | void logItem(QString, int); //! set logger item | ||
65 | void logProgress(int, int); //! set progress bar. | ||
66 | |||
67 | private: | ||
68 | Status voiceList(QList<TalkEntry>* list,int wavetrimth); | ||
69 | Status encodeList(QList<TalkEntry>* list); | ||
70 | |||
71 | TTSBase* m_tts; | ||
72 | EncoderBase* m_enc; | ||
73 | |||
74 | QString m_lang; | ||
75 | |||
76 | struct CorrectionItems | ||
77 | { | ||
78 | QString search; | ||
79 | QString replace; | ||
80 | QString modifier; | ||
81 | }; | ||
82 | QList<struct CorrectionItems> m_corrections; | ||
83 | |||
84 | bool m_abort; | ||
85 | |||
86 | |||
87 | }; | ||
88 | |||
89 | |||
90 | #endif | ||
91 | |||
diff --git a/utils/rbutilqt/base/ttsbase.cpp b/utils/rbutilqt/base/ttsbase.cpp new file mode 100644 index 0000000000..0102d215a6 --- /dev/null +++ b/utils/rbutilqt/base/ttsbase.cpp | |||
@@ -0,0 +1,122 @@ | |||
1 | /*************************************************************************** | ||
2 | * __________ __ ___. | ||
3 | * Open \______ \ ____ ____ | | _\_ |__ _______ ___ | ||
4 | * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / | ||
5 | * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < | ||
6 | * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ | ||
7 | * \/ \/ \/ \/ \/ | ||
8 | * | ||
9 | * Copyright (C) 2007 by Dominik Wenger | ||
10 | * | ||
11 | * All files in this archive are subject to the GNU General Public License. | ||
12 | * See the file COPYING in the source tree root for full license agreement. | ||
13 | * | ||
14 | * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY | ||
15 | * KIND, either express or implied. | ||
16 | * | ||
17 | ****************************************************************************/ | ||
18 | |||
19 | #include <QtCore> | ||
20 | #include "ttsbase.h" | ||
21 | |||
22 | #include "ttsfestival.h" | ||
23 | #include "ttssapi.h" | ||
24 | #include "ttssapi4.h" | ||
25 | #include "ttsmssp.h" | ||
26 | #include "ttsexes.h" | ||
27 | #include "ttsespeak.h" | ||
28 | #include "ttsespeakng.h" | ||
29 | #include "ttsflite.h" | ||
30 | #include "ttsmimic.h" | ||
31 | #include "ttsswift.h" | ||
32 | #if defined(Q_OS_MACX) | ||
33 | #include "ttscarbon.h" | ||
34 | #endif | ||
35 | |||
36 | // list of tts names and identifiers | ||
37 | QMap<QString,QString> TTSBase::ttsList; | ||
38 | |||
39 | TTSBase::TTSBase(QObject* parent): EncTtsSettingInterface(parent) | ||
40 | { | ||
41 | } | ||
42 | |||
43 | // static functions | ||
44 | void TTSBase::initTTSList() | ||
45 | { | ||
46 | #if !defined(Q_OS_WIN) | ||
47 | ttsList["espeak"] = tr("Espeak TTS Engine"); | ||
48 | ttsList["espeakng"] = tr("Espeak-ng TTS Engine"); | ||
49 | ttsList["mimic"] = tr("Mimic TTS Engine"); | ||
50 | #endif | ||
51 | ttsList["flite"] = tr("Flite TTS Engine"); | ||
52 | ttsList["swift"] = tr("Swift TTS Engine"); | ||
53 | #if defined(Q_OS_WIN) | ||
54 | #if 0 /* SAPI4 has been disabled since long. Keep support for now. */ | ||
55 | ttsList["sapi4"] = tr("SAPI4 TTS Engine"); | ||
56 | #endif | ||
57 | ttsList["sapi"] = tr("SAPI5 TTS Engine"); | ||
58 | ttsList["mssp"] = tr("MS Speech Platform"); | ||
59 | #endif | ||
60 | #if defined(Q_OS_LINUX) | ||
61 | ttsList["festival"] = tr("Festival TTS Engine"); | ||
62 | #endif | ||
63 | #if defined(Q_OS_MACX) | ||
64 | ttsList["carbon"] = tr("OS X System Engine"); | ||
65 | #endif | ||
66 | } | ||
67 | |||
68 | // function to get a specific encoder | ||
69 | TTSBase* TTSBase::getTTS(QObject* parent,QString ttsName) | ||
70 | { | ||
71 | |||
72 | TTSBase* tts = nullptr; | ||
73 | #if defined(Q_OS_WIN) | ||
74 | if(ttsName == "sapi") | ||
75 | tts = new TTSSapi(parent); | ||
76 | else if (ttsName == "sapi4") | ||
77 | tts = new TTSSapi4(parent); | ||
78 | else if (ttsName == "mssp") | ||
79 | tts = new TTSMssp(parent); | ||
80 | else | ||
81 | #elif defined(Q_OS_LINUX) | ||
82 | if (ttsName == "festival") | ||
83 | tts = new TTSFestival(parent); | ||
84 | else | ||
85 | #elif defined(Q_OS_MACX) | ||
86 | if(ttsName == "carbon") | ||
87 | tts = new TTSCarbon(parent); | ||
88 | else | ||
89 | #endif | ||
90 | if(ttsName == "espeak") | ||
91 | tts = new TTSEspeak(parent); | ||
92 | else if(ttsName == "espeakng") | ||
93 | tts = new TTSEspeakNG(parent); | ||
94 | else if(ttsName == "mimic") | ||
95 | tts = new TTSMimic(parent); | ||
96 | else if(ttsName == "flite") | ||
97 | tts = new TTSFlite(parent); | ||
98 | else if(ttsName == "swift") | ||
99 | tts = new TTSSwift(parent); | ||
100 | else if(ttsName == "user") | ||
101 | tts = new TTSExes(parent); | ||
102 | |||
103 | return tts; | ||
104 | } | ||
105 | |||
106 | // get the list of encoders, nice names | ||
107 | QStringList TTSBase::getTTSList() | ||
108 | { | ||
109 | // init list if its empty | ||
110 | if(ttsList.count() == 0) | ||
111 | initTTSList(); | ||
112 | |||
113 | return ttsList.keys(); | ||
114 | } | ||
115 | |||
116 | // get nice name of a specific tts | ||
117 | QString TTSBase::getTTSName(QString tts) | ||
118 | { | ||
119 | if(ttsList.isEmpty()) | ||
120 | initTTSList(); | ||
121 | return ttsList.value(tts); | ||
122 | } | ||
diff --git a/utils/rbutilqt/base/ttsbase.h b/utils/rbutilqt/base/ttsbase.h new file mode 100644 index 0000000000..125fe49ff4 --- /dev/null +++ b/utils/rbutilqt/base/ttsbase.h | |||
@@ -0,0 +1,70 @@ | |||
1 | /*************************************************************************** | ||
2 | * __________ __ ___. | ||
3 | * Open \______ \ ____ ____ | | _\_ |__ _______ ___ | ||
4 | * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / | ||
5 | * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < | ||
6 | * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ | ||
7 | * \/ \/ \/ \/ \/ | ||
8 | * | ||
9 | * Copyright (C) 2007 by Dominik Wenger | ||
10 | * | ||
11 | * This program is free software; you can redistribute it and/or | ||
12 | * modify it under the terms of the GNU General Public License | ||
13 | * as published by the Free Software Foundation; either version 2 | ||
14 | * of the License, or (at your option) any later version. | ||
15 | * | ||
16 | * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY | ||
17 | * KIND, either express or implied. | ||
18 | * | ||
19 | ****************************************************************************/ | ||
20 | |||
21 | |||
22 | #ifndef TTSBASE_H | ||
23 | #define TTSBASE_H | ||
24 | |||
25 | #include <QtCore> | ||
26 | |||
27 | #include "encttssettings.h" | ||
28 | |||
29 | enum TTSStatus{ FatalError, NoError, Warning }; | ||
30 | class TTSBase : public EncTtsSettingInterface | ||
31 | { | ||
32 | Q_OBJECT | ||
33 | public: | ||
34 | enum Capability { None = 0, RunInParallel = 1, CanSpeak = 2 }; | ||
35 | Q_DECLARE_FLAGS(Capabilities, Capability) | ||
36 | |||
37 | TTSBase(QObject *parent); | ||
38 | //! Child class should generate a clip | ||
39 | virtual TTSStatus voice(QString text,QString wavfile, QString* errStr) =0; | ||
40 | //! Child class should do startup | ||
41 | virtual bool start(QString *errStr) =0; | ||
42 | //! child class should stop | ||
43 | virtual bool stop() =0; | ||
44 | |||
45 | virtual QString voiceVendor(void) = 0; | ||
46 | // configuration | ||
47 | //! Child class should return true, when configuration is good | ||
48 | virtual bool configOk()=0; | ||
49 | //! Child class should generate and insertSetting(..) its settings | ||
50 | virtual void generateSettings() = 0; | ||
51 | //! Chlid class should commit the Settings to permanent storage | ||
52 | virtual void saveSettings() = 0; | ||
53 | |||
54 | virtual Capabilities capabilities() = 0; | ||
55 | |||
56 | // static functions | ||
57 | static TTSBase* getTTS(QObject* parent,QString ttsname); | ||
58 | static QStringList getTTSList(); | ||
59 | static QString getTTSName(QString tts); | ||
60 | |||
61 | private: | ||
62 | //inits the tts List | ||
63 | static void initTTSList(); | ||
64 | |||
65 | protected: | ||
66 | static QMap<QString,QString> ttsList; | ||
67 | }; | ||
68 | Q_DECLARE_OPERATORS_FOR_FLAGS(TTSBase::Capabilities) | ||
69 | |||
70 | #endif | ||
diff --git a/utils/rbutilqt/base/ttscarbon.cpp b/utils/rbutilqt/base/ttscarbon.cpp new file mode 100644 index 0000000000..2d9fa49dbe --- /dev/null +++ b/utils/rbutilqt/base/ttscarbon.cpp | |||
@@ -0,0 +1,443 @@ | |||
1 | /*************************************************************************** | ||
2 | * __________ __ ___. | ||
3 | * Open \______ \ ____ ____ | | _\_ |__ _______ ___ | ||
4 | * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / | ||
5 | * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < | ||
6 | * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ | ||
7 | * \/ \/ \/ \/ \/ | ||
8 | * | ||
9 | * Copyright (C) 2010 by Dominik Riebeling | ||
10 | * | ||
11 | * All files in this archive are subject to the GNU General Public License. | ||
12 | * See the file COPYING in the source tree root for full license agreement. | ||
13 | * | ||
14 | * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY | ||
15 | * KIND, either express or implied. | ||
16 | * | ||
17 | ****************************************************************************/ | ||
18 | |||
19 | #include <QtCore> | ||
20 | #include "ttsbase.h" | ||
21 | #include "ttscarbon.h" | ||
22 | #include "encttssettings.h" | ||
23 | #include "rbsettings.h" | ||
24 | |||
25 | #include <CoreFoundation/CoreFoundation.h> | ||
26 | #include <ApplicationServices/ApplicationServices.h> | ||
27 | #include <Carbon/Carbon.h> | ||
28 | #include <unistd.h> | ||
29 | #include <sys/stat.h> | ||
30 | #include <inttypes.h> | ||
31 | #include "Logger.h" | ||
32 | |||
33 | TTSCarbon::TTSCarbon(QObject* parent) : TTSBase(parent) | ||
34 | { | ||
35 | } | ||
36 | |||
37 | TTSBase::Capabilities TTSCarbon::capabilities() | ||
38 | { | ||
39 | return TTSBase::CanSpeak; | ||
40 | } | ||
41 | |||
42 | bool TTSCarbon::configOk() | ||
43 | { | ||
44 | return true; | ||
45 | } | ||
46 | |||
47 | |||
48 | bool TTSCarbon::start(QString *errStr) | ||
49 | { | ||
50 | (void)errStr; | ||
51 | VoiceSpec vspec; | ||
52 | VoiceSpec* vspecref = NULL; | ||
53 | VoiceDescription vdesc; | ||
54 | OSErr error; | ||
55 | QString selectedVoice | ||
56 | = RbSettings::subValue("carbon", RbSettings::TtsVoice).toString(); | ||
57 | SInt16 numVoices; | ||
58 | SInt16 voiceIndex; | ||
59 | error = CountVoices(&numVoices); | ||
60 | for(voiceIndex = 1; voiceIndex < numVoices; ++voiceIndex) { | ||
61 | error = GetIndVoice(voiceIndex, &vspec); | ||
62 | error = GetVoiceDescription(&vspec, &vdesc, sizeof(vdesc)); | ||
63 | // name is pascal string, i.e. the first byte is the length. | ||
64 | QString name = QString::fromLocal8Bit((const char*)&vdesc.name[1], | ||
65 | vdesc.name[0]); | ||
66 | if(name == selectedVoice) { | ||
67 | vspecref = &vspec; | ||
68 | if(vdesc.script != -1) | ||
69 | m_voiceScript = (CFStringBuiltInEncodings)vdesc.script; | ||
70 | else | ||
71 | m_voiceScript = (CFStringBuiltInEncodings)vdesc.reserved[0]; | ||
72 | break; | ||
73 | } | ||
74 | } | ||
75 | if(voiceIndex == numVoices) { | ||
76 | // voice not found. Add user notification here and proceed with | ||
77 | // system default voice. | ||
78 | LOG_WARNING() << "Selected voice not found, using system default!"; | ||
79 | GetVoiceDescription(&vspec, &vdesc, sizeof(vdesc)); | ||
80 | if(vdesc.script != -1) | ||
81 | m_voiceScript = (CFStringBuiltInEncodings)vdesc.script; | ||
82 | else | ||
83 | m_voiceScript = (CFStringBuiltInEncodings)vdesc.reserved[0]; | ||
84 | } | ||
85 | |||
86 | error = NewSpeechChannel(vspecref, &m_channel); | ||
87 | //SetSpeechInfo(channel, soSpeechDoneCallBack, speechDone); | ||
88 | Fixed rate = (Fixed)(0x10000 * RbSettings::subValue("carbon", | ||
89 | RbSettings::TtsSpeed).toInt()); | ||
90 | if(rate != 0) | ||
91 | SetSpeechRate(m_channel, rate); | ||
92 | |||
93 | Fixed pitch = (Fixed)(0x10000 * RbSettings::subValue("carbon", | ||
94 | RbSettings::TtsPitch).toInt()); | ||
95 | if(pitch != 0) | ||
96 | SetSpeechPitch(m_channel, pitch); | ||
97 | |||
98 | return (error == 0) ? true : false; | ||
99 | } | ||
100 | |||
101 | |||
102 | bool TTSCarbon::stop(void) | ||
103 | { | ||
104 | DisposeSpeechChannel(m_channel); | ||
105 | return true; | ||
106 | } | ||
107 | |||
108 | |||
109 | void TTSCarbon::generateSettings(void) | ||
110 | { | ||
111 | QStringList voiceNames; | ||
112 | QString systemVoice; | ||
113 | SInt16 numVoices; | ||
114 | OSErr error; | ||
115 | VoiceSpec vspec; | ||
116 | VoiceDescription vdesc; | ||
117 | |||
118 | // get system voice | ||
119 | error = GetVoiceDescription(NULL, &vdesc, sizeof(vdesc)); | ||
120 | systemVoice | ||
121 | = QString::fromLocal8Bit((const char*)&vdesc.name[1], vdesc.name[0]); | ||
122 | // get list of all voices | ||
123 | CountVoices(&numVoices); | ||
124 | for(SInt16 i = 1; i < numVoices; ++i) { | ||
125 | error = GetIndVoice(i, &vspec); | ||
126 | error = GetVoiceDescription(&vspec, &vdesc, sizeof(vdesc)); | ||
127 | // name is pascal string, i.e. the first byte is the length. | ||
128 | QString name | ||
129 | = QString::fromLocal8Bit((const char*)&vdesc.name[1], vdesc.name[0]); | ||
130 | voiceNames.append(name.trimmed()); | ||
131 | } | ||
132 | // voice | ||
133 | EncTtsSetting* setting; | ||
134 | QString voice | ||
135 | = RbSettings::subValue("carbon", RbSettings::TtsVoice).toString(); | ||
136 | if(voice.isEmpty()) | ||
137 | voice = systemVoice; | ||
138 | setting = new EncTtsSetting(this, EncTtsSetting::eSTRINGLIST, | ||
139 | tr("Voice:"), voice, voiceNames, EncTtsSetting::eNOBTN); | ||
140 | insertSetting(ConfigVoice, setting); | ||
141 | |||
142 | // speed | ||
143 | int speed = RbSettings::subValue("carbon", RbSettings::TtsSpeed).toInt(); | ||
144 | setting = new EncTtsSetting(this, EncTtsSetting::eINT, | ||
145 | tr("Speed (words/min):"), speed, 80, 500, | ||
146 | EncTtsSetting::eNOBTN); | ||
147 | insertSetting(ConfigSpeed, setting); | ||
148 | |||
149 | // pitch | ||
150 | int pitch = RbSettings::subValue("carbon", RbSettings::TtsPitch).toInt(); | ||
151 | setting = new EncTtsSetting(this, EncTtsSetting::eINT, | ||
152 | tr("Pitch (0 for default):"), pitch, 0, 65, | ||
153 | EncTtsSetting::eNOBTN); | ||
154 | insertSetting(ConfigPitch, setting); | ||
155 | |||
156 | } | ||
157 | |||
158 | |||
159 | void TTSCarbon::saveSettings(void) | ||
160 | { | ||
161 | // save settings in user config | ||
162 | RbSettings::setSubValue("carbon", RbSettings::TtsVoice, | ||
163 | getSetting(ConfigVoice)->current().toString()); | ||
164 | RbSettings::setSubValue("carbon", RbSettings::TtsSpeed, | ||
165 | getSetting(ConfigSpeed)->current().toInt()); | ||
166 | RbSettings::setSubValue("carbon", RbSettings::TtsPitch, | ||
167 | getSetting(ConfigPitch)->current().toInt()); | ||
168 | RbSettings::sync(); | ||
169 | } | ||
170 | |||
171 | |||
172 | /** @brief create wav file from text using the selected TTS voice. | ||
173 | */ | ||
174 | TTSStatus TTSCarbon::voice(QString text, QString wavfile, QString* errStr) | ||
175 | { | ||
176 | TTSStatus status = NoError; | ||
177 | OSErr error; | ||
178 | |||
179 | char* tmpfile = NULL; | ||
180 | if(!wavfile.isEmpty()) { | ||
181 | QString aifffile = wavfile + ".aiff"; | ||
182 | // FIXME: find out why we need to do this. | ||
183 | // Create a local copy of the temporary file filename. | ||
184 | // Not doing so causes weird issues (path contains trailing spaces) | ||
185 | unsigned int len = aifffile.size() + 1; | ||
186 | tmpfile = (char*)malloc(len * sizeof(char)); | ||
187 | strncpy(tmpfile, aifffile.toLocal8Bit().constData(), len); | ||
188 | CFStringRef tmpfileref = CFStringCreateWithCString(kCFAllocatorDefault, | ||
189 | tmpfile, kCFStringEncodingUTF8); | ||
190 | CFURLRef urlref = CFURLCreateWithFileSystemPath(kCFAllocatorDefault, | ||
191 | tmpfileref, kCFURLPOSIXPathStyle, false); | ||
192 | SetSpeechInfo(m_channel, soOutputToFileWithCFURL, urlref); | ||
193 | } | ||
194 | |||
195 | // speak it. | ||
196 | // Convert the string to the encoding requested by the voice. Do this | ||
197 | // via CFString, as this allows to directly use the destination encoding | ||
198 | // as CFString uses the same values as the voice. | ||
199 | |||
200 | // allocate enough space to allow storing the string in a 2 byte encoding | ||
201 | unsigned int textlen = 2 * text.length() + 1; | ||
202 | char* textbuf = (char*)calloc(textlen, sizeof(char)); | ||
203 | char* utf8data = (char*)text.toUtf8().constData(); | ||
204 | int utf8bytes = text.toUtf8().size(); | ||
205 | CFStringRef cfstring = CFStringCreateWithBytes(kCFAllocatorDefault, | ||
206 | (UInt8*)utf8data, utf8bytes, | ||
207 | kCFStringEncodingUTF8, (Boolean)false); | ||
208 | CFIndex usedBuf = 0; | ||
209 | CFRange range; | ||
210 | range.location = 0; // character in string to start. | ||
211 | range.length = text.length(); // number of _characters_ in string | ||
212 | // FIXME: check if converting between encodings was lossless. | ||
213 | CFStringGetBytes(cfstring, range, m_voiceScript, ' ', | ||
214 | false, (UInt8*)textbuf, textlen, &usedBuf); | ||
215 | |||
216 | error = SpeakText(m_channel, textbuf, (unsigned long)usedBuf); | ||
217 | while(SpeechBusy()) { | ||
218 | // FIXME: add small delay here to make calls less frequent | ||
219 | QCoreApplication::processEvents(); | ||
220 | } | ||
221 | if(error != 0) { | ||
222 | *errStr = tr("Could not voice string"); | ||
223 | status = FatalError; | ||
224 | } | ||
225 | free(textbuf); | ||
226 | CFRelease(cfstring); | ||
227 | |||
228 | if(!wavfile.isEmpty()) { | ||
229 | // convert the temporary aiff file to wav | ||
230 | if(status == NoError | ||
231 | && convertAiffToWav(tmpfile, wavfile.toLocal8Bit().constData()) != 0) { | ||
232 | *errStr = tr("Could not convert intermediate file"); | ||
233 | status = FatalError; | ||
234 | } | ||
235 | // remove temporary aiff file | ||
236 | unlink(tmpfile); | ||
237 | free(tmpfile); | ||
238 | } | ||
239 | |||
240 | return status; | ||
241 | } | ||
242 | |||
243 | |||
244 | unsigned long TTSCarbon::be2u32(unsigned char* buf) | ||
245 | { | ||
246 | return (buf[0]&0xff)<<24 | (buf[1]&0xff)<<16 | (buf[2]&0xff)<<8 | (buf[3]&0xff); | ||
247 | } | ||
248 | |||
249 | |||
250 | unsigned long TTSCarbon::be2u16(unsigned char* buf) | ||
251 | { | ||
252 | return (buf[1]&0xff) | (buf[0]&0xff)<<8; | ||
253 | } | ||
254 | |||
255 | |||
256 | unsigned char* TTSCarbon::u32tobuf(unsigned char* buf, uint32_t val) | ||
257 | { | ||
258 | buf[0] = val & 0xff; | ||
259 | buf[1] = (val>> 8) & 0xff; | ||
260 | buf[2] = (val>>16) & 0xff; | ||
261 | buf[3] = (val>>24) & 0xff; | ||
262 | return buf; | ||
263 | } | ||
264 | |||
265 | |||
266 | unsigned char* TTSCarbon::u16tobuf(unsigned char* buf, uint16_t val) | ||
267 | { | ||
268 | buf[0] = val & 0xff; | ||
269 | buf[1] = (val>> 8) & 0xff; | ||
270 | return buf; | ||
271 | } | ||
272 | |||
273 | |||
274 | /** @brief convert 80 bit extended ("long double") to int. | ||
275 | * This is simplified to handle the usual audio sample rates. Everything else | ||
276 | * might break. If the value isn't supported it will return 0. | ||
277 | * Conversion taken from Rockbox aiff codec. | ||
278 | */ | ||
279 | unsigned int TTSCarbon::extended2int(unsigned char* buf) | ||
280 | { | ||
281 | unsigned int result = 0; | ||
282 | /* value negative? */ | ||
283 | if(buf[0] & 0x80) | ||
284 | return 0; | ||
285 | /* check exponent. Int can handle up to 2^31. */ | ||
286 | int exponent = buf[0] << 8 | buf[1]; | ||
287 | if(exponent < 0x4000 || exponent > (0x4000 + 30)) | ||
288 | return 0; | ||
289 | result = ((buf[2]<<24) | (buf[3]<<16) | (buf[4]<<8) | buf[5]) + 1; | ||
290 | result >>= (16 + 14 - buf[1]); | ||
291 | return result; | ||
292 | } | ||
293 | |||
294 | |||
295 | /** @brief Convert aiff file to wav. Returns 0 on success. | ||
296 | */ | ||
297 | int TTSCarbon::convertAiffToWav(const char* aiff, const char* wav) | ||
298 | { | ||
299 | struct commchunk { | ||
300 | unsigned long chunksize; | ||
301 | unsigned short channels; | ||
302 | unsigned long frames; | ||
303 | unsigned short size; | ||
304 | int rate; | ||
305 | }; | ||
306 | |||
307 | struct ssndchunk { | ||
308 | unsigned long chunksize; | ||
309 | unsigned long offset; | ||
310 | unsigned long blocksize; | ||
311 | }; | ||
312 | |||
313 | FILE* in; | ||
314 | FILE* out; | ||
315 | unsigned char obuf[4]; | ||
316 | unsigned char* buf; | ||
317 | /* minimum file size for a valid aiff file is 46 bytes: | ||
318 | * - FORM chunk: 12 bytes | ||
319 | * - COMM chunk: 18 bytes | ||
320 | * - SSND chunk: 16 bytes (with no actual data) | ||
321 | */ | ||
322 | struct stat filestat; | ||
323 | stat(aiff, &filestat); | ||
324 | if(filestat.st_size < 46) | ||
325 | return -1; | ||
326 | /* read input file into memory */ | ||
327 | buf = (unsigned char*)malloc(filestat.st_size * sizeof(unsigned char)); | ||
328 | if(!buf) /* error out if malloc() failed */ | ||
329 | return -1; | ||
330 | in = fopen(aiff, "rb"); | ||
331 | if(fread(buf, 1, filestat.st_size, in) < filestat.st_size) { | ||
332 | printf("could not read file: not enought bytes read\n"); | ||
333 | fclose(in); | ||
334 | free(buf); | ||
335 | return -1; | ||
336 | } | ||
337 | fclose(in); | ||
338 | |||
339 | /* check input file format */ | ||
340 | if(memcmp(buf, "FORM", 4) | memcmp(&buf[8], "AIFF", 4)) { | ||
341 | printf("No valid AIFF header found.\n"); | ||
342 | free(buf); | ||
343 | return -1; | ||
344 | } | ||
345 | /* read COMM chunk */ | ||
346 | unsigned char* commstart = &buf[12]; | ||
347 | struct commchunk comm; | ||
348 | if(memcmp(commstart, "COMM", 4)) { | ||
349 | printf("COMM chunk not at beginning.\n"); | ||
350 | free(buf); | ||
351 | return -1; | ||
352 | } | ||
353 | comm.chunksize = be2u32(&commstart[4]); | ||
354 | comm.channels = be2u16(&commstart[8]); | ||
355 | comm.frames = be2u32(&commstart[10]); | ||
356 | comm.size = be2u16(&commstart[14]); | ||
357 | comm.rate = extended2int(&commstart[16]); | ||
358 | |||
359 | /* find SSND as next chunk */ | ||
360 | unsigned char* ssndstart = commstart + 8 + comm.chunksize; | ||
361 | while(memcmp(ssndstart, "SSND", 4) && ssndstart < (buf + filestat.st_size)) { | ||
362 | printf("Skipping chunk.\n"); | ||
363 | ssndstart += be2u32(&ssndstart[4]) + 8; | ||
364 | } | ||
365 | if(ssndstart > (buf + filestat.st_size)) { | ||
366 | free(buf); | ||
367 | return -1; | ||
368 | } | ||
369 | |||
370 | struct ssndchunk ssnd; | ||
371 | ssnd.chunksize = be2u32(&ssndstart[4]); | ||
372 | ssnd.offset = be2u32(&ssndstart[8]); | ||
373 | ssnd.blocksize = be2u32(&ssndstart[12]); | ||
374 | |||
375 | /* Calculate the total length of the resulting RIFF chunk. | ||
376 | * The length is given by frames * samples * bytes/sample. | ||
377 | * We need to add: | ||
378 | * - 16 bytes: fmt chunk header | ||
379 | * - 8 bytes: data chunk header | ||
380 | * - 4 bytes: wave chunk identifier | ||
381 | */ | ||
382 | out = fopen(wav, "wb+"); | ||
383 | |||
384 | /* write the wav header */ | ||
385 | unsigned short blocksize = comm.channels * (comm.size >> 3); | ||
386 | unsigned long rifflen = blocksize * comm.frames + 28; | ||
387 | fwrite("RIFF", 1, 4, out); | ||
388 | fwrite(u32tobuf(obuf, rifflen), 1, 4, out); | ||
389 | fwrite("WAVE", 1, 4, out); | ||
390 | |||
391 | /* write the fmt chunk and chunk size (always 16) */ | ||
392 | /* write fmt chunk header: | ||
393 | * header, size (always 0x10, format code (always 0x0001) | ||
394 | */ | ||
395 | fwrite("fmt \x10\x00\x00\x00\x01\x00", 1, 10, out); | ||
396 | /* number of channels (2 bytes) */ | ||
397 | fwrite(u16tobuf(obuf, comm.channels), 1, 2, out); | ||
398 | /* sampling rate (4 bytes) */ | ||
399 | fwrite(u32tobuf(obuf, comm.rate), 1, 4, out); | ||
400 | |||
401 | /* data rate, i.e. bytes/sec */ | ||
402 | fwrite(u32tobuf(obuf, comm.rate * blocksize), 1, 4, out); | ||
403 | |||
404 | /* data block size */ | ||
405 | fwrite(u16tobuf(obuf, blocksize), 1, 2, out); | ||
406 | |||
407 | /* bits per sample */ | ||
408 | fwrite(u16tobuf(obuf, comm.size), 1, 2, out); | ||
409 | |||
410 | /* write the data chunk */ | ||
411 | /* chunk id */ | ||
412 | fwrite("data", 1, 4, out); | ||
413 | /* chunk size: 4 bytes. */ | ||
414 | unsigned long cs = blocksize * comm.frames; | ||
415 | fwrite(u32tobuf(obuf, cs), 1, 4, out); | ||
416 | |||
417 | /* write data */ | ||
418 | unsigned char* data = ssndstart; | ||
419 | unsigned long pos = ssnd.chunksize; | ||
420 | /* byteswap if samples are 16 bit */ | ||
421 | if(comm.size == 16) { | ||
422 | while(pos) { | ||
423 | obuf[1] = *data++ & 0xff; | ||
424 | obuf[0] = *data++ & 0xff; | ||
425 | fwrite(obuf, 1, 2, out); | ||
426 | pos -= 2; | ||
427 | } | ||
428 | } | ||
429 | /* 8 bit samples have need no conversion so we can bulk copy. | ||
430 | * Everything that is not 16 bit is considered 8. */ | ||
431 | else { | ||
432 | fwrite(data, 1, pos, out); | ||
433 | } | ||
434 | /* number of bytes has to be even, even if chunksize is not. */ | ||
435 | if(cs % 2) { | ||
436 | fwrite(obuf, 1, 1, out); | ||
437 | } | ||
438 | |||
439 | fclose(out); | ||
440 | free(buf); | ||
441 | return 0; | ||
442 | } | ||
443 | |||
diff --git a/utils/rbutilqt/base/ttscarbon.h b/utils/rbutilqt/base/ttscarbon.h new file mode 100644 index 0000000000..2e9e84aa7d --- /dev/null +++ b/utils/rbutilqt/base/ttscarbon.h | |||
@@ -0,0 +1,73 @@ | |||
1 | /*************************************************************************** | ||
2 | * __________ __ ___. | ||
3 | * Open \______ \ ____ ____ | | _\_ |__ _______ ___ | ||
4 | * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / | ||
5 | * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < | ||
6 | * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ | ||
7 | * \/ \/ \/ \/ \/ | ||
8 | * | ||
9 | * Copyright (C) 2010 by Dominik Riebeling | ||
10 | * | ||
11 | * All files in this archive are subject to the GNU General Public License. | ||
12 | * See the file COPYING in the source tree root for full license agreement. | ||
13 | * | ||
14 | * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY | ||
15 | * KIND, either express or implied. | ||
16 | * | ||
17 | ****************************************************************************/ | ||
18 | |||
19 | #ifndef TTSCARBON_H | ||
20 | #define TTSCARBON_H | ||
21 | |||
22 | #include <QtCore> | ||
23 | #include "ttsbase.h" | ||
24 | |||
25 | #include <Carbon/Carbon.h> | ||
26 | #include <inttypes.h> | ||
27 | |||
28 | class TTSCarbon : public TTSBase | ||
29 | { | ||
30 | Q_OBJECT | ||
31 | //! Enum to identify the settings | ||
32 | enum ConfigValuesCarbon | ||
33 | { | ||
34 | ConfigVoice, | ||
35 | ConfigSpeed, | ||
36 | ConfigPitch | ||
37 | }; | ||
38 | public: | ||
39 | TTSCarbon(QObject *parent = NULL); | ||
40 | |||
41 | //! Child class should generate a clip | ||
42 | TTSStatus voice(QString text, QString wavfile, QString* errStr); | ||
43 | //! Child class should do startup | ||
44 | bool start(QString *errStr); | ||
45 | //! child class should stop | ||
46 | bool stop() ; | ||
47 | QString voiceVendor(void) { return QString(); } | ||
48 | |||
49 | // configuration | ||
50 | //! Child class should return true, when configuration is good | ||
51 | bool configOk(); | ||
52 | //! Child class should generate and insertSetting(..) its settings | ||
53 | void generateSettings(); | ||
54 | //! Child class should commit the Settings to permanent storage | ||
55 | void saveSettings(); | ||
56 | |||
57 | Capabilities capabilities(); | ||
58 | |||
59 | private: | ||
60 | SpeechChannel m_channel; | ||
61 | CFStringBuiltInEncodings m_voiceScript; | ||
62 | |||
63 | unsigned long be2u32(unsigned char* buf); | ||
64 | unsigned long be2u16(unsigned char* buf); | ||
65 | unsigned char* u32tobuf(unsigned char* buf, uint32_t val); | ||
66 | unsigned char* u16tobuf(unsigned char* buf, uint16_t val); | ||
67 | unsigned int extended2int(unsigned char* buf); | ||
68 | int convertAiffToWav(const char* aiff, const char* wav); | ||
69 | |||
70 | }; | ||
71 | |||
72 | #endif // TTSCARBON_H | ||
73 | |||
diff --git a/utils/rbutilqt/base/ttsespeak.h b/utils/rbutilqt/base/ttsespeak.h new file mode 100644 index 0000000000..afe19fbdac --- /dev/null +++ b/utils/rbutilqt/base/ttsespeak.h | |||
@@ -0,0 +1,42 @@ | |||
1 | /*************************************************************************** | ||
2 | * __________ __ ___. | ||
3 | * Open \______ \ ____ ____ | | _\_ |__ _______ ___ | ||
4 | * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / | ||
5 | * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < | ||
6 | * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ | ||
7 | * \/ \/ \/ \/ \/ | ||
8 | * | ||
9 | * Copyright (C) 2012 by Dominik Riebeling | ||
10 | * | ||
11 | * This program is free software; you can redistribute it and/or | ||
12 | * modify it under the terms of the GNU General Public License | ||
13 | * as published by the Free Software Foundation; either version 2 | ||
14 | * of the License, or (at your option) any later version. | ||
15 | * | ||
16 | * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY | ||
17 | * KIND, either express or implied. | ||
18 | * | ||
19 | ****************************************************************************/ | ||
20 | |||
21 | #ifndef TTSESPEAK_H | ||
22 | #define TTSESPEAK_H | ||
23 | |||
24 | #include <QtCore> | ||
25 | #include "ttsexes.h" | ||
26 | |||
27 | class TTSEspeak : public TTSExes | ||
28 | { | ||
29 | Q_OBJECT | ||
30 | public: | ||
31 | TTSEspeak(QObject* parent=nullptr) : TTSExes(parent) | ||
32 | { | ||
33 | m_name = "espeak"; | ||
34 | |||
35 | /* default to espeak */ | ||
36 | m_TTSTemplate = "\"%exe\" %options -w \"%wavfile\" -- \"%text\""; | ||
37 | m_TTSSpeakTemplate = "\"%exe\" %options -- \"%text\""; | ||
38 | m_capabilities = TTSBase::CanSpeak; | ||
39 | } | ||
40 | }; | ||
41 | |||
42 | #endif | ||
diff --git a/utils/rbutilqt/base/ttsespeakng.h b/utils/rbutilqt/base/ttsespeakng.h new file mode 100644 index 0000000000..55aba62e7d --- /dev/null +++ b/utils/rbutilqt/base/ttsespeakng.h | |||
@@ -0,0 +1,41 @@ | |||
1 | /*************************************************************************** | ||
2 | * __________ __ ___. | ||
3 | * Open \______ \ ____ ____ | | _\_ |__ _______ ___ | ||
4 | * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / | ||
5 | * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < | ||
6 | * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ | ||
7 | * \/ \/ \/ \/ \/ | ||
8 | * | ||
9 | * Copyright (C) 2020 by Solomon Peachy | ||
10 | * | ||
11 | * This program is free software; you can redistribute it and/or | ||
12 | * modify it under the terms of the GNU General Public License | ||
13 | * as published by the Free Software Foundation; either version 2 | ||
14 | * of the License, or (at your option) any later version. | ||
15 | * | ||
16 | * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY | ||
17 | * KIND, either express or implied. | ||
18 | * | ||
19 | ****************************************************************************/ | ||
20 | |||
21 | #ifndef TTSESPEAKNG_H | ||
22 | #define TTSESPEAKNG_H | ||
23 | |||
24 | #include <QtCore> | ||
25 | #include "ttsexes.h" | ||
26 | |||
27 | class TTSEspeakNG : public TTSExes | ||
28 | { | ||
29 | Q_OBJECT | ||
30 | public: | ||
31 | TTSEspeakNG(QObject* parent=nullptr) : TTSExes(parent) | ||
32 | { | ||
33 | m_name = "espeak-ng"; | ||
34 | |||
35 | m_TTSTemplate = "\"%exe\" %options -w \"%wavfile\" -- \"%text\""; | ||
36 | m_TTSSpeakTemplate = "\"%exe\" %options -- \"%text\""; | ||
37 | m_capabilities = TTSBase::CanSpeak; | ||
38 | } | ||
39 | }; | ||
40 | |||
41 | #endif | ||
diff --git a/utils/rbutilqt/base/ttsexes.cpp b/utils/rbutilqt/base/ttsexes.cpp new file mode 100644 index 0000000000..446725968f --- /dev/null +++ b/utils/rbutilqt/base/ttsexes.cpp | |||
@@ -0,0 +1,127 @@ | |||
1 | /*************************************************************************** | ||
2 | * __________ __ ___. | ||
3 | * Open \______ \ ____ ____ | | _\_ |__ _______ ___ | ||
4 | * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / | ||
5 | * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < | ||
6 | * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ | ||
7 | * \/ \/ \/ \/ \/ | ||
8 | * | ||
9 | * Copyright (C) 2007 by Dominik Wenger | ||
10 | * | ||
11 | * All files in this archive are subject to the GNU General Public License. | ||
12 | * See the file COPYING in the source tree root for full license agreement. | ||
13 | * | ||
14 | * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY | ||
15 | * KIND, either express or implied. | ||
16 | * | ||
17 | ****************************************************************************/ | ||
18 | |||
19 | #include <QtCore> | ||
20 | #include "ttsexes.h" | ||
21 | #include "utils.h" | ||
22 | #include "rbsettings.h" | ||
23 | #include "Logger.h" | ||
24 | |||
25 | TTSExes::TTSExes(QObject* parent) : TTSBase(parent) | ||
26 | { | ||
27 | /* default to espeak */ | ||
28 | m_name = "espeak"; | ||
29 | m_capabilities = TTSBase::CanSpeak; | ||
30 | m_TTSTemplate = "\"%exe\" %options -w \"%wavfile\" -- \"%text\""; | ||
31 | m_TTSSpeakTemplate = "\"%exe\" %options -- \"%text\""; | ||
32 | } | ||
33 | |||
34 | |||
35 | TTSBase::Capabilities TTSExes::capabilities() | ||
36 | { | ||
37 | return m_capabilities; | ||
38 | } | ||
39 | |||
40 | void TTSExes::generateSettings() | ||
41 | { | ||
42 | loadSettings(); | ||
43 | insertSetting(eEXEPATH, new EncTtsSetting(this, EncTtsSetting::eSTRING, | ||
44 | tr("Path to TTS engine:"), m_TTSexec, EncTtsSetting::eBROWSEBTN)); | ||
45 | insertSetting(eOPTIONS, new EncTtsSetting(this, EncTtsSetting::eSTRING, | ||
46 | tr("TTS engine options:"), m_TTSOpts)); | ||
47 | } | ||
48 | |||
49 | void TTSExes::saveSettings() | ||
50 | { | ||
51 | RbSettings::setSubValue(m_name, RbSettings::TtsPath, | ||
52 | getSetting(eEXEPATH)->current().toString()); | ||
53 | RbSettings::setSubValue(m_name, RbSettings::TtsOptions, | ||
54 | getSetting(eOPTIONS)->current().toString()); | ||
55 | RbSettings::sync(); | ||
56 | } | ||
57 | |||
58 | |||
59 | void TTSExes::loadSettings(void) | ||
60 | { | ||
61 | m_TTSexec = RbSettings::subValue(m_name, RbSettings::TtsPath).toString(); | ||
62 | if(m_TTSexec.isEmpty()) m_TTSexec = Utils::findExecutable(m_name); | ||
63 | m_TTSOpts = RbSettings::subValue(m_name, RbSettings::TtsOptions).toString(); | ||
64 | } | ||
65 | |||
66 | |||
67 | bool TTSExes::start(QString *errStr) | ||
68 | { | ||
69 | loadSettings(); | ||
70 | |||
71 | QFileInfo tts(m_TTSexec); | ||
72 | if(tts.exists()) | ||
73 | { | ||
74 | return true; | ||
75 | } | ||
76 | else | ||
77 | { | ||
78 | *errStr = tr("TTS executable not found"); | ||
79 | return false; | ||
80 | } | ||
81 | } | ||
82 | |||
83 | TTSStatus TTSExes::voice(QString text, QString wavfile, QString *errStr) | ||
84 | { | ||
85 | (void) errStr; | ||
86 | QString execstring; | ||
87 | if(wavfile.isEmpty() && m_capabilities & TTSBase::CanSpeak) { | ||
88 | if(m_TTSSpeakTemplate.isEmpty()) { | ||
89 | LOG_ERROR() << "internal error: TTS announces CanSpeak " | ||
90 | "but template empty!"; | ||
91 | return FatalError; | ||
92 | } | ||
93 | execstring = m_TTSSpeakTemplate; | ||
94 | } | ||
95 | else if(wavfile.isEmpty()) { | ||
96 | LOG_ERROR() << "no output file passed to voice() " | ||
97 | "but TTS can't speak directly."; | ||
98 | return FatalError; | ||
99 | } | ||
100 | else { | ||
101 | execstring = m_TTSTemplate; | ||
102 | } | ||
103 | |||
104 | execstring.replace("%exe",m_TTSexec); | ||
105 | execstring.replace("%options",m_TTSOpts); | ||
106 | execstring.replace("%wavfile",wavfile); | ||
107 | execstring.replace("%text",text); | ||
108 | |||
109 | QProcess::execute(execstring); | ||
110 | |||
111 | if(!wavfile.isEmpty() && !QFileInfo(wavfile).isFile()) { | ||
112 | LOG_ERROR() << "output file does not exist:" << wavfile; | ||
113 | return FatalError; | ||
114 | } | ||
115 | return NoError; | ||
116 | |||
117 | } | ||
118 | |||
119 | bool TTSExes::configOk() | ||
120 | { | ||
121 | loadSettings(); | ||
122 | if (QFileInfo::exists(m_TTSexec)) | ||
123 | return true; | ||
124 | else | ||
125 | return false; | ||
126 | } | ||
127 | |||
diff --git a/utils/rbutilqt/base/ttsexes.h b/utils/rbutilqt/base/ttsexes.h new file mode 100644 index 0000000000..5707c827fe --- /dev/null +++ b/utils/rbutilqt/base/ttsexes.h | |||
@@ -0,0 +1,61 @@ | |||
1 | /*************************************************************************** | ||
2 | * __________ __ ___. | ||
3 | * Open \______ \ ____ ____ | | _\_ |__ _______ ___ | ||
4 | * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / | ||
5 | * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < | ||
6 | * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ | ||
7 | * \/ \/ \/ \/ \/ | ||
8 | * | ||
9 | * Copyright (C) 2009 by Dominik Wenger | ||
10 | * | ||
11 | * This program is free software; you can redistribute it and/or | ||
12 | * modify it under the terms of the GNU General Public License | ||
13 | * as published by the Free Software Foundation; either version 2 | ||
14 | * of the License, or (at your option) any later version. | ||
15 | * | ||
16 | * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY | ||
17 | * KIND, either express or implied. | ||
18 | * | ||
19 | ****************************************************************************/ | ||
20 | |||
21 | #ifndef TTSEXES_H | ||
22 | #define TTSEXES_H | ||
23 | |||
24 | #include <QtCore> | ||
25 | #include "ttsbase.h" | ||
26 | |||
27 | class TTSExes : public TTSBase | ||
28 | { | ||
29 | enum ESettings | ||
30 | { | ||
31 | eEXEPATH, | ||
32 | eOPTIONS | ||
33 | }; | ||
34 | |||
35 | Q_OBJECT | ||
36 | public: | ||
37 | TTSExes(QObject* parent=nullptr); | ||
38 | TTSStatus voice(QString text, QString wavfile, QString *errStr); | ||
39 | bool start(QString *errStr); | ||
40 | bool stop() {return true;} | ||
41 | QString voiceVendor(void) { return QString(); } | ||
42 | Capabilities capabilities(); | ||
43 | |||
44 | // for settings | ||
45 | void generateSettings(); | ||
46 | void saveSettings(); | ||
47 | bool configOk(); | ||
48 | |||
49 | private: | ||
50 | void loadSettings(void); | ||
51 | |||
52 | protected: | ||
53 | QString m_TTSTemplate; | ||
54 | QString m_TTSSpeakTemplate; | ||
55 | QString m_name; | ||
56 | QString m_TTSexec; | ||
57 | QString m_TTSOpts; | ||
58 | TTSBase::Capabilities m_capabilities; | ||
59 | }; | ||
60 | |||
61 | #endif | ||
diff --git a/utils/rbutilqt/base/ttsfestival.cpp b/utils/rbutilqt/base/ttsfestival.cpp new file mode 100644 index 0000000000..d0ca400909 --- /dev/null +++ b/utils/rbutilqt/base/ttsfestival.cpp | |||
@@ -0,0 +1,412 @@ | |||
1 | /*************************************************************************** | ||
2 | * __________ __ ___. | ||
3 | * Open \______ \ ____ ____ | | _\_ |__ _______ ___ | ||
4 | * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / | ||
5 | * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < | ||
6 | * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ | ||
7 | * \/ \/ \/ \/ \/ | ||
8 | * | ||
9 | * Copyright (C) 2007 by Dominik Wenger | ||
10 | * | ||
11 | * All files in this archive are subject to the GNU General Public License. | ||
12 | * See the file COPYING in the source tree root for full license agreement. | ||
13 | * | ||
14 | * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY | ||
15 | * KIND, either express or implied. | ||
16 | * | ||
17 | ****************************************************************************/ | ||
18 | |||
19 | #include <QtCore> | ||
20 | #include <QTcpSocket> | ||
21 | |||
22 | #include "ttsfestival.h" | ||
23 | #include "utils.h" | ||
24 | #include "rbsettings.h" | ||
25 | #include "Logger.h" | ||
26 | |||
27 | TTSFestival::~TTSFestival() | ||
28 | { | ||
29 | LOG_INFO() << "Destroying instance"; | ||
30 | stop(); | ||
31 | } | ||
32 | |||
33 | TTSBase::Capabilities TTSFestival::capabilities() | ||
34 | { | ||
35 | return RunInParallel; | ||
36 | } | ||
37 | |||
38 | void TTSFestival::generateSettings() | ||
39 | { | ||
40 | // server path | ||
41 | QString exepath = RbSettings::subValue("festival-server", | ||
42 | RbSettings::TtsPath).toString(); | ||
43 | if(exepath == "" ) exepath = Utils::findExecutable("festival"); | ||
44 | insertSetting(eSERVERPATH,new EncTtsSetting(this, | ||
45 | EncTtsSetting::eSTRING, "Path to Festival server:", | ||
46 | exepath,EncTtsSetting::eBROWSEBTN)); | ||
47 | |||
48 | // client path | ||
49 | QString clientpath = RbSettings::subValue("festival-client", | ||
50 | RbSettings::TtsPath).toString(); | ||
51 | if(clientpath == "" ) clientpath = Utils::findExecutable("festival_client"); | ||
52 | insertSetting(eCLIENTPATH,new EncTtsSetting(this,EncTtsSetting::eSTRING, | ||
53 | tr("Path to Festival client:"), | ||
54 | clientpath,EncTtsSetting::eBROWSEBTN)); | ||
55 | |||
56 | // voice | ||
57 | EncTtsSetting* setting = new EncTtsSetting(this, | ||
58 | EncTtsSetting::eSTRINGLIST, tr("Voice:"), | ||
59 | RbSettings::subValue("festival", RbSettings::TtsVoice), | ||
60 | getVoiceList(), EncTtsSetting::eREFRESHBTN); | ||
61 | connect(setting, &EncTtsSetting::refresh, | ||
62 | this, &TTSFestival::updateVoiceList); | ||
63 | connect(setting, &EncTtsSetting::dataChanged, | ||
64 | this, &TTSFestival::clearVoiceDescription); | ||
65 | insertSetting(eVOICE,setting); | ||
66 | |||
67 | //voice description | ||
68 | setting = new EncTtsSetting(this,EncTtsSetting::eREADONLYSTRING, | ||
69 | tr("Voice description:"),"",EncTtsSetting::eREFRESHBTN); | ||
70 | connect(setting, &EncTtsSetting::refresh, | ||
71 | this, &TTSFestival::updateVoiceDescription); | ||
72 | insertSetting(eVOICEDESC,setting); | ||
73 | } | ||
74 | |||
75 | void TTSFestival::saveSettings() | ||
76 | { | ||
77 | //save settings in user config | ||
78 | RbSettings::setSubValue("festival-server", | ||
79 | RbSettings::TtsPath,getSetting(eSERVERPATH)->current().toString()); | ||
80 | RbSettings::setSubValue("festival-client", | ||
81 | RbSettings::TtsPath,getSetting(eCLIENTPATH)->current().toString()); | ||
82 | RbSettings::setSubValue("festival", | ||
83 | RbSettings::TtsVoice,getSetting(eVOICE)->current().toString()); | ||
84 | |||
85 | RbSettings::sync(); | ||
86 | } | ||
87 | |||
88 | void TTSFestival::updateVoiceDescription() | ||
89 | { | ||
90 | // get voice Info with current voice and path | ||
91 | currentPath = getSetting(eSERVERPATH)->current().toString(); | ||
92 | QString info = getVoiceInfo(getSetting(eVOICE)->current().toString()); | ||
93 | currentPath = ""; | ||
94 | |||
95 | getSetting(eVOICEDESC)->setCurrent(info); | ||
96 | } | ||
97 | |||
98 | void TTSFestival::clearVoiceDescription() | ||
99 | { | ||
100 | getSetting(eVOICEDESC)->setCurrent(""); | ||
101 | } | ||
102 | |||
103 | void TTSFestival::updateVoiceList() | ||
104 | { | ||
105 | currentPath = getSetting(eSERVERPATH)->current().toString(); | ||
106 | QStringList voiceList = getVoiceList(); | ||
107 | currentPath = ""; | ||
108 | |||
109 | getSetting(eVOICE)->setList(voiceList); | ||
110 | if(voiceList.size() > 0) getSetting(eVOICE)->setCurrent(voiceList.at(0)); | ||
111 | else getSetting(eVOICE)->setCurrent(""); | ||
112 | } | ||
113 | |||
114 | void TTSFestival::startServer() | ||
115 | { | ||
116 | if(!configOk()) | ||
117 | return; | ||
118 | |||
119 | if(serverProcess.state() != QProcess::Running) | ||
120 | { | ||
121 | QString path; | ||
122 | /* currentPath is set by the GUI - if it's set, it is the currently set | ||
123 | path in the configuration GUI; if it's not set, use the saved path */ | ||
124 | if (currentPath == "") | ||
125 | path = RbSettings::subValue("festival-server",RbSettings::TtsPath).toString(); | ||
126 | else | ||
127 | path = currentPath; | ||
128 | |||
129 | serverProcess.start(QString("%1 --server").arg(path)); | ||
130 | serverProcess.waitForStarted(); | ||
131 | |||
132 | /* A friendlier version of a spinlock */ | ||
133 | while (serverProcess.processId() == 0 && serverProcess.state() != QProcess::Running) | ||
134 | QCoreApplication::processEvents(QEventLoop::AllEvents, 50); | ||
135 | |||
136 | if(serverProcess.state() == QProcess::Running) | ||
137 | LOG_INFO() << "Server is up and running"; | ||
138 | else | ||
139 | LOG_ERROR() << "Server failed to start, state:" | ||
140 | << serverProcess.state(); | ||
141 | } | ||
142 | } | ||
143 | |||
144 | bool TTSFestival::ensureServerRunning() | ||
145 | { | ||
146 | if(serverProcess.state() != QProcess::Running) | ||
147 | { | ||
148 | startServer(); | ||
149 | } | ||
150 | return serverProcess.state() == QProcess::Running; | ||
151 | } | ||
152 | |||
153 | bool TTSFestival::start(QString* errStr) | ||
154 | { | ||
155 | LOG_INFO() << "Starting server with voice" | ||
156 | << RbSettings::subValue("festival", RbSettings::TtsVoice).toString(); | ||
157 | |||
158 | bool running = ensureServerRunning(); | ||
159 | if (!RbSettings::subValue("festival",RbSettings::TtsVoice).toString().isEmpty()) | ||
160 | { | ||
161 | /* There's no harm in using both methods to set the voice .. */ | ||
162 | QString voiceSelect = QString("(voice.select '%1)\n") | ||
163 | .arg(RbSettings::subValue("festival", RbSettings::TtsVoice).toString()); | ||
164 | queryServer(voiceSelect, 3000); | ||
165 | |||
166 | if(prologFile.open()) | ||
167 | { | ||
168 | prologFile.write(voiceSelect.toLatin1()); | ||
169 | prologFile.close(); | ||
170 | prologPath = QFileInfo(prologFile).absoluteFilePath(); | ||
171 | LOG_INFO() << "Prolog created at" << prologPath; | ||
172 | } | ||
173 | |||
174 | } | ||
175 | |||
176 | if (!running) | ||
177 | (*errStr) = "Festival could not be started"; | ||
178 | return running; | ||
179 | } | ||
180 | |||
181 | bool TTSFestival::stop() | ||
182 | { | ||
183 | serverProcess.terminate(); | ||
184 | serverProcess.kill(); | ||
185 | |||
186 | return true; | ||
187 | } | ||
188 | |||
189 | TTSStatus TTSFestival::voice(QString text, QString wavfile, QString* errStr) | ||
190 | { | ||
191 | LOG_INFO() << "Voicing" << text << "->" << wavfile; | ||
192 | |||
193 | QString path = RbSettings::subValue("festival-client", | ||
194 | RbSettings::TtsPath).toString(); | ||
195 | QString cmd = QString("%1 --server localhost --otype riff --ttw --withlisp" | ||
196 | " --output \"%2\" --prolog \"%3\" - ").arg(path).arg(wavfile).arg(prologPath); | ||
197 | LOG_INFO() << "Client cmd:" << cmd; | ||
198 | |||
199 | QProcess clientProcess; | ||
200 | clientProcess.start(cmd); | ||
201 | clientProcess.write(QString("%1.\n").arg(text).toLatin1()); | ||
202 | clientProcess.waitForBytesWritten(); | ||
203 | clientProcess.closeWriteChannel(); | ||
204 | clientProcess.waitForReadyRead(); | ||
205 | QString response = clientProcess.readAll(); | ||
206 | response = response.trimmed(); | ||
207 | if(!response.contains("Utterance")) | ||
208 | { | ||
209 | LOG_WARNING() << "Could not voice string: " << response; | ||
210 | *errStr = tr("engine could not voice string"); | ||
211 | return Warning; | ||
212 | /* do not stop the voicing process because of a single string | ||
213 | TODO: needs proper settings */ | ||
214 | } | ||
215 | clientProcess.closeReadChannel(QProcess::StandardError); | ||
216 | clientProcess.closeReadChannel(QProcess::StandardOutput); | ||
217 | clientProcess.terminate(); | ||
218 | clientProcess.kill(); | ||
219 | |||
220 | return NoError; | ||
221 | } | ||
222 | |||
223 | bool TTSFestival::configOk() | ||
224 | { | ||
225 | bool ret; | ||
226 | if (currentPath == "") | ||
227 | { | ||
228 | QString serverPath = RbSettings::subValue("festival-server", | ||
229 | RbSettings::TtsPath).toString(); | ||
230 | QString clientPath = RbSettings::subValue("festival-client", | ||
231 | RbSettings::TtsPath).toString(); | ||
232 | |||
233 | ret = QFileInfo(serverPath).isExecutable() && | ||
234 | QFileInfo(clientPath).isExecutable(); | ||
235 | if(RbSettings::subValue("festival",RbSettings::TtsVoice).toString().size() > 0 | ||
236 | && voices.size() > 0) | ||
237 | ret = ret && (voices.indexOf(RbSettings::subValue("festival", | ||
238 | RbSettings::TtsVoice).toString()) != -1); | ||
239 | } | ||
240 | else /* If we're currently configuring the server, we need to know that | ||
241 | the entered path is valid */ | ||
242 | ret = QFileInfo(currentPath).isExecutable(); | ||
243 | |||
244 | return ret; | ||
245 | } | ||
246 | |||
247 | QStringList TTSFestival::getVoiceList() | ||
248 | { | ||
249 | if(!configOk()) | ||
250 | return QStringList(); | ||
251 | |||
252 | if(voices.size() > 0) | ||
253 | { | ||
254 | LOG_INFO() << "Using voice cache"; | ||
255 | return voices; | ||
256 | } | ||
257 | |||
258 | QString response = queryServer("(voice.list)", 10000); | ||
259 | |||
260 | // get the 2nd line. It should be (<voice_name>, <voice_name>) | ||
261 | response = response.mid(response.indexOf('\n') + 1, -1); | ||
262 | response = response.left(response.indexOf('\n')).trimmed(); | ||
263 | |||
264 | voices = response.mid(1, response.size()-2).split(' '); | ||
265 | |||
266 | voices.sort(); | ||
267 | if (voices.size() == 1 && voices[0].size() == 0) | ||
268 | voices.removeAt(0); | ||
269 | if (voices.size() > 0) | ||
270 | LOG_INFO() << "Voices:" << voices; | ||
271 | else | ||
272 | LOG_WARNING() << "No voices. Response was:" << response; | ||
273 | |||
274 | return voices; | ||
275 | } | ||
276 | |||
277 | QString TTSFestival::getVoiceInfo(QString voice) | ||
278 | { | ||
279 | if(!configOk()) | ||
280 | return ""; | ||
281 | |||
282 | if(!getVoiceList().contains(voice)) | ||
283 | return ""; | ||
284 | |||
285 | if(voiceDescriptions.contains(voice)) | ||
286 | return voiceDescriptions[voice]; | ||
287 | |||
288 | QString response = queryServer(QString("(voice.description '%1)").arg(voice), | ||
289 | 10000); | ||
290 | |||
291 | if (response == "") | ||
292 | { | ||
293 | voiceDescriptions[voice]=tr("No description available"); | ||
294 | } | ||
295 | else | ||
296 | { | ||
297 | response = response.remove(QRegExp("(description \"*\")", | ||
298 | Qt::CaseInsensitive, QRegExp::Wildcard)); | ||
299 | LOG_INFO() << "voiceInfo w/o descr:" << response; | ||
300 | response = response.remove(')'); | ||
301 | #if QT_VERSION >= 0x050e00 | ||
302 | QStringList responseLines = response.split('(', Qt::SkipEmptyParts); | ||
303 | #else | ||
304 | QStringList responseLines = response.split('(', QString::SkipEmptyParts); | ||
305 | #endif | ||
306 | responseLines.removeAt(0); // the voice name itself | ||
307 | |||
308 | QString description; | ||
309 | foreach(QString line, responseLines) | ||
310 | { | ||
311 | line = line.remove('('); | ||
312 | line = line.simplified(); | ||
313 | |||
314 | line[0] = line[0].toUpper(); // capitalize the key | ||
315 | |||
316 | int firstSpace = line.indexOf(' '); | ||
317 | if (firstSpace > 0) | ||
318 | { | ||
319 | // add a colon between the key and the value | ||
320 | line = line.insert(firstSpace, ':'); | ||
321 | // capitalize the value | ||
322 | line[firstSpace+2] = line[firstSpace+2].toUpper(); | ||
323 | } | ||
324 | |||
325 | description += line + "\n"; | ||
326 | } | ||
327 | voiceDescriptions[voice] = description.trimmed(); | ||
328 | } | ||
329 | |||
330 | return voiceDescriptions[voice]; | ||
331 | } | ||
332 | |||
333 | QString TTSFestival::queryServer(QString query, int timeout) | ||
334 | { | ||
335 | if(!configOk()) | ||
336 | return ""; | ||
337 | |||
338 | // this operation could take some time | ||
339 | emit busy(); | ||
340 | |||
341 | LOG_INFO() << "queryServer with" << query; | ||
342 | |||
343 | if (!ensureServerRunning()) | ||
344 | { | ||
345 | LOG_ERROR() << "queryServer: ensureServerRunning failed"; | ||
346 | emit busyEnd(); | ||
347 | return ""; | ||
348 | } | ||
349 | |||
350 | QString response; | ||
351 | |||
352 | QDateTime endTime; | ||
353 | if(timeout > 0) | ||
354 | endTime = QDateTime::currentDateTime().addMSecs(timeout); | ||
355 | |||
356 | /* Festival is *extremely* unreliable. Although at this | ||
357 | * point we are sure that SIOD is accepting commands, | ||
358 | * we might end up with an empty response. Hence, the loop. | ||
359 | */ | ||
360 | while(true) | ||
361 | { | ||
362 | QCoreApplication::processEvents(QEventLoop::AllEvents, 50); | ||
363 | QTcpSocket socket; | ||
364 | |||
365 | socket.connectToHost("localhost", 1314); | ||
366 | socket.waitForConnected(); | ||
367 | |||
368 | if(socket.state() == QAbstractSocket::ConnectedState) | ||
369 | { | ||
370 | socket.write(QString("%1\n").arg(query).toLatin1()); | ||
371 | socket.waitForBytesWritten(); | ||
372 | socket.waitForReadyRead(); | ||
373 | |||
374 | response = socket.readAll().trimmed(); | ||
375 | |||
376 | if (response != "LP" && response != "") | ||
377 | break; | ||
378 | } | ||
379 | socket.abort(); | ||
380 | socket.disconnectFromHost(); | ||
381 | |||
382 | if(timeout > 0 && QDateTime::currentDateTime() >= endTime) | ||
383 | { | ||
384 | emit busyEnd(); | ||
385 | return ""; | ||
386 | } | ||
387 | /* make sure we wait a little as we don't want to flood the server | ||
388 | * with requests */ | ||
389 | QDateTime tmpEndTime = QDateTime::currentDateTime().addMSecs(500); | ||
390 | while(QDateTime::currentDateTime() < tmpEndTime) | ||
391 | QCoreApplication::processEvents(QEventLoop::AllEvents); | ||
392 | } | ||
393 | if(response == "nil") | ||
394 | { | ||
395 | emit busyEnd(); | ||
396 | return ""; | ||
397 | } | ||
398 | |||
399 | QStringList lines = response.split('\n'); | ||
400 | if(lines.size() > 2) | ||
401 | { | ||
402 | lines.removeFirst(); /* should be LP */ | ||
403 | lines.removeLast(); /* should be ft_StUfF_keyOK */ | ||
404 | } | ||
405 | else | ||
406 | LOG_ERROR() << "Response too short:" << response; | ||
407 | |||
408 | emit busyEnd(); | ||
409 | return lines.join("\n"); | ||
410 | |||
411 | } | ||
412 | |||
diff --git a/utils/rbutilqt/base/ttsfestival.h b/utils/rbutilqt/base/ttsfestival.h new file mode 100644 index 0000000000..5f6dc13ab5 --- /dev/null +++ b/utils/rbutilqt/base/ttsfestival.h | |||
@@ -0,0 +1,72 @@ | |||
1 | /*************************************************************************** | ||
2 | * __________ __ ___. | ||
3 | * Open \______ \ ____ ____ | | _\_ |__ _______ ___ | ||
4 | * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / | ||
5 | * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < | ||
6 | * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ | ||
7 | * \/ \/ \/ \/ \/ | ||
8 | * | ||
9 | * Copyright (C) 2009 by Dominik Wenger | ||
10 | * | ||
11 | * This program is free software; you can redistribute it and/or | ||
12 | * modify it under the terms of the GNU General Public License | ||
13 | * as published by the Free Software Foundation; either version 2 | ||
14 | * of the License, or (at your option) any later version. | ||
15 | * | ||
16 | * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY | ||
17 | * KIND, either express or implied. | ||
18 | * | ||
19 | ****************************************************************************/ | ||
20 | |||
21 | #ifndef TTSFESTIVAL_H | ||
22 | #define TTSFESTIVAL_H | ||
23 | |||
24 | #include <QTemporaryFile> | ||
25 | #include "ttsbase.h" | ||
26 | |||
27 | class TTSFestival : public TTSBase | ||
28 | { | ||
29 | enum ESettings | ||
30 | { | ||
31 | eSERVERPATH, | ||
32 | eCLIENTPATH, | ||
33 | eVOICE, | ||
34 | eVOICEDESC | ||
35 | }; | ||
36 | |||
37 | Q_OBJECT | ||
38 | public: | ||
39 | TTSFestival(QObject* parent=nullptr) : TTSBase(parent) {} | ||
40 | ~TTSFestival(); | ||
41 | bool start(QString *errStr); | ||
42 | bool stop(); | ||
43 | TTSStatus voice(QString text,QString wavfile, QString *errStr); | ||
44 | QString voiceVendor(void) { return QString(); } | ||
45 | Capabilities capabilities(); | ||
46 | |||
47 | // for settings | ||
48 | bool configOk(); | ||
49 | void generateSettings(); | ||
50 | void saveSettings(); | ||
51 | |||
52 | private slots: | ||
53 | void updateVoiceList(); | ||
54 | void updateVoiceDescription(); | ||
55 | void clearVoiceDescription(); | ||
56 | private: | ||
57 | QTemporaryFile prologFile; | ||
58 | QString prologPath; | ||
59 | QString currentPath; | ||
60 | QStringList getVoiceList(); | ||
61 | QString getVoiceInfo(QString voice); | ||
62 | |||
63 | inline void startServer(); | ||
64 | inline bool ensureServerRunning(); | ||
65 | QString queryServer(QString query, int timeout = -1); | ||
66 | QProcess serverProcess; | ||
67 | QStringList voices; | ||
68 | QMap<QString, QString> voiceDescriptions; | ||
69 | }; | ||
70 | |||
71 | |||
72 | #endif | ||
diff --git a/utils/rbutilqt/base/ttsflite.h b/utils/rbutilqt/base/ttsflite.h new file mode 100644 index 0000000000..717c311a34 --- /dev/null +++ b/utils/rbutilqt/base/ttsflite.h | |||
@@ -0,0 +1,43 @@ | |||
1 | /*************************************************************************** | ||
2 | * __________ __ ___. | ||
3 | * Open \______ \ ____ ____ | | _\_ |__ _______ ___ | ||
4 | * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / | ||
5 | * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < | ||
6 | * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ | ||
7 | * \/ \/ \/ \/ \/ | ||
8 | * | ||
9 | * Copyright (C) 2012 by Dominik Riebeling | ||
10 | * | ||
11 | * This program is free software; you can redistribute it and/or | ||
12 | * modify it under the terms of the GNU General Public License | ||
13 | * as published by the Free Software Foundation; either version 2 | ||
14 | * of the License, or (at your option) any later version. | ||
15 | * | ||
16 | * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY | ||
17 | * KIND, either express or implied. | ||
18 | * | ||
19 | ****************************************************************************/ | ||
20 | |||
21 | #ifndef TTSFLITE_H | ||
22 | #define TTSFLITE_H | ||
23 | |||
24 | #include <QtCore> | ||
25 | #include "ttsexes.h" | ||
26 | |||
27 | class TTSFlite : public TTSExes | ||
28 | { | ||
29 | Q_OBJECT | ||
30 | public: | ||
31 | TTSFlite(QObject* parent=nullptr) : TTSExes(parent) | ||
32 | { | ||
33 | m_name = "flite"; | ||
34 | |||
35 | /* default to espeak */ | ||
36 | m_TTSTemplate = "\"%exe\" %options -o \"%wavfile\" -t \"%text\""; | ||
37 | m_TTSSpeakTemplate = ""; | ||
38 | m_capabilities = TTSBase::None; | ||
39 | |||
40 | } | ||
41 | }; | ||
42 | |||
43 | #endif | ||
diff --git a/utils/rbutilqt/base/ttsmimic.h b/utils/rbutilqt/base/ttsmimic.h new file mode 100644 index 0000000000..f4d7a8beac --- /dev/null +++ b/utils/rbutilqt/base/ttsmimic.h | |||
@@ -0,0 +1,41 @@ | |||
1 | /*************************************************************************** | ||
2 | * __________ __ ___. | ||
3 | * Open \______ \ ____ ____ | | _\_ |__ _______ ___ | ||
4 | * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / | ||
5 | * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < | ||
6 | * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ | ||
7 | * \/ \/ \/ \/ \/ | ||
8 | * | ||
9 | * Copyright (C) 2020 by Solomon Peachy | ||
10 | * | ||
11 | * This program is free software; you can redistribute it and/or | ||
12 | * modify it under the terms of the GNU General Public License | ||
13 | * as published by the Free Software Foundation; either version 2 | ||
14 | * of the License, or (at your option) any later version. | ||
15 | * | ||
16 | * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY | ||
17 | * KIND, either express or implied. | ||
18 | * | ||
19 | ****************************************************************************/ | ||
20 | |||
21 | #ifndef TTSMIMIC_H | ||
22 | #define TTSMIMIC_H | ||
23 | |||
24 | #include <QtCore> | ||
25 | #include "ttsexes.h" | ||
26 | |||
27 | class TTSMimic : public TTSExes | ||
28 | { | ||
29 | Q_OBJECT | ||
30 | public: | ||
31 | TTSMimic(QObject* parent=nullptr) : TTSExes(parent) | ||
32 | { | ||
33 | m_name = "mimic"; | ||
34 | |||
35 | m_TTSTemplate = "\"%exe\" %options -o \"%wavfile\" -t \"%text\""; | ||
36 | m_TTSSpeakTemplate = "\"%exe\" %options -t \"%text\""; | ||
37 | m_capabilities = TTSBase::CanSpeak; | ||
38 | } | ||
39 | }; | ||
40 | |||
41 | #endif | ||
diff --git a/utils/rbutilqt/base/ttsmssp.h b/utils/rbutilqt/base/ttsmssp.h new file mode 100644 index 0000000000..817b9fde58 --- /dev/null +++ b/utils/rbutilqt/base/ttsmssp.h | |||
@@ -0,0 +1,43 @@ | |||
1 | /*************************************************************************** | ||
2 | * __________ __ ___. | ||
3 | * Open \______ \ ____ ____ | | _\_ |__ _______ ___ | ||
4 | * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / | ||
5 | * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < | ||
6 | * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ | ||
7 | * \/ \/ \/ \/ \/ | ||
8 | * | ||
9 | * Copyright (C) 2012 by Dominik Riebeling | ||
10 | * | ||
11 | * This program is free software; you can redistribute it and/or | ||
12 | * modify it under the terms of the GNU General Public License | ||
13 | * as published by the Free Software Foundation; either version 2 | ||
14 | * of the License, or (at your option) any later version. | ||
15 | * | ||
16 | * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY | ||
17 | * KIND, either express or implied. | ||
18 | * | ||
19 | ****************************************************************************/ | ||
20 | |||
21 | #ifndef TTSMSSP_H | ||
22 | #define TTSMSSP_H | ||
23 | |||
24 | #include "ttsbase.h" | ||
25 | #include "ttssapi.h" | ||
26 | |||
27 | class TTSMssp: public TTSSapi | ||
28 | { | ||
29 | Q_OBJECT | ||
30 | public: | ||
31 | TTSMssp(QObject* parent=nullptr) : TTSSapi(parent) | ||
32 | { | ||
33 | m_TTSTemplate = "cscript //nologo \"%exe\" " | ||
34 | "/language:%lang /voice:\"%voice\" " | ||
35 | "/speed:%speed \"%options\" /mssp"; | ||
36 | m_TTSVoiceTemplate = "cscript //nologo \"%exe\" " | ||
37 | "/language:%lang /listvoices /mssp"; | ||
38 | m_TTSType = "mssp"; | ||
39 | } | ||
40 | |||
41 | }; | ||
42 | |||
43 | #endif | ||
diff --git a/utils/rbutilqt/base/ttssapi.cpp b/utils/rbutilqt/base/ttssapi.cpp new file mode 100644 index 0000000000..1b326b301e --- /dev/null +++ b/utils/rbutilqt/base/ttssapi.cpp | |||
@@ -0,0 +1,274 @@ | |||
1 | /*************************************************************************** | ||
2 | * __________ __ ___. | ||
3 | * Open \______ \ ____ ____ | | _\_ |__ _______ ___ | ||
4 | * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / | ||
5 | * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < | ||
6 | * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ | ||
7 | * \/ \/ \/ \/ \/ | ||
8 | * | ||
9 | * Copyright (C) 2007 by Dominik Wenger | ||
10 | * | ||
11 | * All files in this archive are subject to the GNU General Public License. | ||
12 | * See the file COPYING in the source tree root for full license agreement. | ||
13 | * | ||
14 | * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY | ||
15 | * KIND, either express or implied. | ||
16 | * | ||
17 | ****************************************************************************/ | ||
18 | |||
19 | #include "ttssapi.h" | ||
20 | #include "utils.h" | ||
21 | #include "rbsettings.h" | ||
22 | #include "playerbuildinfo.h" | ||
23 | #include "Logger.h" | ||
24 | |||
25 | TTSSapi::TTSSapi(QObject* parent) : TTSBase(parent) | ||
26 | { | ||
27 | m_TTSTemplate = "cscript //nologo \"%exe\" /language:%lang " | ||
28 | "/voice:\"%voice\" /speed:%speed \"%options\""; | ||
29 | m_TTSVoiceTemplate = "cscript //nologo \"%exe\" /language:%lang /listvoices"; | ||
30 | m_TTSType = "sapi"; | ||
31 | defaultLanguage = "english"; | ||
32 | m_started = false; | ||
33 | } | ||
34 | |||
35 | TTSBase::Capabilities TTSSapi::capabilities() | ||
36 | { | ||
37 | return None; | ||
38 | } | ||
39 | |||
40 | void TTSSapi::generateSettings() | ||
41 | { | ||
42 | // language | ||
43 | QMap<QString, QVariant> langmap = PlayerBuildInfo::instance()->value( | ||
44 | PlayerBuildInfo::LanguageList).toMap(); | ||
45 | EncTtsSetting* setting = new EncTtsSetting(this, | ||
46 | EncTtsSetting::eSTRINGLIST, tr("Language:"), | ||
47 | RbSettings::subValue(m_TTSType, RbSettings::TtsLanguage), | ||
48 | langmap.keys()); | ||
49 | connect(setting, &EncTtsSetting::dataChanged, this, &TTSSapi::updateVoiceList); | ||
50 | insertSetting(eLANGUAGE,setting); | ||
51 | // voice | ||
52 | setting = new EncTtsSetting(this, | ||
53 | EncTtsSetting::eSTRINGLIST, tr("Voice:"), | ||
54 | RbSettings::subValue(m_TTSType, RbSettings::TtsVoice), | ||
55 | getVoiceList(RbSettings::subValue(m_TTSType, | ||
56 | RbSettings::TtsLanguage).toString()), | ||
57 | EncTtsSetting::eREFRESHBTN); | ||
58 | connect(setting, &EncTtsSetting::refresh, this, &TTSSapi::updateVoiceList); | ||
59 | insertSetting(eVOICE,setting); | ||
60 | //speed | ||
61 | int speed = RbSettings::subValue(m_TTSType, RbSettings::TtsSpeed).toInt(); | ||
62 | if(speed > 10 || speed < -10) | ||
63 | speed = 0; | ||
64 | insertSetting(eSPEED, new EncTtsSetting(this, | ||
65 | EncTtsSetting::eINT, tr("Speed:"), speed, -10, 10)); | ||
66 | // options | ||
67 | insertSetting(eOPTIONS, new EncTtsSetting(this, | ||
68 | EncTtsSetting::eSTRING, tr("Options:"), | ||
69 | RbSettings::subValue(m_TTSType, RbSettings::TtsOptions))); | ||
70 | |||
71 | } | ||
72 | |||
73 | void TTSSapi::saveSettings() | ||
74 | { | ||
75 | //save settings in user config | ||
76 | RbSettings::setSubValue(m_TTSType, RbSettings::TtsLanguage, | ||
77 | getSetting(eLANGUAGE)->current().toString()); | ||
78 | RbSettings::setSubValue(m_TTSType, RbSettings::TtsVoice, | ||
79 | getSetting(eVOICE)->current().toString()); | ||
80 | RbSettings::setSubValue(m_TTSType, RbSettings::TtsSpeed, | ||
81 | getSetting(eSPEED)->current().toInt()); | ||
82 | RbSettings::setSubValue(m_TTSType, RbSettings::TtsOptions, | ||
83 | getSetting(eOPTIONS)->current().toString()); | ||
84 | |||
85 | RbSettings::sync(); | ||
86 | } | ||
87 | |||
88 | void TTSSapi::updateVoiceList() | ||
89 | { | ||
90 | LOG_INFO() << "updating voicelist"; | ||
91 | QStringList voiceList = getVoiceList(getSetting(eLANGUAGE)->current().toString()); | ||
92 | getSetting(eVOICE)->setList(voiceList); | ||
93 | if(voiceList.size() > 0) getSetting(eVOICE)->setCurrent(voiceList.at(0)); | ||
94 | else getSetting(eVOICE)->setCurrent(""); | ||
95 | } | ||
96 | |||
97 | bool TTSSapi::start(QString *errStr) | ||
98 | { | ||
99 | |||
100 | m_TTSOpts = RbSettings::subValue(m_TTSType, RbSettings::TtsOptions).toString(); | ||
101 | m_TTSLanguage =RbSettings::subValue(m_TTSType, RbSettings::TtsLanguage).toString(); | ||
102 | m_TTSVoice=RbSettings::subValue(m_TTSType, RbSettings::TtsVoice).toString(); | ||
103 | m_TTSSpeed=RbSettings::subValue(m_TTSType, RbSettings::TtsSpeed).toString(); | ||
104 | |||
105 | QFile::remove(QDir::tempPath() +"/sapi_voice.vbs"); | ||
106 | QFile::copy(":/builtin/sapi_voice.vbs",QDir::tempPath() + "/sapi_voice.vbs"); | ||
107 | m_TTSexec = QDir::tempPath() +"/sapi_voice.vbs"; | ||
108 | |||
109 | QFileInfo tts(m_TTSexec); | ||
110 | if(!tts.exists()) | ||
111 | { | ||
112 | *errStr = tr("Could not copy the SAPI script"); | ||
113 | return false; | ||
114 | } | ||
115 | // create the voice process | ||
116 | QString execstring = m_TTSTemplate; | ||
117 | execstring.replace("%exe",m_TTSexec); | ||
118 | execstring.replace("%options",m_TTSOpts); | ||
119 | execstring.replace("%lang",m_TTSLanguage); | ||
120 | execstring.replace("%voice",m_TTSVoice); | ||
121 | execstring.replace("%speed",m_TTSSpeed); | ||
122 | |||
123 | LOG_INFO() << "Start:" << execstring; | ||
124 | voicescript = new QProcess(nullptr); | ||
125 | //connect(voicescript,SIGNAL(readyReadStandardError()),this,SLOT(error())); | ||
126 | voicescript->start(execstring); | ||
127 | LOG_INFO() << "wait for process"; | ||
128 | if(!voicescript->waitForStarted()) | ||
129 | { | ||
130 | *errStr = tr("Could not start SAPI process"); | ||
131 | LOG_ERROR() << "starting process timed out!"; | ||
132 | return false; | ||
133 | } | ||
134 | |||
135 | if(!voicescript->waitForReadyRead(300)) | ||
136 | { | ||
137 | *errStr = voicescript->readAllStandardError(); | ||
138 | if(*errStr != "") | ||
139 | return false; | ||
140 | } | ||
141 | |||
142 | voicestream = new QTextStream(voicescript); | ||
143 | #if QT_VERSION < 0x060000 | ||
144 | voicestream->setCodec("UTF16-LE"); | ||
145 | #else | ||
146 | voicestream->setEncoding(QStringConverter::Utf16LE); | ||
147 | #endif | ||
148 | |||
149 | m_started = true; | ||
150 | return true; | ||
151 | } | ||
152 | |||
153 | QString TTSSapi::voiceVendor(void) | ||
154 | { | ||
155 | bool keeprunning = m_started; | ||
156 | QString vendor; | ||
157 | if(!m_started) { | ||
158 | QString error; | ||
159 | start(&error); | ||
160 | } | ||
161 | *voicestream << "QUERY\tVENDOR\r\n"; | ||
162 | voicestream->flush(); | ||
163 | while((vendor = voicestream->readLine()).isEmpty()) | ||
164 | QCoreApplication::processEvents(); | ||
165 | |||
166 | LOG_INFO() << "TTS vendor:" << vendor; | ||
167 | if(!keeprunning) { | ||
168 | stop(); | ||
169 | } | ||
170 | return vendor; | ||
171 | } | ||
172 | |||
173 | QStringList TTSSapi::getVoiceList(QString language) | ||
174 | { | ||
175 | QStringList result; | ||
176 | |||
177 | QFile::copy(":/builtin/sapi_voice.vbs",QDir::tempPath() + "/sapi_voice.vbs"); | ||
178 | m_TTSexec = QDir::tempPath() +"/sapi_voice.vbs"; | ||
179 | |||
180 | QFileInfo tts(m_TTSexec); | ||
181 | if(!tts.exists()) | ||
182 | return result; | ||
183 | |||
184 | // create the voice process | ||
185 | QString execstring = m_TTSVoiceTemplate; | ||
186 | execstring.replace("%exe",m_TTSexec); | ||
187 | execstring.replace("%lang",language); | ||
188 | |||
189 | LOG_INFO() << "Start:" << execstring; | ||
190 | voicescript = new QProcess(nullptr); | ||
191 | voicescript->start(execstring); | ||
192 | LOG_INFO() << "wait for process"; | ||
193 | if(!voicescript->waitForStarted()) { | ||
194 | LOG_INFO() << "process startup timed out!"; | ||
195 | return result; | ||
196 | } | ||
197 | voicescript->closeWriteChannel(); | ||
198 | voicescript->waitForReadyRead(); | ||
199 | |||
200 | QString dataRaw = voicescript->readAllStandardError().data(); | ||
201 | if(dataRaw.startsWith("Error")) { | ||
202 | LOG_INFO() << "Error:" << dataRaw; | ||
203 | } | ||
204 | #if QT_VERSION >= 0x050e00 | ||
205 | result = dataRaw.split(";", Qt::SkipEmptyParts); | ||
206 | #else | ||
207 | result = dataRaw.split(";", QString::SkipEmptyParts); | ||
208 | #endif | ||
209 | if(result.size() > 0) | ||
210 | { | ||
211 | result.sort(); | ||
212 | result.removeFirst(); | ||
213 | for(int i = 0; i< result.size();i++) | ||
214 | { | ||
215 | result[i] = result.at(i).simplified(); | ||
216 | } | ||
217 | } | ||
218 | |||
219 | delete voicescript; | ||
220 | QFile::setPermissions(QDir::tempPath() +"/sapi_voice.vbs", | ||
221 | QFile::ReadOwner | QFile::WriteOwner | QFile::ExeOwner | ||
222 | | QFile::ReadUser | QFile::WriteUser | QFile::ExeUser | ||
223 | | QFile::ReadGroup | QFile::WriteGroup | QFile::ExeGroup | ||
224 | | QFile::ReadOther | QFile::WriteOther | QFile::ExeOther ); | ||
225 | QFile::remove(QDir::tempPath() +"/sapi_voice.vbs"); | ||
226 | return result; | ||
227 | } | ||
228 | |||
229 | |||
230 | |||
231 | TTSStatus TTSSapi::voice(QString text,QString wavfile, QString *errStr) | ||
232 | { | ||
233 | (void) errStr; | ||
234 | QString query = "SPEAK\t"+wavfile+"\t"+text; | ||
235 | LOG_INFO() << "voicing" << query; | ||
236 | // append newline to query. Done now to keep debug output more readable. | ||
237 | query.append("\r\n"); | ||
238 | *voicestream << query; | ||
239 | *voicestream << "SYNC\tbla\r\n"; | ||
240 | voicestream->flush(); | ||
241 | // do NOT poll the output with readLine(), this causes sync issues! | ||
242 | voicescript->waitForReadyRead(); | ||
243 | |||
244 | if(!QFileInfo(wavfile).isFile()) { | ||
245 | LOG_ERROR() << "output file does not exist:" << wavfile; | ||
246 | return FatalError; | ||
247 | } | ||
248 | return NoError; | ||
249 | } | ||
250 | |||
251 | bool TTSSapi::stop() | ||
252 | { | ||
253 | *voicestream << "QUIT\r\n"; | ||
254 | voicestream->flush(); | ||
255 | voicescript->waitForFinished(); | ||
256 | delete voicestream; | ||
257 | delete voicescript; | ||
258 | QFile::setPermissions(QDir::tempPath() +"/sapi_voice.vbs", | ||
259 | QFile::ReadOwner | QFile::WriteOwner | QFile::ExeOwner | ||
260 | | QFile::ReadUser | QFile::WriteUser | QFile::ExeUser | ||
261 | | QFile::ReadGroup | QFile::WriteGroup | QFile::ExeGroup | ||
262 | | QFile::ReadOther | QFile::WriteOther | QFile::ExeOther ); | ||
263 | QFile::remove(QDir::tempPath() +"/sapi_voice.vbs"); | ||
264 | m_started = false; | ||
265 | return true; | ||
266 | } | ||
267 | |||
268 | bool TTSSapi::configOk() | ||
269 | { | ||
270 | if(RbSettings::subValue(m_TTSType, RbSettings::TtsVoice).toString().isEmpty()) | ||
271 | return false; | ||
272 | return true; | ||
273 | } | ||
274 | |||
diff --git a/utils/rbutilqt/base/ttssapi.h b/utils/rbutilqt/base/ttssapi.h new file mode 100644 index 0000000000..25d7d3dab7 --- /dev/null +++ b/utils/rbutilqt/base/ttssapi.h | |||
@@ -0,0 +1,77 @@ | |||
1 | /*************************************************************************** | ||
2 | * __________ __ ___. | ||
3 | * Open \______ \ ____ ____ | | _\_ |__ _______ ___ | ||
4 | * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / | ||
5 | * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < | ||
6 | * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ | ||
7 | * \/ \/ \/ \/ \/ | ||
8 | * | ||
9 | * Copyright (C) 2009 by Dominik Wenger | ||
10 | * | ||
11 | * This program is free software; you can redistribute it and/or | ||
12 | * modify it under the terms of the GNU General Public License | ||
13 | * as published by the Free Software Foundation; either version 2 | ||
14 | * of the License, or (at your option) any later version. | ||
15 | * | ||
16 | * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY | ||
17 | * KIND, either express or implied. | ||
18 | * | ||
19 | ****************************************************************************/ | ||
20 | |||
21 | #ifndef TTSSAPI_H | ||
22 | #define TTSSAPI_H | ||
23 | |||
24 | #include "ttsbase.h" | ||
25 | |||
26 | class TTSSapi : public TTSBase | ||
27 | { | ||
28 | //! Enum to identify the settings | ||
29 | enum ESettings | ||
30 | { | ||
31 | eLANGUAGE, | ||
32 | eVOICE, | ||
33 | eSPEED, | ||
34 | eOPTIONS | ||
35 | }; | ||
36 | |||
37 | Q_OBJECT | ||
38 | public: | ||
39 | TTSSapi(QObject* parent=nullptr); | ||
40 | |||
41 | TTSStatus voice(QString text,QString wavfile, QString *errStr); | ||
42 | bool start(QString *errStr); | ||
43 | bool stop(); | ||
44 | QString voiceVendor(void); | ||
45 | Capabilities capabilities(); | ||
46 | |||
47 | // for settings | ||
48 | bool configOk(); | ||
49 | void generateSettings(); | ||
50 | void saveSettings(); | ||
51 | |||
52 | private slots: | ||
53 | void updateVoiceList(); | ||
54 | |||
55 | private: | ||
56 | QStringList getVoiceList(QString language); | ||
57 | |||
58 | QProcess* voicescript; | ||
59 | QTextStream* voicestream; | ||
60 | QString defaultLanguage; | ||
61 | |||
62 | QString m_TTSexec; | ||
63 | QString m_TTSOpts; | ||
64 | QString m_TTSLanguage; | ||
65 | QString m_TTSVoice; | ||
66 | QString m_TTSSpeed; | ||
67 | bool m_started; | ||
68 | |||
69 | protected: | ||
70 | QString m_TTSTemplate; | ||
71 | QString m_TTSVoiceTemplate; | ||
72 | QString m_TTSType; | ||
73 | }; | ||
74 | |||
75 | |||
76 | |||
77 | #endif | ||
diff --git a/utils/rbutilqt/base/ttssapi4.h b/utils/rbutilqt/base/ttssapi4.h new file mode 100644 index 0000000000..d6408eaa2c --- /dev/null +++ b/utils/rbutilqt/base/ttssapi4.h | |||
@@ -0,0 +1,43 @@ | |||
1 | /*************************************************************************** | ||
2 | * __________ __ ___. | ||
3 | * Open \______ \ ____ ____ | | _\_ |__ _______ ___ | ||
4 | * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / | ||
5 | * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < | ||
6 | * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ | ||
7 | * \/ \/ \/ \/ \/ | ||
8 | * | ||
9 | * Copyright (C) 2012 by Dominik Riebeling | ||
10 | * | ||
11 | * This program is free software; you can redistribute it and/or | ||
12 | * modify it under the terms of the GNU General Public License | ||
13 | * as published by the Free Software Foundation; either version 2 | ||
14 | * of the License, or (at your option) any later version. | ||
15 | * | ||
16 | * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY | ||
17 | * KIND, either express or implied. | ||
18 | * | ||
19 | ****************************************************************************/ | ||
20 | |||
21 | #ifndef TTSSAPI4_H | ||
22 | #define TTSSAPI4_H | ||
23 | |||
24 | #include "ttsbase.h" | ||
25 | #include "ttssapi.h" | ||
26 | |||
27 | class TTSSapi4: public TTSSapi | ||
28 | { | ||
29 | Q_OBJECT | ||
30 | public: | ||
31 | TTSSapi4(QObject* parent=nullptr) : TTSSapi(parent) | ||
32 | { | ||
33 | m_TTSTemplate = "cscript //nologo \"%exe\" " | ||
34 | "/language:%lang /voice:\"%voice\" " | ||
35 | "/speed:%speed \"%options\" /sapi4"; | ||
36 | m_TTSVoiceTemplate = "cscript //nologo \"%exe\" " | ||
37 | "/language:%lang /listvoices /sapi4"; | ||
38 | m_TTSType = "sapi4"; | ||
39 | } | ||
40 | |||
41 | }; | ||
42 | |||
43 | #endif | ||
diff --git a/utils/rbutilqt/base/ttsswift.h b/utils/rbutilqt/base/ttsswift.h new file mode 100644 index 0000000000..adbc674d78 --- /dev/null +++ b/utils/rbutilqt/base/ttsswift.h | |||
@@ -0,0 +1,40 @@ | |||
1 | /*************************************************************************** | ||
2 | * __________ __ ___. | ||
3 | * Open \______ \ ____ ____ | | _\_ |__ _______ ___ | ||
4 | * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / | ||
5 | * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < | ||
6 | * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ | ||
7 | * \/ \/ \/ \/ \/ | ||
8 | * | ||
9 | * Copyright (C) 2012 by Dominik Riebeling | ||
10 | * | ||
11 | * This program is free software; you can redistribute it and/or | ||
12 | * modify it under the terms of the GNU General Public License | ||
13 | * as published by the Free Software Foundation; either version 2 | ||
14 | * of the License, or (at your option) any later version. | ||
15 | * | ||
16 | * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY | ||
17 | * KIND, either express or implied. | ||
18 | * | ||
19 | ****************************************************************************/ | ||
20 | |||
21 | #ifndef TTSSWIFT_H | ||
22 | #define TTSSWIFT_H | ||
23 | |||
24 | #include <QtCore> | ||
25 | #include "ttsexes.h" | ||
26 | |||
27 | class TTSSwift : public TTSExes | ||
28 | { | ||
29 | Q_OBJECT | ||
30 | public: | ||
31 | TTSSwift(QObject* parent=nullptr) : TTSExes(parent) | ||
32 | { | ||
33 | m_name = "swift"; | ||
34 | m_TTSTemplate = "\"%exe\" %options -o \"%wavfile\" -- \"%text\""; | ||
35 | m_TTSSpeakTemplate = ""; | ||
36 | m_capabilities = TTSBase::None; | ||
37 | } | ||
38 | }; | ||
39 | |||
40 | #endif | ||
diff --git a/utils/rbutilqt/base/uninstall.cpp b/utils/rbutilqt/base/uninstall.cpp new file mode 100644 index 0000000000..5ab670a031 --- /dev/null +++ b/utils/rbutilqt/base/uninstall.cpp | |||
@@ -0,0 +1,126 @@ | |||
1 | /*************************************************************************** | ||
2 | * __________ __ ___. | ||
3 | * Open \______ \ ____ ____ | | _\_ |__ _______ ___ | ||
4 | * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / | ||
5 | * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < | ||
6 | * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ | ||
7 | * \/ \/ \/ \/ \/ | ||
8 | * | ||
9 | * Copyright (C) 2007 by Dominik Wenger | ||
10 | * | ||
11 | * All files in this archive are subject to the GNU General Public License. | ||
12 | * See the file COPYING in the source tree root for full license agreement. | ||
13 | * | ||
14 | * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY | ||
15 | * KIND, either express or implied. | ||
16 | * | ||
17 | ****************************************************************************/ | ||
18 | |||
19 | #include <QtCore> | ||
20 | #include "uninstall.h" | ||
21 | #include "utils.h" | ||
22 | #include "Logger.h" | ||
23 | |||
24 | Uninstaller::Uninstaller(QObject* parent,QString mountpoint): QObject(parent) | ||
25 | { | ||
26 | m_mountpoint = mountpoint; | ||
27 | } | ||
28 | |||
29 | void Uninstaller::deleteAll(void) | ||
30 | { | ||
31 | QString rbdir(m_mountpoint + ".rockbox/"); | ||
32 | emit logItem(tr("Starting Uninstallation"), LOGINFO); | ||
33 | emit logProgress(0, 0); | ||
34 | Utils::recursiveRmdir(rbdir); | ||
35 | emit logProgress(1, 1); | ||
36 | emit logItem(tr("Finished Uninstallation"), LOGOK); | ||
37 | emit logFinished(); | ||
38 | } | ||
39 | |||
40 | void Uninstaller::uninstall(void) | ||
41 | { | ||
42 | emit logProgress(0, 0); | ||
43 | emit logItem(tr("Starting Uninstallation"), LOGINFO); | ||
44 | |||
45 | QSettings installlog(m_mountpoint + "/.rockbox/rbutil.log", QSettings::IniFormat, this); | ||
46 | |||
47 | for(int i=0; i< uninstallSections.size() ; i++) | ||
48 | { | ||
49 | emit logItem(tr("Uninstalling %1...").arg(uninstallSections.at(i)), LOGINFO); | ||
50 | QCoreApplication::processEvents(); | ||
51 | // create list of all other install sections | ||
52 | QStringList sections = installlog.childGroups(); | ||
53 | sections.removeAt(sections.indexOf(uninstallSections.at(i))); | ||
54 | installlog.beginGroup(uninstallSections.at(i)); | ||
55 | QStringList toDeleteList = installlog.allKeys(); | ||
56 | QStringList dirList; | ||
57 | installlog.endGroup(); | ||
58 | |||
59 | // iterate over all entries | ||
60 | for(int j =0; j < toDeleteList.size(); j++ ) | ||
61 | { | ||
62 | // check if current file is in use by another section | ||
63 | bool deleteFile = true; | ||
64 | for(int s = 0; s < sections.size(); s++) | ||
65 | { | ||
66 | installlog.beginGroup(sections.at(s)); | ||
67 | if(installlog.contains(toDeleteList.at(j))) | ||
68 | { | ||
69 | deleteFile = false; | ||
70 | LOG_INFO() << "file still in use:" << toDeleteList.at(j); | ||
71 | } | ||
72 | installlog.endGroup(); | ||
73 | } | ||
74 | |||
75 | installlog.beginGroup(uninstallSections.at(i)); | ||
76 | QFileInfo toDelete(m_mountpoint + "/" + toDeleteList.at(j)); | ||
77 | if(toDelete.isFile()) // if it is a file remove it | ||
78 | { | ||
79 | if(deleteFile && !QFile::remove(toDelete.filePath())) | ||
80 | emit logItem(tr("Could not delete %1") | ||
81 | .arg(toDelete.filePath()), LOGWARNING); | ||
82 | installlog.remove(toDeleteList.at(j)); | ||
83 | LOG_INFO() << "deleted:" << toDelete.filePath(); | ||
84 | } | ||
85 | else // if it is a dir, remember it for later deletion | ||
86 | { | ||
87 | // no need to keep track on folders still in use -- only empty | ||
88 | // folders will be rm'ed. | ||
89 | dirList << toDeleteList.at(j); | ||
90 | } | ||
91 | installlog.endGroup(); | ||
92 | QCoreApplication::processEvents(); | ||
93 | } | ||
94 | // delete the dirs | ||
95 | installlog.beginGroup(uninstallSections.at(i)); | ||
96 | for(int j=0; j < dirList.size(); j++ ) | ||
97 | { | ||
98 | installlog.remove(dirList.at(j)); | ||
99 | QDir dir(m_mountpoint); | ||
100 | dir.rmdir(dirList.at(j)); // rm works only on empty folders | ||
101 | } | ||
102 | |||
103 | installlog.endGroup(); | ||
104 | //installlog.removeGroup(uninstallSections.at(i)) | ||
105 | } | ||
106 | uninstallSections.clear(); | ||
107 | installlog.sync(); | ||
108 | emit logProgress(1, 1); | ||
109 | emit logItem(tr("Uninstallation finished"), LOGOK); | ||
110 | emit logFinished(); | ||
111 | } | ||
112 | |||
113 | QStringList Uninstaller::getAllSections() | ||
114 | { | ||
115 | QSettings installlog(m_mountpoint + "/.rockbox/rbutil.log", QSettings::IniFormat, nullptr); | ||
116 | QStringList allSections = installlog.childGroups(); | ||
117 | allSections.removeAt(allSections.lastIndexOf("Bootloader")); | ||
118 | return allSections; | ||
119 | } | ||
120 | |||
121 | |||
122 | bool Uninstaller::uninstallPossible() | ||
123 | { | ||
124 | return QFileInfo::exists(m_mountpoint +"/.rockbox/rbutil.log"); | ||
125 | } | ||
126 | |||
diff --git a/utils/rbutilqt/base/uninstall.h b/utils/rbutilqt/base/uninstall.h new file mode 100644 index 0000000000..d8c9815117 --- /dev/null +++ b/utils/rbutilqt/base/uninstall.h | |||
@@ -0,0 +1,63 @@ | |||
1 | /*************************************************************************** | ||
2 | * __________ __ ___. | ||
3 | * Open \______ \ ____ ____ | | _\_ |__ _______ ___ | ||
4 | * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / | ||
5 | * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < | ||
6 | * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ | ||
7 | * \/ \/ \/ \/ \/ | ||
8 | * | ||
9 | * Copyright (C) 2007 by Dominik Wenger | ||
10 | * | ||
11 | * This program is free software; you can redistribute it and/or | ||
12 | * modify it under the terms of the GNU General Public License | ||
13 | * as published by the Free Software Foundation; either version 2 | ||
14 | * of the License, or (at your option) any later version. | ||
15 | * | ||
16 | * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY | ||
17 | * KIND, either express or implied. | ||
18 | * | ||
19 | ****************************************************************************/ | ||
20 | |||
21 | |||
22 | #ifndef UNINSTALL_H | ||
23 | #define UNINSTALL_H | ||
24 | |||
25 | #include <QtCore> | ||
26 | |||
27 | #include "progressloggerinterface.h" | ||
28 | |||
29 | |||
30 | class Uninstaller : public QObject | ||
31 | { | ||
32 | Q_OBJECT | ||
33 | public: | ||
34 | Uninstaller(QObject* parent,QString mountpoint) ; | ||
35 | ~Uninstaller(){} | ||
36 | |||
37 | void deleteAll(void); | ||
38 | void uninstall(void); | ||
39 | |||
40 | bool uninstallPossible(); | ||
41 | |||
42 | QStringList getAllSections(); | ||
43 | |||
44 | void setSections(QStringList sections) {uninstallSections = sections;} | ||
45 | |||
46 | signals: | ||
47 | void logItem(QString, int); //! set logger item | ||
48 | void logProgress(int, int); //! set progress bar. | ||
49 | void logFinished(void); | ||
50 | |||
51 | private slots: | ||
52 | |||
53 | |||
54 | private: | ||
55 | QString m_mountpoint; | ||
56 | |||
57 | QStringList uninstallSections; | ||
58 | }; | ||
59 | |||
60 | |||
61 | |||
62 | #endif | ||
63 | |||
diff --git a/utils/rbutilqt/base/utils.cpp b/utils/rbutilqt/base/utils.cpp new file mode 100644 index 0000000000..f2d3f04887 --- /dev/null +++ b/utils/rbutilqt/base/utils.cpp | |||
@@ -0,0 +1,1062 @@ | |||
1 | /*************************************************************************** | ||
2 | * __________ __ ___. | ||
3 | * Open \______ \ ____ ____ | | _\_ |__ _______ ___ | ||
4 | * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / | ||
5 | * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < | ||
6 | * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ | ||
7 | * \/ \/ \/ \/ \/ | ||
8 | * | ||
9 | * Copyright (C) 2007 by Dominik Wenger | ||
10 | * | ||
11 | * All files in this archive are subject to the GNU General Public License. | ||
12 | * See the file COPYING in the source tree root for full license agreement. | ||
13 | * | ||
14 | * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY | ||
15 | * KIND, either express or implied. | ||
16 | * | ||
17 | ****************************************************************************/ | ||
18 | |||
19 | #include "utils.h" | ||
20 | #include "rockboxinfo.h" | ||
21 | #include "system.h" | ||
22 | #include "rbsettings.h" | ||
23 | #include "playerbuildinfo.h" | ||
24 | #include "Logger.h" | ||
25 | |||
26 | #if !defined(_UNICODE) | ||
27 | #define _UNICODE | ||
28 | #endif | ||
29 | |||
30 | #include <QtCore> | ||
31 | #include <QDebug> | ||
32 | #include <cstdlib> | ||
33 | #include <stdio.h> | ||
34 | |||
35 | #if defined(Q_OS_LINUX) || defined(Q_OS_MACX) | ||
36 | #include <sys/statvfs.h> | ||
37 | #endif | ||
38 | #if defined(Q_OS_LINUX) || defined(Q_OS_MACX) | ||
39 | #include <stdio.h> | ||
40 | #endif | ||
41 | #if defined(Q_OS_LINUX) | ||
42 | #include <mntent.h> | ||
43 | #endif | ||
44 | #if defined(Q_OS_MACX) || defined(Q_OS_OPENBSD) | ||
45 | #include <sys/param.h> | ||
46 | #include <sys/ucred.h> | ||
47 | #include <sys/mount.h> | ||
48 | #endif | ||
49 | #if defined(Q_OS_WIN32) | ||
50 | #include <stdio.h> | ||
51 | #include <tchar.h> | ||
52 | #include <windows.h> | ||
53 | #include <setupapi.h> | ||
54 | #include <winioctl.h> | ||
55 | #include <tlhelp32.h> | ||
56 | #endif | ||
57 | #if defined(Q_OS_MACX) | ||
58 | #include <Carbon/Carbon.h> | ||
59 | #include <CoreFoundation/CoreFoundation.h> | ||
60 | #include <CoreServices/CoreServices.h> | ||
61 | #include <IOKit/IOKitLib.h> | ||
62 | #endif | ||
63 | |||
64 | // recursive function to delete a dir with files | ||
65 | bool Utils::recursiveRmdir( const QString &dirName ) | ||
66 | { | ||
67 | QString dirN = dirName; | ||
68 | QDir dir(dirN); | ||
69 | // make list of entries in directory | ||
70 | QStringList list = dir.entryList(QDir::AllEntries | QDir::NoDotAndDotDot); | ||
71 | QFileInfo fileInfo; | ||
72 | QString curItem; | ||
73 | for(int i = 0; i < list.size(); i++){ // loop through all items of list | ||
74 | QString name = list.at(i); | ||
75 | curItem = dirN + "/" + name; | ||
76 | fileInfo.setFile(curItem); | ||
77 | if(fileInfo.isDir()) // is directory | ||
78 | recursiveRmdir(curItem); // call recRmdir() recursively for | ||
79 | // deleting subdirectory | ||
80 | else // is file | ||
81 | QFile::remove(curItem); // ok, delete file | ||
82 | } | ||
83 | dir.cdUp(); | ||
84 | return dir.rmdir(dirN); // delete empty dir and return if (now empty) | ||
85 | // dir-removing was successfull | ||
86 | } | ||
87 | |||
88 | |||
89 | //! @brief resolves the given path, ignoring case. | ||
90 | //! @param path absolute path to resolve. | ||
91 | //! @return returns exact casing of path, empty string if path not found. | ||
92 | QString Utils::resolvePathCase(QString path) | ||
93 | { | ||
94 | int start; | ||
95 | QString realpath; | ||
96 | #if QT_VERSION >= 0x050e00 | ||
97 | QStringList elems = path.split("/", Qt::SkipEmptyParts); | ||
98 | #else | ||
99 | QStringList elems = path.split("/", QString::SkipEmptyParts); | ||
100 | #endif | ||
101 | |||
102 | if(path.isEmpty()) | ||
103 | return QString(); | ||
104 | #if defined(Q_OS_WIN32) | ||
105 | // on windows we must make sure to start with the first entry (i.e. the | ||
106 | // drive letter) instead of a single / to make resolving work. | ||
107 | start = 1; | ||
108 | realpath = elems.at(0) + "/"; | ||
109 | #else | ||
110 | start = 0; | ||
111 | realpath = "/"; | ||
112 | #endif | ||
113 | |||
114 | for(int i = start; i < elems.size(); i++) { | ||
115 | QStringList direlems | ||
116 | = QDir(realpath).entryList(QDir::AllEntries|QDir::Hidden|QDir::System); | ||
117 | if(direlems.contains(elems.at(i), Qt::CaseInsensitive)) { | ||
118 | // need to filter using QRegExp as QStringList::filter(QString) | ||
119 | // matches any substring | ||
120 | QString expr = QString("^" + elems.at(i) + "$"); | ||
121 | QRegExp rx = QRegExp(expr, Qt::CaseInsensitive); | ||
122 | QStringList a = direlems.filter(rx); | ||
123 | |||
124 | if(a.size() != 1) | ||
125 | return QString(""); | ||
126 | if(!realpath.endsWith("/")) | ||
127 | realpath += "/"; | ||
128 | realpath += a.at(0); | ||
129 | } | ||
130 | else | ||
131 | return QString(""); | ||
132 | } | ||
133 | LOG_INFO() << "resolving path" << path << "->" << realpath; | ||
134 | return realpath; | ||
135 | } | ||
136 | |||
137 | |||
138 | QString Utils::filesystemType(QString path) | ||
139 | { | ||
140 | #if defined(Q_OS_LINUX) | ||
141 | FILE *mn = setmntent("/etc/mtab", "r"); | ||
142 | if(!mn) | ||
143 | return QString(""); | ||
144 | |||
145 | struct mntent *ent; | ||
146 | while((ent = getmntent(mn))) { | ||
147 | if(QString(ent->mnt_dir) == path) { | ||
148 | endmntent(mn); | ||
149 | LOG_INFO() << "device type is" << ent->mnt_type; | ||
150 | return QString(ent->mnt_type); | ||
151 | } | ||
152 | } | ||
153 | endmntent(mn); | ||
154 | #endif | ||
155 | |||
156 | #if defined(Q_OS_MACX) || defined(Q_OS_OPENBSD) | ||
157 | int num; | ||
158 | struct statfs *mntinf; | ||
159 | |||
160 | num = getmntinfo(&mntinf, MNT_WAIT); | ||
161 | while(num--) { | ||
162 | if(QString(mntinf->f_mntonname) == path) { | ||
163 | LOG_INFO() << "device type is" << mntinf->f_fstypename; | ||
164 | return QString(mntinf->f_fstypename); | ||
165 | } | ||
166 | mntinf++; | ||
167 | } | ||
168 | #endif | ||
169 | |||
170 | #if defined(Q_OS_WIN32) | ||
171 | wchar_t t[64]; | ||
172 | memset(t, 0, 32); | ||
173 | if(GetVolumeInformationW((LPCWSTR)path.utf16(), | ||
174 | NULL, 0, NULL, NULL, NULL, t, 64)) { | ||
175 | LOG_INFO() << "device type is" << t; | ||
176 | return QString::fromWCharArray(t); | ||
177 | } | ||
178 | #endif | ||
179 | return QString("-"); | ||
180 | } | ||
181 | |||
182 | |||
183 | QString Utils::filesystemName(QString path) | ||
184 | { | ||
185 | QString name; | ||
186 | #if defined(Q_OS_WIN32) | ||
187 | wchar_t volname[MAX_PATH+1]; | ||
188 | bool res = GetVolumeInformationW((LPTSTR)path.utf16(), volname, MAX_PATH+1, | ||
189 | NULL, NULL, NULL, NULL, 0); | ||
190 | if(res) { | ||
191 | name = QString::fromWCharArray(volname); | ||
192 | } | ||
193 | #endif | ||
194 | #if defined(Q_OS_MACX) | ||
195 | // BSD label does not include folder. | ||
196 | QString bsd = Utils::resolveDevicename(path).remove("/dev/"); | ||
197 | if(bsd.isEmpty()) { | ||
198 | return name; | ||
199 | } | ||
200 | OSStatus result; | ||
201 | ItemCount index = 1; | ||
202 | |||
203 | do { | ||
204 | FSVolumeRefNum volrefnum; | ||
205 | HFSUniStr255 volname; | ||
206 | |||
207 | result = FSGetVolumeInfo(kFSInvalidVolumeRefNum, index, &volrefnum, | ||
208 | kFSVolInfoFSInfo, NULL, &volname, NULL); | ||
209 | |||
210 | if(result == noErr) { | ||
211 | GetVolParmsInfoBuffer volparms; | ||
212 | /* PBHGetVolParmsSync() is not available for 64bit while | ||
213 | FSGetVolumeParms() is available in 10.5+. Thus we need to use | ||
214 | PBHGetVolParmsSync() for 10.4, and that also requires 10.4 to | ||
215 | always use 32bit. | ||
216 | Qt 4 supports 32bit on 10.6 Cocoa only. | ||
217 | */ | ||
218 | #if __ENVIRONMENT_MAC_OS_X_VERSION_MIN_REQUIRED__ >= 1050 | ||
219 | if(FSGetVolumeParms(volrefnum, &volparms, sizeof(volparms)) == noErr) | ||
220 | #else | ||
221 | HParamBlockRec hpb; | ||
222 | hpb.ioParam.ioNamePtr = NULL; | ||
223 | hpb.ioParam.ioVRefNum = volrefnum; | ||
224 | hpb.ioParam.ioBuffer = (Ptr)&volparms; | ||
225 | hpb.ioParam.ioReqCount = sizeof(volparms); | ||
226 | if(PBHGetVolParmsSync(&hpb) == noErr) | ||
227 | #endif | ||
228 | { | ||
229 | if(volparms.vMServerAdr == 0) { | ||
230 | if(bsd == (char*)volparms.vMDeviceID) { | ||
231 | name = QString::fromUtf16((const ushort*)volname.unicode, | ||
232 | (int)volname.length); | ||
233 | break; | ||
234 | } | ||
235 | } | ||
236 | } | ||
237 | } | ||
238 | index++; | ||
239 | } while(result == noErr); | ||
240 | #endif | ||
241 | |||
242 | LOG_INFO() << "Volume name of" << path << "is" << name; | ||
243 | return name; | ||
244 | } | ||
245 | |||
246 | |||
247 | //! @brief figure the free disk space on a filesystem | ||
248 | //! @param path path on the filesystem to check | ||
249 | //! @return size in bytes | ||
250 | qulonglong Utils::filesystemFree(QString path) | ||
251 | { | ||
252 | qulonglong size = filesystemSize(path, FilesystemFree); | ||
253 | LOG_INFO() << "free disk space for" << path << size; | ||
254 | return size; | ||
255 | } | ||
256 | |||
257 | |||
258 | qulonglong Utils::filesystemTotal(QString path) | ||
259 | { | ||
260 | qulonglong size = filesystemSize(path, FilesystemTotal); | ||
261 | LOG_INFO() << "total disk space for" << path << size; | ||
262 | return size; | ||
263 | } | ||
264 | |||
265 | |||
266 | qulonglong Utils::filesystemSize(QString path, enum Utils::Size type) | ||
267 | { | ||
268 | qulonglong size = 0; | ||
269 | #if defined(Q_OS_LINUX) || defined(Q_OS_MACX) | ||
270 | // the usage of statfs() is deprecated by the LSB so use statvfs(). | ||
271 | struct statvfs fs; | ||
272 | int ret; | ||
273 | |||
274 | ret = statvfs(qPrintable(path), &fs); | ||
275 | |||
276 | if(ret == 0) { | ||
277 | if(type == FilesystemFree) { | ||
278 | size = (qulonglong)fs.f_frsize * (qulonglong)fs.f_bavail; | ||
279 | } | ||
280 | if(type == FilesystemTotal) { | ||
281 | size = (qulonglong)fs.f_frsize * (qulonglong)fs.f_blocks; | ||
282 | } | ||
283 | if(type == FilesystemClusterSize) { | ||
284 | size = (qulonglong)fs.f_frsize; | ||
285 | } | ||
286 | } | ||
287 | #endif | ||
288 | #if defined(Q_OS_WIN32) | ||
289 | BOOL ret; | ||
290 | ULARGE_INTEGER freeAvailBytes; | ||
291 | ULARGE_INTEGER totalNumberBytes; | ||
292 | |||
293 | ret = GetDiskFreeSpaceExW((LPCTSTR)path.utf16(), &freeAvailBytes, | ||
294 | &totalNumberBytes, NULL); | ||
295 | if(ret) { | ||
296 | if(type == FilesystemFree) { | ||
297 | size = freeAvailBytes.QuadPart; | ||
298 | } | ||
299 | if(type == FilesystemTotal) { | ||
300 | size = totalNumberBytes.QuadPart; | ||
301 | } | ||
302 | if(type == FilesystemClusterSize) { | ||
303 | DWORD sectorsPerCluster; | ||
304 | DWORD bytesPerSector; | ||
305 | DWORD freeClusters; | ||
306 | DWORD totalClusters; | ||
307 | ret = GetDiskFreeSpaceW((LPCTSTR)path.utf16(), §orsPerCluster, | ||
308 | &bytesPerSector, &freeClusters, &totalClusters); | ||
309 | if(ret) { | ||
310 | size = bytesPerSector * sectorsPerCluster; | ||
311 | } | ||
312 | } | ||
313 | } | ||
314 | #endif | ||
315 | return size; | ||
316 | } | ||
317 | |||
318 | //! \brief searches for a Executable in the Environement Path | ||
319 | QString Utils::findExecutable(QString name) | ||
320 | { | ||
321 | //try autodetect tts | ||
322 | #if defined(Q_OS_LINUX) || defined(Q_OS_MACX) || defined(Q_OS_OPENBSD) | ||
323 | #if QT_VERSION >= 0x050e00 | ||
324 | QStringList path = QString(getenv("PATH")).split(":", Qt::SkipEmptyParts); | ||
325 | #else | ||
326 | QStringList path = QString(getenv("PATH")).split(":", QString::SkipEmptyParts); | ||
327 | #endif | ||
328 | #elif defined(Q_OS_WIN) | ||
329 | #if QT_VERSION >= 0x050e00 | ||
330 | QStringList path = QString(getenv("PATH")).split(";", Qt::SkipEmptyParts); | ||
331 | #else | ||
332 | QStringList path = QString(getenv("PATH")).split(";", QString::SkipEmptyParts); | ||
333 | #endif | ||
334 | #endif | ||
335 | LOG_INFO() << "system path:" << path; | ||
336 | for(int i = 0; i < path.size(); i++) | ||
337 | { | ||
338 | QString executable = QDir::fromNativeSeparators(path.at(i)) + "/" + name; | ||
339 | #if defined(Q_OS_WIN) | ||
340 | executable += ".exe"; | ||
341 | #if QT_VERSION >= 0x050e00 | ||
342 | QStringList ex = executable.split("\"", Qt::SkipEmptyParts); | ||
343 | #else | ||
344 | QStringList ex = executable.split("\"", QString::SkipEmptyParts); | ||
345 | #endif | ||
346 | executable = ex.join(""); | ||
347 | #endif | ||
348 | if(QFileInfo(executable).isExecutable()) | ||
349 | { | ||
350 | LOG_INFO() << "findExecutable: found" << executable; | ||
351 | return QDir::toNativeSeparators(executable); | ||
352 | } | ||
353 | } | ||
354 | LOG_INFO() << "findExecutable: could not find" << name; | ||
355 | return ""; | ||
356 | } | ||
357 | |||
358 | |||
359 | /** @brief checks different Enviroment things. Ask if user wants to continue. | ||
360 | * @param permission if it should check for permission | ||
361 | * @return string with error messages if problems occurred, empty strings if none. | ||
362 | */ | ||
363 | QString Utils::checkEnvironment(bool permission) | ||
364 | { | ||
365 | LOG_INFO() << "checking environment"; | ||
366 | QString text = ""; | ||
367 | |||
368 | // check permission | ||
369 | if(permission) | ||
370 | { | ||
371 | #if defined(Q_OS_WIN32) | ||
372 | if(System::userPermissions() != System::ADMIN) | ||
373 | { | ||
374 | text += tr("<li>Permissions insufficient for bootloader " | ||
375 | "installation.\nAdministrator priviledges are necessary.</li>"); | ||
376 | } | ||
377 | #endif | ||
378 | } | ||
379 | |||
380 | // Check TargetId | ||
381 | RockboxInfo rbinfo(RbSettings::value(RbSettings::Mountpoint).toString()); | ||
382 | QString installed = rbinfo.target(); | ||
383 | if(!installed.isEmpty() && installed != | ||
384 | RbSettings::value(RbSettings::CurrentPlatform).toString().split(".").at(0)) | ||
385 | { | ||
386 | text += tr("<li>Target mismatch detected.<br/>" | ||
387 | "Installed target: %1<br/>Selected target: %2.</li>") | ||
388 | .arg(PlayerBuildInfo::instance()->value( | ||
389 | PlayerBuildInfo::DisplayName, installed).toString(), | ||
390 | PlayerBuildInfo::instance()->value( | ||
391 | PlayerBuildInfo::DisplayName).toString()); | ||
392 | } | ||
393 | |||
394 | if(!text.isEmpty()) | ||
395 | return tr("Problem detected:") + "<ul>" + text + "</ul>"; | ||
396 | else | ||
397 | return text; | ||
398 | } | ||
399 | |||
400 | /** @brief Trim version string from filename to version part only. | ||
401 | * @param s Version string | ||
402 | * @return Version part of string if found, input string on error. | ||
403 | */ | ||
404 | QString Utils::trimVersionString(QString s) | ||
405 | { | ||
406 | QRegExp r = QRegExp(".*([\\d\\.]+\\d+[a-z]?).*"); | ||
407 | if(r.indexIn(s) != -1) { | ||
408 | return r.cap(1); | ||
409 | } | ||
410 | return s; | ||
411 | } | ||
412 | |||
413 | /** @brief Compare two version strings. | ||
414 | * @param s1 first version string | ||
415 | * @param s2 second version string | ||
416 | * @return 0 if strings identical, 1 if second is newer, -1 if first. | ||
417 | */ | ||
418 | int Utils::compareVersionStrings(QString s1, QString s2) | ||
419 | { | ||
420 | LOG_INFO() << "comparing version strings" << s1 << "and" << s2; | ||
421 | QString a = s1.trimmed(); | ||
422 | QString b = s2.trimmed(); | ||
423 | // if strings are identical return 0. | ||
424 | if(a.isEmpty()) | ||
425 | return 1; | ||
426 | if(b.isEmpty()) | ||
427 | return -1; | ||
428 | |||
429 | while(!a.isEmpty() || !b.isEmpty()) { | ||
430 | // trim all leading non-digits and non-dots (dots are removed afterwards) | ||
431 | a.remove(QRegExp("^[^\\d\\.]*")); | ||
432 | b.remove(QRegExp("^[^\\d\\.]*")); | ||
433 | |||
434 | // trim all trailing non-digits for conversion (QString::toInt() | ||
435 | // requires this). Copy strings first as replace() changes the string. | ||
436 | QString numa = a; | ||
437 | QString numb = b; | ||
438 | numa.remove(QRegExp("\\D+.*$")); | ||
439 | numb.remove(QRegExp("\\D+.*$")); | ||
440 | |||
441 | // convert to number | ||
442 | bool ok1, ok2; | ||
443 | unsigned int vala = numa.toUInt(&ok1); | ||
444 | unsigned int valb = numb.toUInt(&ok2); | ||
445 | // if none of the numbers converted successfully we're at trailing garbage. | ||
446 | if(!ok1 && !ok2) | ||
447 | break; | ||
448 | if(!ok1) | ||
449 | return 1; | ||
450 | if(!ok2) | ||
451 | return -1; | ||
452 | |||
453 | // if numbers mismatch we have a decision. | ||
454 | if(vala != valb) | ||
455 | return (vala > valb) ? -1 : 1; | ||
456 | |||
457 | // trim leading digits. | ||
458 | a.remove(QRegExp("^\\d*")); | ||
459 | b.remove(QRegExp("^\\d*")); | ||
460 | |||
461 | // If only one of the following characters is a dot that one is | ||
462 | // "greater" then anything else. Make sure it's followed by a number, | ||
463 | // Otherwise it might be the end of the string or suffix. Do this | ||
464 | // before version addon characters check to avoid stopping too early. | ||
465 | bool adot = a.contains(QRegExp("^[a-zA-Z]*\\.[0-9]")); | ||
466 | bool bdot = b.contains(QRegExp("^[a-zA-Z]*\\.[0-9]")); | ||
467 | if(adot && !bdot) | ||
468 | return -1; | ||
469 | if(!adot && bdot) | ||
470 | return 1; | ||
471 | // if number is immediately followed by a character consider it as | ||
472 | // version addon (like 1.2.3b). In this case compare characters and end | ||
473 | // (version numbers like 1.2b.3 aren't handled). | ||
474 | QChar ltra; | ||
475 | QChar ltrb; | ||
476 | if(a.contains(QRegExp("^[a-zA-Z]"))) | ||
477 | ltra = a.at(0); | ||
478 | if(b.contains(QRegExp("^[a-zA-Z]"))) | ||
479 | ltrb = b.at(0); | ||
480 | if(ltra != ltrb) | ||
481 | return (ltra < ltrb) ? 1 : -1; | ||
482 | |||
483 | // both are identical or no addon characters, ignore. | ||
484 | // remove modifiers and following dot. | ||
485 | a.remove(QRegExp("^[a-zA-Z]*\\.")); | ||
486 | b.remove(QRegExp("^[a-zA-Z]*\\.")); | ||
487 | } | ||
488 | |||
489 | // no differences found. | ||
490 | return 0; | ||
491 | } | ||
492 | |||
493 | |||
494 | /** Resolve mountpoint to devicename / disk number | ||
495 | * @param path mountpoint path / drive letter | ||
496 | * @return devicename / disk number | ||
497 | */ | ||
498 | QString Utils::resolveDevicename(QString path) | ||
499 | { | ||
500 | LOG_INFO() << "resolving device name" << path; | ||
501 | #if defined(Q_OS_LINUX) | ||
502 | FILE *mn = setmntent("/etc/mtab", "r"); | ||
503 | if(!mn) | ||
504 | return QString(""); | ||
505 | |||
506 | struct mntent *ent; | ||
507 | while((ent = getmntent(mn))) { | ||
508 | // check for valid filesystem type. | ||
509 | // Linux can handle hfs (and hfsplus), so consider it a valid file | ||
510 | // system. Otherwise resolving the device name would fail, which in | ||
511 | // turn would make it impossible to warn about a MacPod. | ||
512 | if(QString(ent->mnt_dir) == path | ||
513 | && (QString(ent->mnt_type).contains("vfat", Qt::CaseInsensitive) | ||
514 | || QString(ent->mnt_type).contains("hfs", Qt::CaseInsensitive))) { | ||
515 | endmntent(mn); | ||
516 | LOG_INFO() << "device name is" << ent->mnt_fsname; | ||
517 | return QString(ent->mnt_fsname); | ||
518 | } | ||
519 | } | ||
520 | endmntent(mn); | ||
521 | |||
522 | #endif | ||
523 | |||
524 | #if defined(Q_OS_MACX) || defined(Q_OS_OPENBSD) | ||
525 | int num; | ||
526 | struct statfs *mntinf; | ||
527 | |||
528 | num = getmntinfo(&mntinf, MNT_WAIT); | ||
529 | while(num--) { | ||
530 | // check for valid filesystem type. OS X can handle hfs (hfs+ is | ||
531 | // treated as hfs), BSD should be the same. | ||
532 | if(QString(mntinf->f_mntonname) == path | ||
533 | && (QString(mntinf->f_fstypename).contains("msdos", Qt::CaseInsensitive) | ||
534 | || QString(mntinf->f_fstypename).contains("hfs", Qt::CaseInsensitive))) { | ||
535 | LOG_INFO() << "device name is" << mntinf->f_mntfromname; | ||
536 | return QString(mntinf->f_mntfromname); | ||
537 | } | ||
538 | mntinf++; | ||
539 | } | ||
540 | #endif | ||
541 | |||
542 | #if defined(Q_OS_WIN32) | ||
543 | DWORD written; | ||
544 | HANDLE h; | ||
545 | TCHAR uncpath[MAX_PATH]; | ||
546 | UCHAR buffer[0x400]; | ||
547 | PVOLUME_DISK_EXTENTS extents = (PVOLUME_DISK_EXTENTS)buffer; | ||
548 | |||
549 | _stprintf(uncpath, _TEXT("\\\\.\\%c:"), path.toLatin1().at(0)); | ||
550 | h = CreateFile(uncpath, GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE, | ||
551 | NULL, OPEN_EXISTING, 0, NULL); | ||
552 | if(h == INVALID_HANDLE_VALUE) { | ||
553 | //LOG_INFO() << "error getting extents for" << uncpath; | ||
554 | return ""; | ||
555 | } | ||
556 | // get the extents | ||
557 | if(DeviceIoControl(h, IOCTL_VOLUME_GET_VOLUME_DISK_EXTENTS, | ||
558 | NULL, 0, extents, sizeof(buffer), &written, NULL)) { | ||
559 | if(extents->NumberOfDiskExtents == 1) { | ||
560 | CloseHandle(h); | ||
561 | LOG_INFO() << "device name is" << extents->Extents[0].DiskNumber; | ||
562 | return QString("%1").arg(extents->Extents[0].DiskNumber); | ||
563 | } | ||
564 | LOG_INFO() << "resolving device name: volume spans multiple disks!"; | ||
565 | } | ||
566 | CloseHandle(h); | ||
567 | #endif | ||
568 | return QString(""); | ||
569 | |||
570 | } | ||
571 | |||
572 | |||
573 | /** resolve device name to mount point / drive letter | ||
574 | * @param device device name / disk number | ||
575 | * @return mount point / drive letter | ||
576 | */ | ||
577 | QString Utils::resolveMountPoint(QString device) | ||
578 | { | ||
579 | LOG_INFO() << "resolving mountpoint:" << device; | ||
580 | |||
581 | #if defined(Q_OS_LINUX) | ||
582 | FILE *mn = setmntent("/etc/mtab", "r"); | ||
583 | if(!mn) | ||
584 | return QString(""); | ||
585 | |||
586 | struct mntent *ent; | ||
587 | while((ent = getmntent(mn))) { | ||
588 | // Check for valid filesystem. Allow hfs too, as an Ipod might be a | ||
589 | // MacPod. | ||
590 | if(QString(ent->mnt_fsname) == device) { | ||
591 | QString result; | ||
592 | if(QString(ent->mnt_type).contains("vfat", Qt::CaseInsensitive) | ||
593 | || QString(ent->mnt_type).contains("hfs", Qt::CaseInsensitive)) { | ||
594 | LOG_INFO() << "resolved mountpoint is:" << ent->mnt_dir; | ||
595 | result = QString(ent->mnt_dir); | ||
596 | } | ||
597 | else { | ||
598 | LOG_INFO() << "mountpoint is wrong filesystem!"; | ||
599 | } | ||
600 | endmntent(mn); | ||
601 | return result; | ||
602 | } | ||
603 | } | ||
604 | endmntent(mn); | ||
605 | |||
606 | #endif | ||
607 | |||
608 | #if defined(Q_OS_MACX) || defined(Q_OS_OPENBSD) | ||
609 | int num; | ||
610 | struct statfs *mntinf; | ||
611 | |||
612 | num = getmntinfo(&mntinf, MNT_WAIT); | ||
613 | while(num--) { | ||
614 | // Check for valid filesystem. Allow hfs too, as an Ipod might be a | ||
615 | // MacPod. | ||
616 | if(QString(mntinf->f_mntfromname) == device) { | ||
617 | if(QString(mntinf->f_fstypename).contains("msdos", Qt::CaseInsensitive) | ||
618 | || QString(mntinf->f_fstypename).contains("hfs", Qt::CaseInsensitive)) { | ||
619 | LOG_INFO() << "resolved mountpoint is:" << mntinf->f_mntonname; | ||
620 | return QString(mntinf->f_mntonname); | ||
621 | } | ||
622 | else { | ||
623 | LOG_INFO() << "mountpoint is wrong filesystem!"; | ||
624 | return QString(); | ||
625 | } | ||
626 | } | ||
627 | mntinf++; | ||
628 | } | ||
629 | #endif | ||
630 | |||
631 | #if defined(Q_OS_WIN32) | ||
632 | QString result; | ||
633 | unsigned int driveno = device.replace(QRegExp("^.*([0-9]+)"), "\\1").toInt(); | ||
634 | |||
635 | int letter; | ||
636 | for(letter = 'A'; letter <= 'Z'; letter++) { | ||
637 | if(resolveDevicename(QString(letter)).toUInt() == driveno) { | ||
638 | result = letter; | ||
639 | LOG_INFO() << "resolved mountpoint is:" << result; | ||
640 | break; | ||
641 | } | ||
642 | } | ||
643 | if(!result.isEmpty()) | ||
644 | return result + ":/"; | ||
645 | #endif | ||
646 | LOG_INFO() << "resolving mountpoint failed!"; | ||
647 | return QString(""); | ||
648 | } | ||
649 | |||
650 | |||
651 | QStringList Utils::mountpoints(enum MountpointsFilter type) | ||
652 | { | ||
653 | QStringList supported; | ||
654 | QStringList tempList; | ||
655 | #if defined(Q_OS_WIN32) | ||
656 | supported << "FAT32" << "FAT16" << "FAT12" << "FAT" << "HFS"; | ||
657 | QFileInfoList list = QDir::drives(); | ||
658 | for(int i=0; i<list.size();i++) | ||
659 | { | ||
660 | wchar_t t[32]; | ||
661 | memset(t, 0, 32); | ||
662 | if(GetVolumeInformationW((LPCWSTR)list.at(i).absolutePath().utf16(), | ||
663 | NULL, 0, NULL, NULL, NULL, t, 32) == 0) { | ||
664 | // on error empty retrieved type -- don't rely on | ||
665 | // GetVolumeInformation not changing it. | ||
666 | memset(t, 0, sizeof(t)); | ||
667 | } | ||
668 | |||
669 | QString fstype = QString::fromWCharArray(t); | ||
670 | if(type == MountpointsAll || supported.contains(fstype)) { | ||
671 | tempList << list.at(i).absolutePath(); | ||
672 | LOG_INFO() << "Added:" << list.at(i).absolutePath() | ||
673 | << "type" << fstype; | ||
674 | } | ||
675 | else { | ||
676 | LOG_INFO() << "Ignored:" << list.at(i).absolutePath() | ||
677 | << "type" << fstype; | ||
678 | } | ||
679 | } | ||
680 | |||
681 | #elif defined(Q_OS_MACX) || defined(Q_OS_OPENBSD) | ||
682 | supported << "vfat" << "msdos" << "hfs"; | ||
683 | int num; | ||
684 | struct statfs *mntinf; | ||
685 | |||
686 | num = getmntinfo(&mntinf, MNT_WAIT); | ||
687 | while(num--) { | ||
688 | if(type == MountpointsAll || supported.contains(mntinf->f_fstypename)) { | ||
689 | tempList << QString(mntinf->f_mntonname); | ||
690 | LOG_INFO() << "Added:" << mntinf->f_mntonname | ||
691 | << "is" << mntinf->f_mntfromname << "type" << mntinf->f_fstypename; | ||
692 | } | ||
693 | else { | ||
694 | LOG_INFO() << "Ignored:" << mntinf->f_mntonname | ||
695 | << "is" << mntinf->f_mntfromname << "type" << mntinf->f_fstypename; | ||
696 | } | ||
697 | mntinf++; | ||
698 | } | ||
699 | #elif defined(Q_OS_LINUX) | ||
700 | supported << "vfat" << "msdos" << "hfsplus"; | ||
701 | FILE *mn = setmntent("/etc/mtab", "r"); | ||
702 | if(!mn) | ||
703 | return QStringList(""); | ||
704 | |||
705 | struct mntent *ent; | ||
706 | while((ent = getmntent(mn))) { | ||
707 | if(type == MountpointsAll || supported.contains(ent->mnt_type)) { | ||
708 | tempList << QString(ent->mnt_dir); | ||
709 | LOG_INFO() << "Added:" << ent->mnt_dir | ||
710 | << "is" << ent->mnt_fsname << "type" << ent->mnt_type; | ||
711 | } | ||
712 | else { | ||
713 | LOG_INFO() << "Ignored:" << ent->mnt_dir | ||
714 | << "is" << ent->mnt_fsname << "type" << ent->mnt_type; | ||
715 | } | ||
716 | } | ||
717 | endmntent(mn); | ||
718 | |||
719 | #else | ||
720 | #error Unknown Platform | ||
721 | #endif | ||
722 | return tempList; | ||
723 | } | ||
724 | |||
725 | |||
726 | /** Check if a process with a given name is running | ||
727 | * @param names list of names to filter on. All processes if empty list. | ||
728 | * @return list of processname, process ID pairs. | ||
729 | */ | ||
730 | QMap<QString, QList<int> > Utils::findRunningProcess(QStringList names) | ||
731 | { | ||
732 | QMap<QString, QList<int> > processlist; | ||
733 | QMap<QString, QList<int> > found; | ||
734 | #if defined(Q_OS_WIN32) | ||
735 | HANDLE hdl; | ||
736 | PROCESSENTRY32 entry; | ||
737 | bool result; | ||
738 | |||
739 | hdl = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0); | ||
740 | if(hdl == INVALID_HANDLE_VALUE) { | ||
741 | LOG_ERROR() << "CreateToolhelp32Snapshot failed."; | ||
742 | return found; | ||
743 | } | ||
744 | entry.dwSize = sizeof(PROCESSENTRY32); | ||
745 | entry.szExeFile[0] = '\0'; | ||
746 | if(!Process32First(hdl, &entry)) { | ||
747 | LOG_ERROR() << "Process32First failed."; | ||
748 | return found; | ||
749 | } | ||
750 | |||
751 | do { | ||
752 | int pid = entry.th32ProcessID; // FIXME: DWORD vs int! | ||
753 | QString name = QString::fromWCharArray(entry.szExeFile); | ||
754 | if(processlist.find(name) == processlist.end()) { | ||
755 | processlist.insert(name, QList<int>()); | ||
756 | } | ||
757 | processlist[name].append(pid); | ||
758 | entry.dwSize = sizeof(PROCESSENTRY32); | ||
759 | entry.szExeFile[0] = '\0'; | ||
760 | result = Process32Next(hdl, &entry); | ||
761 | } while(result); | ||
762 | CloseHandle(hdl); | ||
763 | #endif | ||
764 | #if defined(Q_OS_MACX) | ||
765 | ProcessSerialNumber psn = { 0, kNoProcess }; | ||
766 | OSErr err; | ||
767 | do { | ||
768 | pid_t pid; | ||
769 | err = GetNextProcess(&psn); | ||
770 | err = GetProcessPID(&psn, &pid); | ||
771 | if(err == noErr) { | ||
772 | char buf[32] = {0}; | ||
773 | ProcessInfoRec info; | ||
774 | memset(&info, 0, sizeof(ProcessInfoRec)); | ||
775 | info.processName = (unsigned char*)buf; | ||
776 | info.processInfoLength = sizeof(ProcessInfoRec); | ||
777 | err = GetProcessInformation(&psn, &info); | ||
778 | if(err == noErr) { | ||
779 | // some processes start with nonprintable characters. Skip those. | ||
780 | int i; | ||
781 | for(i = 0; i < 32; i++) { | ||
782 | if(isprint(buf[i])) break; | ||
783 | } | ||
784 | // avoid adding duplicates. | ||
785 | QString name = QString::fromUtf8(&buf[i]); | ||
786 | if(processlist.find(name) == processlist.end()) { | ||
787 | processlist.insert(name, QList<int>()); | ||
788 | } | ||
789 | processlist[name].append(pid); | ||
790 | } | ||
791 | } | ||
792 | } while(err == noErr); | ||
793 | #endif | ||
794 | #if defined(Q_OS_LINUX) | ||
795 | // not implemented for Linux! | ||
796 | #endif | ||
797 | // Filter for names (unless empty) | ||
798 | if(names.size() > 0) { | ||
799 | for(int i = 0; i < names.size(); ++i) { | ||
800 | QStringList k(processlist.keys()); | ||
801 | #if defined(Q_OS_WIN32) | ||
802 | // the process name might be truncated. Allow the extension to be partial. | ||
803 | int index = k.indexOf(QRegExp(names.at(i) + "(\\.(e(x(e?)?)?)?)?", | ||
804 | Qt::CaseInsensitive)); | ||
805 | #else | ||
806 | int index = k.indexOf(names.at(i)); | ||
807 | #endif | ||
808 | if(index != -1) { | ||
809 | found.insert(k[index], processlist[k[index]]); | ||
810 | } | ||
811 | } | ||
812 | } | ||
813 | else { | ||
814 | found = processlist; | ||
815 | } | ||
816 | LOG_INFO() << "Looking for processes" << names << "found" << found; | ||
817 | return found; | ||
818 | } | ||
819 | |||
820 | |||
821 | /** Suspends/resumes processes | ||
822 | * @param pidlist a list of PIDs to suspend/resume | ||
823 | * @param suspend processes are suspended if true, or resumed when false | ||
824 | * @return a list of PIDs successfully suspended/resumed | ||
825 | */ | ||
826 | QList<int> Utils::suspendProcess(QList<int> pidlist, bool suspend) | ||
827 | { | ||
828 | QList<int> result; | ||
829 | #if defined(Q_OS_WIN32) | ||
830 | // Enable debug privilege | ||
831 | HANDLE hToken = NULL; | ||
832 | LUID seDebugValue; | ||
833 | TOKEN_PRIVILEGES tNext, tPrev; | ||
834 | DWORD sPrev; | ||
835 | if(LookupPrivilegeValue(NULL, SE_DEBUG_NAME, &seDebugValue)) { | ||
836 | if(OpenProcessToken(GetCurrentProcess(), | ||
837 | TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &hToken)) { | ||
838 | memset(&tNext, 0, sizeof(tNext)); | ||
839 | tNext.PrivilegeCount = 1; | ||
840 | tNext.Privileges[0].Luid = seDebugValue; | ||
841 | tNext.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED; | ||
842 | if(!AdjustTokenPrivileges(hToken, FALSE, &tNext, sizeof(tNext), | ||
843 | &tPrev, &sPrev) || GetLastError() != 0) { | ||
844 | CloseHandle(hToken); | ||
845 | hToken = NULL; | ||
846 | LOG_ERROR() << "AdjustTokenPrivileges(next) error" << GetLastError(); | ||
847 | } | ||
848 | } | ||
849 | else { | ||
850 | LOG_ERROR() << "OpenProcessToken error" << GetLastError(); | ||
851 | } | ||
852 | } | ||
853 | else { | ||
854 | LOG_ERROR() << "LookupPrivilegeValue error" << GetLastError(); | ||
855 | } | ||
856 | |||
857 | // Suspend/resume threads | ||
858 | for(int i = 0; i < pidlist.size(); i++) { | ||
859 | HANDLE hdl = INVALID_HANDLE_VALUE; | ||
860 | THREADENTRY32 entry; | ||
861 | int n_fails = 0; | ||
862 | |||
863 | hdl = CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, 0); | ||
864 | if(hdl == INVALID_HANDLE_VALUE) { | ||
865 | LOG_ERROR() << "CreateToolhelp32Snapshot error" << GetLastError(); | ||
866 | continue; | ||
867 | } | ||
868 | entry.dwSize = sizeof(THREADENTRY32); | ||
869 | if(!Thread32First(hdl, &entry)) { | ||
870 | LOG_ERROR() << "Process32First error" << GetLastError(); | ||
871 | CloseHandle(hdl); | ||
872 | continue; | ||
873 | } | ||
874 | |||
875 | do { | ||
876 | if(entry.th32OwnerProcessID != (DWORD)(pidlist[i])) | ||
877 | continue; | ||
878 | HANDLE thr = OpenThread(THREAD_SUSPEND_RESUME, | ||
879 | FALSE, entry.th32ThreadID); | ||
880 | if(!thr) { | ||
881 | LOG_ERROR() << "OpenThread" << entry.th32ThreadID | ||
882 | << "error" << GetLastError(); | ||
883 | n_fails++; | ||
884 | continue; | ||
885 | } | ||
886 | if(suspend) { | ||
887 | // Execution of the specified thread is suspended and | ||
888 | // the thread's suspend count is incremented. | ||
889 | if(SuspendThread(thr) == (DWORD)(-1)) { | ||
890 | LOG_ERROR() << "SuspendThread" << entry.th32ThreadID | ||
891 | << "error" << GetLastError(); | ||
892 | n_fails++; | ||
893 | } | ||
894 | } | ||
895 | else { | ||
896 | // Decrements a thread's suspend count. When the | ||
897 | // suspend count is decremented to zero, the | ||
898 | // execution of the thread is resumed. | ||
899 | if(ResumeThread(thr) == (DWORD)(-1)) { | ||
900 | LOG_ERROR() << "ResumeThread" << entry.th32ThreadID | ||
901 | << "error" << GetLastError(); | ||
902 | n_fails++; | ||
903 | } | ||
904 | } | ||
905 | CloseHandle(thr); | ||
906 | } while(Thread32Next(hdl, &entry)); | ||
907 | if (!n_fails) | ||
908 | result.append(pidlist[i]); | ||
909 | CloseHandle(hdl); | ||
910 | } | ||
911 | |||
912 | // Restore previous debug privilege | ||
913 | if (hToken) { | ||
914 | if(!AdjustTokenPrivileges(hToken, FALSE, | ||
915 | &tPrev, sPrev, NULL, NULL) || GetLastError() != 0) { | ||
916 | LOG_ERROR() << "AdjustTokenPrivileges(prev) error" << GetLastError(); | ||
917 | } | ||
918 | CloseHandle(hToken); | ||
919 | } | ||
920 | #endif | ||
921 | #if defined(Q_OS_MACX) | ||
922 | int signal = suspend ? SIGSTOP : SIGCONT; | ||
923 | for(int i = 0; i < pidlist.size(); i++) { | ||
924 | pid_t pid = pidlist[i]; | ||
925 | if(kill(pid, signal) != 0) { | ||
926 | LOG_ERROR() << "kill signal" << signal | ||
927 | << "for PID" << pid << "error:" << errno; | ||
928 | } | ||
929 | else { | ||
930 | result.append(pidlist[i]); | ||
931 | } | ||
932 | } | ||
933 | #endif | ||
934 | #if defined(Q_OS_LINUX) | ||
935 | // not implemented for Linux! | ||
936 | #endif | ||
937 | LOG_INFO() << (suspend ? "Suspending" : "Resuming") | ||
938 | << "PIDs" << pidlist << "result" << result; | ||
939 | return result; | ||
940 | } | ||
941 | |||
942 | |||
943 | /** Eject device from PC. | ||
944 | * Request the OS to eject the player. | ||
945 | * @param device mountpoint of the device | ||
946 | * @return true on success, fals otherwise. | ||
947 | */ | ||
948 | bool Utils::ejectDevice(QString device) | ||
949 | { | ||
950 | #if defined(Q_OS_WIN32) | ||
951 | /* See http://support.microsoft.com/kb/165721 on the procedure to eject a | ||
952 | * device. */ | ||
953 | bool success = false; | ||
954 | int i; | ||
955 | HANDLE hdl; | ||
956 | DWORD bytesReturned; | ||
957 | TCHAR volume[8]; | ||
958 | |||
959 | /* CreateFile */ | ||
960 | _stprintf(volume, _TEXT("\\\\.\\%c:"), device.toLatin1().at(0)); | ||
961 | hdl = CreateFile(volume, GENERIC_READ | GENERIC_WRITE, | ||
962 | FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, | ||
963 | OPEN_EXISTING, 0, NULL); | ||
964 | if(hdl == INVALID_HANDLE_VALUE) | ||
965 | return false; | ||
966 | |||
967 | /* lock volume to make sure no other application is accessing the volume. | ||
968 | * Try up to 10 times. */ | ||
969 | for(i = 0; i < 10; i++) { | ||
970 | if(DeviceIoControl(hdl, FSCTL_LOCK_VOLUME, | ||
971 | NULL, 0, NULL, 0, &bytesReturned, NULL)) | ||
972 | break; | ||
973 | /* short break before retry */ | ||
974 | Sleep(100); | ||
975 | } | ||
976 | if(i < 10) { | ||
977 | /* successfully locked, now dismount */ | ||
978 | if(DeviceIoControl(hdl, FSCTL_DISMOUNT_VOLUME, | ||
979 | NULL, 0, NULL, 0, &bytesReturned, NULL)) { | ||
980 | /* make sure media can be removed. */ | ||
981 | PREVENT_MEDIA_REMOVAL pmr; | ||
982 | pmr.PreventMediaRemoval = false; | ||
983 | if(DeviceIoControl(hdl, IOCTL_STORAGE_MEDIA_REMOVAL, | ||
984 | &pmr, sizeof(PREVENT_MEDIA_REMOVAL), | ||
985 | NULL, 0, &bytesReturned, NULL)) { | ||
986 | /* eject the media */ | ||
987 | if(DeviceIoControl(hdl, IOCTL_STORAGE_EJECT_MEDIA, | ||
988 | NULL, 0, NULL, 0, &bytesReturned, NULL)) | ||
989 | success = true; | ||
990 | } | ||
991 | } | ||
992 | } | ||
993 | /* close handle */ | ||
994 | CloseHandle(hdl); | ||
995 | return success; | ||
996 | |||
997 | #endif | ||
998 | #if defined(Q_OS_MACX) | ||
999 | // FIXME: FSUnmountVolumeSync is deprecated starting with 10.8. | ||
1000 | // Use DADiskUnmount / DiskArbitration framework eventually. | ||
1001 | // BSD label does not include folder. | ||
1002 | QString bsd = Utils::resolveDevicename(device).remove("/dev/"); | ||
1003 | OSStatus result; | ||
1004 | ItemCount index = 1; | ||
1005 | bool found = false; | ||
1006 | |||
1007 | do { | ||
1008 | FSVolumeRefNum volrefnum; | ||
1009 | |||
1010 | result = FSGetVolumeInfo(kFSInvalidVolumeRefNum, index, &volrefnum, | ||
1011 | kFSVolInfoFSInfo, NULL, NULL, NULL); | ||
1012 | if(result == noErr) { | ||
1013 | GetVolParmsInfoBuffer volparms; | ||
1014 | /* See above -- PBHGetVolParmsSync() is not available for 64bit, | ||
1015 | * and FSGetVolumeParms() on 10.5+ only. */ | ||
1016 | #if __ENVIRONMENT_MAC_OS_X_VERSION_MIN_REQUIRED__ >= 1050 | ||
1017 | if(FSGetVolumeParms(volrefnum, &volparms, sizeof(volparms)) == noErr) | ||
1018 | #else | ||
1019 | HParamBlockRec hpb; | ||
1020 | hpb.ioParam.ioNamePtr = NULL; | ||
1021 | hpb.ioParam.ioVRefNum = volrefnum; | ||
1022 | hpb.ioParam.ioBuffer = (Ptr)&volparms; | ||
1023 | hpb.ioParam.ioReqCount = sizeof(volparms); | ||
1024 | if(PBHGetVolParmsSync(&hpb) == noErr) | ||
1025 | #endif | ||
1026 | { | ||
1027 | if(volparms.vMServerAdr == 0) { | ||
1028 | if(bsd == (char*)volparms.vMDeviceID) { | ||
1029 | pid_t dissenter; | ||
1030 | result = FSUnmountVolumeSync(volrefnum, 0, &dissenter); | ||
1031 | found = true; | ||
1032 | break; | ||
1033 | } | ||
1034 | } | ||
1035 | } | ||
1036 | } | ||
1037 | index++; | ||
1038 | } while(result == noErr); | ||
1039 | if(result == noErr && found) | ||
1040 | return true; | ||
1041 | |||
1042 | #endif | ||
1043 | #if defined(Q_OS_LINUX) | ||
1044 | (void)device; | ||
1045 | #endif | ||
1046 | return false; | ||
1047 | } | ||
1048 | |||
1049 | |||
1050 | qint64 Utils::recursiveFolderSize(QString path) | ||
1051 | { | ||
1052 | qint64 size = 0; | ||
1053 | QList<QFileInfo> items = QDir(path).entryInfoList(QDir::Files | QDir::NoDotAndDotDot); | ||
1054 | for (auto item: items) { | ||
1055 | size += item.size(); | ||
1056 | } | ||
1057 | QList<QString> folders = QDir(path).entryList(QDir::Dirs | QDir::NoDotAndDotDot); | ||
1058 | for (auto folder: folders) { | ||
1059 | size += recursiveFolderSize(path + "/" + folder); | ||
1060 | } | ||
1061 | return size; | ||
1062 | } | ||
diff --git a/utils/rbutilqt/base/utils.h b/utils/rbutilqt/base/utils.h new file mode 100644 index 0000000000..7f27c7d321 --- /dev/null +++ b/utils/rbutilqt/base/utils.h | |||
@@ -0,0 +1,64 @@ | |||
1 | /*************************************************************************** | ||
2 | * __________ __ ___. | ||
3 | * Open \______ \ ____ ____ | | _\_ |__ _______ ___ | ||
4 | * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / | ||
5 | * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < | ||
6 | * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ | ||
7 | * \/ \/ \/ \/ \/ | ||
8 | * | ||
9 | * Copyright (C) 2007 by Dominik Wenger | ||
10 | * | ||
11 | * This program is free software; you can redistribute it and/or | ||
12 | * modify it under the terms of the GNU General Public License | ||
13 | * as published by the Free Software Foundation; either version 2 | ||
14 | * of the License, or (at your option) any later version. | ||
15 | * | ||
16 | * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY | ||
17 | * KIND, either express or implied. | ||
18 | * | ||
19 | ****************************************************************************/ | ||
20 | |||
21 | |||
22 | #ifndef UTILS_H | ||
23 | #define UTILS_H | ||
24 | |||
25 | #include <QtCore/QObject> | ||
26 | |||
27 | #include <QString> | ||
28 | #include <QUrl> | ||
29 | |||
30 | class Utils : public QObject | ||
31 | { | ||
32 | public: | ||
33 | enum Size { | ||
34 | FilesystemTotal, | ||
35 | FilesystemFree, | ||
36 | FilesystemClusterSize, | ||
37 | }; | ||
38 | enum MountpointsFilter { | ||
39 | MountpointsAll, | ||
40 | MountpointsSupported, | ||
41 | }; | ||
42 | |||
43 | static bool recursiveRmdir(const QString &dirName); | ||
44 | static QString resolvePathCase(QString path); | ||
45 | static qulonglong filesystemFree(QString path); | ||
46 | static qulonglong filesystemTotal(QString path); | ||
47 | static qulonglong filesystemSize(QString path, enum Size type); | ||
48 | static QString filesystemType(QString path); | ||
49 | static QString findExecutable(QString name); | ||
50 | static QString checkEnvironment(bool permission); | ||
51 | static int compareVersionStrings(QString s1, QString s2); | ||
52 | static QString trimVersionString(QString s); | ||
53 | static QString filesystemName(QString path); | ||
54 | static QStringList mountpoints(enum MountpointsFilter type = MountpointsAll); | ||
55 | static QString resolveDevicename(QString path); | ||
56 | static QString resolveMountPoint(QString device); | ||
57 | static QMap<QString, QList<int> > findRunningProcess(QStringList names); | ||
58 | static QList<int> suspendProcess(QList<int> pidlist, bool suspend); | ||
59 | static bool ejectDevice(QString device); | ||
60 | static qint64 recursiveFolderSize(QString path); | ||
61 | }; | ||
62 | |||
63 | #endif | ||
64 | |||
diff --git a/utils/rbutilqt/base/voicefile.cpp b/utils/rbutilqt/base/voicefile.cpp new file mode 100644 index 0000000000..eabf7a721a --- /dev/null +++ b/utils/rbutilqt/base/voicefile.cpp | |||
@@ -0,0 +1,362 @@ | |||
1 | /*************************************************************************** | ||
2 | * __________ __ ___. | ||
3 | * Open \______ \ ____ ____ | | _\_ |__ _______ ___ | ||
4 | * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / | ||
5 | * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < | ||
6 | * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ | ||
7 | * \/ \/ \/ \/ \/ | ||
8 | * | ||
9 | * Copyright (C) 2007 by Dominik Wenger | ||
10 | * | ||
11 | * All files in this archive are subject to the GNU General Public License. | ||
12 | * See the file COPYING in the source tree root for full license agreement. | ||
13 | * | ||
14 | * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY | ||
15 | * KIND, either express or implied. | ||
16 | * | ||
17 | ****************************************************************************/ | ||
18 | |||
19 | #include <QtCore> | ||
20 | #include "voicefile.h" | ||
21 | #include "utils.h" | ||
22 | #include "rockboxinfo.h" | ||
23 | #include "rbsettings.h" | ||
24 | #include "playerbuildinfo.h" | ||
25 | #include "ziputil.h" | ||
26 | #include "Logger.h" | ||
27 | |||
28 | VoiceFileCreator::VoiceFileCreator(QObject* parent) :QObject(parent) | ||
29 | { | ||
30 | m_wavtrimThreshold=500; | ||
31 | } | ||
32 | |||
33 | void VoiceFileCreator::abort() | ||
34 | { | ||
35 | m_abort = true; | ||
36 | emit aborted(); | ||
37 | } | ||
38 | |||
39 | bool VoiceFileCreator::createVoiceFile() | ||
40 | { | ||
41 | m_talkList.clear(); | ||
42 | m_abort = false; | ||
43 | emit logItem(tr("Starting Voicefile generation"),LOGINFO); | ||
44 | |||
45 | // test if tempdir exists | ||
46 | if(!QDir(QDir::tempPath()+"/rbvoice/").exists()) | ||
47 | { | ||
48 | QDir(QDir::tempPath()).mkdir("rbvoice"); | ||
49 | } | ||
50 | m_path = QDir::tempPath() + "/rbvoice/"; | ||
51 | |||
52 | // read rockbox-info.txt | ||
53 | RockboxInfo info(m_mountpoint); | ||
54 | if(!info.success()) | ||
55 | { | ||
56 | emit logItem(tr("could not find rockbox-info.txt"),LOGERROR); | ||
57 | emit done(true); | ||
58 | return false; | ||
59 | } | ||
60 | QString target = info.target(); | ||
61 | QString features = info.features(); | ||
62 | m_targetid = info.targetID().toInt(); | ||
63 | m_versionstring = info.version(); | ||
64 | m_voiceformat = info.voicefmt(); | ||
65 | QString version = m_versionstring.left(m_versionstring.indexOf("-")).remove("r"); | ||
66 | |||
67 | // check if voicefile is present on target | ||
68 | QString fn = m_mountpoint + "/.rockbox/langs/voicestrings.zip"; | ||
69 | LOG_INFO() << "searching for zipped voicestrings at" << fn; | ||
70 | if(QFileInfo(fn).isFile()) { | ||
71 | // search for binary voice strings file in archive | ||
72 | ZipUtil z(this); | ||
73 | if(z.open(fn)) { | ||
74 | QStringList contents = z.files(); | ||
75 | int index; | ||
76 | for(index = 0; index < contents.size(); ++index) { | ||
77 | // strip any path, we don't know the structure in the zip | ||
78 | if(QFileInfo(contents.at(index)).baseName() == m_lang) { | ||
79 | break; | ||
80 | } | ||
81 | } | ||
82 | if(index < contents.size()) { | ||
83 | LOG_INFO() << "extracting strings file from zip"; | ||
84 | // extract strings | ||
85 | QTemporaryFile stringsfile; | ||
86 | stringsfile.open(); | ||
87 | QString sfn = stringsfile.fileName(); | ||
88 | // ZipUtil::extractArchive() only compares the filename. | ||
89 | if(z.extractArchive(sfn, QFileInfo(contents.at(index)).fileName())) { | ||
90 | emit logItem(tr("Extracted voice strings from installation"), LOGINFO); | ||
91 | |||
92 | stringsfile.seek(0); | ||
93 | QByteArray data = stringsfile.readAll(); | ||
94 | const char* buf = data.constData(); | ||
95 | // check file header | ||
96 | // header (4 bytes): cookie = 9a, version = 06, targetid, options | ||
97 | // subheader for each user. Only "core" for now. | ||
98 | // subheader (6 bytes): count (2bytes), size (2bytes), offset (2bytes) | ||
99 | if(buf[0] != (char)0x9a || buf[1] != 0x06 || buf[2] != m_targetid) { | ||
100 | emit logItem(tr("Extracted voice strings incompatible"), LOGINFO); | ||
101 | } | ||
102 | else { | ||
103 | QMap<int, QString> voicestrings; | ||
104 | |||
105 | /* skip header */ | ||
106 | int idx = 10; | ||
107 | do { | ||
108 | unsigned int id = ((unsigned char)buf[idx])<<8 | ||
109 | | ((unsigned char)buf[idx+1]); | ||
110 | // need to use strlen here, since QString::size() | ||
111 | // returns number of characters, not bytes. | ||
112 | int len = strlen(&buf[idx + 2]); | ||
113 | voicestrings[id] = QString::fromUtf8(&buf[idx+2]); | ||
114 | idx += 2 + len + 1; | ||
115 | |||
116 | } while(idx < data.size()); | ||
117 | |||
118 | stringsfile.close(); | ||
119 | |||
120 | // create input file suitable for voicefont from strings. | ||
121 | QTemporaryFile voicefontlist; | ||
122 | voicefontlist.open(); | ||
123 | m_filename = voicefontlist.fileName(); | ||
124 | for(int i = 0; i < voicestrings.size(); ++i) { | ||
125 | QByteArray qba; | ||
126 | qba = QString("id: %1_%2\n") | ||
127 | .arg(voicestrings.keys().at(i) < 0x8000 ? "LANG" : "VOICE") | ||
128 | .arg(voicestrings.keys().at(i)).toUtf8(); | ||
129 | voicefontlist.write(qba); | ||
130 | qba = QString("voice: \"%1\"\n").arg( | ||
131 | voicestrings[voicestrings.keys().at(i)]).toUtf8(); | ||
132 | voicefontlist.write(qba); | ||
133 | } | ||
134 | voicefontlist.close(); | ||
135 | |||
136 | // everything successful, now create the actual voice file. | ||
137 | create(); | ||
138 | return true; | ||
139 | } | ||
140 | |||
141 | } | ||
142 | } | ||
143 | } | ||
144 | } | ||
145 | emit logItem(tr("Could not retrieve strings from installation, downloading"), LOGINFO); | ||
146 | // if either no zip with voice strings is found or something went wrong | ||
147 | // retrieving the necessary files we'll end up here, trying to get the | ||
148 | // genlang output as previously from the webserver. | ||
149 | |||
150 | // prepare download url | ||
151 | QString genlang = PlayerBuildInfo::instance()->value( | ||
152 | PlayerBuildInfo::GenlangUrl).toString(); | ||
153 | genlang.replace("%LANG%", m_lang); | ||
154 | genlang.replace("%TARGET%", target); | ||
155 | genlang.replace("%REVISION%", version); | ||
156 | genlang.replace("%FEATURES%", features); | ||
157 | QUrl genlangUrl(genlang); | ||
158 | LOG_INFO() << "downloading" << genlangUrl; | ||
159 | |||
160 | //download the correct genlang output | ||
161 | QTemporaryFile *downloadFile = new QTemporaryFile(this); | ||
162 | downloadFile->open(); | ||
163 | m_filename = downloadFile->fileName(); | ||
164 | downloadFile->close(); | ||
165 | // get the real file. | ||
166 | getter = new HttpGet(this); | ||
167 | getter->setFile(downloadFile); | ||
168 | |||
169 | connect(getter, &HttpGet::done, this, &VoiceFileCreator::downloadDone); | ||
170 | connect(getter, &HttpGet::dataReadProgress, this, &VoiceFileCreator::logProgress); | ||
171 | connect(this, &VoiceFileCreator::aborted, getter, &HttpGet::abort); | ||
172 | emit logItem(tr("Downloading voice info..."),LOGINFO); | ||
173 | getter->getFile(genlangUrl); | ||
174 | return true; | ||
175 | } | ||
176 | |||
177 | |||
178 | void VoiceFileCreator::downloadDone(bool error) | ||
179 | { | ||
180 | LOG_INFO() << "download done, error:" << error; | ||
181 | |||
182 | // update progress bar | ||
183 | emit logProgress(1,1); | ||
184 | if(getter->httpResponse() != 200 && !getter->isCached()) { | ||
185 | emit logItem(tr("Download error: received HTTP error %1.") | ||
186 | .arg(getter->httpResponse()),LOGERROR); | ||
187 | emit done(true); | ||
188 | return; | ||
189 | } | ||
190 | |||
191 | if(getter->isCached()) | ||
192 | emit logItem(tr("Cached file used."), LOGINFO); | ||
193 | if(error) | ||
194 | { | ||
195 | emit logItem(tr("Download error: %1").arg(getter->errorString()),LOGERROR); | ||
196 | emit done(true); | ||
197 | return; | ||
198 | } | ||
199 | else | ||
200 | emit logItem(tr("Download finished."),LOGINFO); | ||
201 | |||
202 | QCoreApplication::processEvents(); | ||
203 | create(); | ||
204 | } | ||
205 | |||
206 | |||
207 | void VoiceFileCreator::create(void) | ||
208 | { | ||
209 | //open downloaded file | ||
210 | QFile genlang(m_filename); | ||
211 | if(!genlang.open(QIODevice::ReadOnly)) | ||
212 | { | ||
213 | emit logItem(tr("failed to open downloaded file"),LOGERROR); | ||
214 | emit done(true); | ||
215 | return; | ||
216 | } | ||
217 | |||
218 | //read in downloaded file | ||
219 | emit logItem(tr("Reading strings..."),LOGINFO); | ||
220 | QTextStream in(&genlang); | ||
221 | #if QT_VERSION < 0x060000 | ||
222 | in.setCodec("UTF-8"); | ||
223 | #else | ||
224 | in.setEncoding(QStringConverter::Utf8); | ||
225 | #endif | ||
226 | QString id, voice; | ||
227 | bool idfound = false; | ||
228 | bool voicefound=false; | ||
229 | bool useCorrection = RbSettings::value(RbSettings::UseTtsCorrections).toBool(); | ||
230 | while (!in.atEnd()) | ||
231 | { | ||
232 | QString line = in.readLine(); | ||
233 | if(line.contains("id:")) //ID found | ||
234 | { | ||
235 | id = line.remove("id:").remove('"').trimmed(); | ||
236 | idfound = true; | ||
237 | } | ||
238 | else if(line.contains("voice:")) // voice found | ||
239 | { | ||
240 | voice = line.remove("voice:").remove('"').trimmed(); | ||
241 | voice = voice.remove("<").remove(">"); | ||
242 | voicefound=true; | ||
243 | } | ||
244 | |||
245 | if(idfound && voicefound) | ||
246 | { | ||
247 | TalkGenerator::TalkEntry entry; | ||
248 | entry.toSpeak = voice; | ||
249 | entry.wavfilename = m_path + "/" + id + ".wav"; | ||
250 | //voicefont wants them with .mp3 extension | ||
251 | entry.talkfilename = m_path + "/" + id + ".mp3"; | ||
252 | entry.voiced = false; | ||
253 | entry.encoded = false; | ||
254 | if(id == "VOICE_PAUSE") | ||
255 | { | ||
256 | QFile::copy(":/builtin/VOICE_PAUSE.wav",m_path + "/VOICE_PAUSE.wav"); | ||
257 | entry.wavfilename = m_path + "/VOICE_PAUSE.wav"; | ||
258 | entry.voiced = true; | ||
259 | m_talkList.append(entry); | ||
260 | } | ||
261 | else if(entry.toSpeak.isEmpty()) { | ||
262 | LOG_WARNING() << "Empty voice string for ID" << id; | ||
263 | } | ||
264 | else { | ||
265 | m_talkList.append(entry); | ||
266 | } | ||
267 | idfound=false; | ||
268 | voicefound=false; | ||
269 | } | ||
270 | } | ||
271 | genlang.close(); | ||
272 | |||
273 | // check for empty list | ||
274 | if(m_talkList.size() == 0) | ||
275 | { | ||
276 | emit logItem(tr("The downloaded file was empty!"),LOGERROR); | ||
277 | emit done(true); | ||
278 | return; | ||
279 | } | ||
280 | |||
281 | // generate files | ||
282 | { | ||
283 | TalkGenerator generator(this); | ||
284 | // set language for string correction. If not set no correction will be made. | ||
285 | if(useCorrection) | ||
286 | generator.setLang(m_lang); | ||
287 | connect(&generator, &TalkGenerator::done, this, &VoiceFileCreator::done); | ||
288 | connect(&generator, &TalkGenerator::logItem, this, &VoiceFileCreator::logItem); | ||
289 | connect(&generator, &TalkGenerator::logProgress, this, &VoiceFileCreator::logProgress); | ||
290 | connect(this, &VoiceFileCreator::aborted, &generator, &TalkGenerator::abort); | ||
291 | |||
292 | if(generator.process(&m_talkList, m_wavtrimThreshold) == TalkGenerator::eERROR) | ||
293 | { | ||
294 | cleanup(); | ||
295 | emit logProgress(0,1); | ||
296 | emit done(true); | ||
297 | return; | ||
298 | } | ||
299 | } | ||
300 | |||
301 | //make voicefile | ||
302 | emit logItem(tr("Creating voicefiles..."),LOGINFO); | ||
303 | FILE* ids2 = fopen(m_filename.toLocal8Bit(), "r"); | ||
304 | if (ids2 == nullptr) | ||
305 | { | ||
306 | cleanup(); | ||
307 | emit logItem(tr("Error opening downloaded file"),LOGERROR); | ||
308 | emit done(true); | ||
309 | return; | ||
310 | } | ||
311 | |||
312 | FILE* output = fopen(QString(m_mountpoint + "/.rockbox/langs/" + m_lang | ||
313 | + ".voice").toLocal8Bit(), "wb"); | ||
314 | if (output == nullptr) | ||
315 | { | ||
316 | cleanup(); | ||
317 | fclose(ids2); | ||
318 | emit logItem(tr("Error opening output file"),LOGERROR); | ||
319 | emit done(true); | ||
320 | return; | ||
321 | } | ||
322 | |||
323 | LOG_INFO() << "Running voicefont, format" << m_voiceformat; | ||
324 | voicefont(ids2,m_targetid,m_path.toLocal8Bit().data(), output, m_voiceformat); | ||
325 | // ids2 and output are closed by voicefont(). | ||
326 | |||
327 | //cleanup | ||
328 | cleanup(); | ||
329 | |||
330 | // Add Voice file to the install log | ||
331 | QSettings installlog(m_mountpoint + "/.rockbox/rbutil.log", QSettings::IniFormat, nullptr); | ||
332 | installlog.beginGroup(QString("Voice (self created, %1)").arg(m_lang)); | ||
333 | installlog.setValue("/.rockbox/langs/" + m_lang + ".voice", m_versionstring); | ||
334 | installlog.endGroup(); | ||
335 | installlog.sync(); | ||
336 | |||
337 | emit logProgress(1,1); | ||
338 | emit logItem(tr("successfully created."),LOGOK); | ||
339 | |||
340 | emit done(false); | ||
341 | } | ||
342 | |||
343 | //! \brief Cleans up Files potentially left in the temp dir | ||
344 | //! | ||
345 | void VoiceFileCreator::cleanup() | ||
346 | { | ||
347 | emit logItem(tr("Cleaning up..."),LOGINFO); | ||
348 | |||
349 | for(int i=0; i < m_talkList.size(); i++) | ||
350 | { | ||
351 | if(QFile::exists(m_talkList[i].wavfilename)) | ||
352 | QFile::remove(m_talkList[i].wavfilename); | ||
353 | if(QFile::exists(m_talkList[i].talkfilename)) | ||
354 | QFile::remove(m_talkList[i].talkfilename); | ||
355 | |||
356 | QCoreApplication::processEvents(); | ||
357 | } | ||
358 | emit logItem(tr("Finished"),LOGINFO); | ||
359 | |||
360 | return; | ||
361 | } | ||
362 | |||
diff --git a/utils/rbutilqt/base/voicefile.h b/utils/rbutilqt/base/voicefile.h new file mode 100644 index 0000000000..d34535e700 --- /dev/null +++ b/utils/rbutilqt/base/voicefile.h | |||
@@ -0,0 +1,77 @@ | |||
1 | /*************************************************************************** | ||
2 | * __________ __ ___. | ||
3 | * Open \______ \ ____ ____ | | _\_ |__ _______ ___ | ||
4 | * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / | ||
5 | * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < | ||
6 | * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ | ||
7 | * \/ \/ \/ \/ \/ | ||
8 | * | ||
9 | * Copyright (C) 2007 by Dominik Wenger | ||
10 | * | ||
11 | * This program is free software; you can redistribute it and/or | ||
12 | * modify it under the terms of the GNU General Public License | ||
13 | * as published by the Free Software Foundation; either version 2 | ||
14 | * of the License, or (at your option) any later version. | ||
15 | * | ||
16 | * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY | ||
17 | * KIND, either express or implied. | ||
18 | * | ||
19 | ****************************************************************************/ | ||
20 | |||
21 | |||
22 | #ifndef VOICEFILE_H | ||
23 | #define VOICEFILE_H | ||
24 | |||
25 | #include <QtCore> | ||
26 | #include "progressloggerinterface.h" | ||
27 | |||
28 | #include "httpget.h" | ||
29 | #include "voicefont.h" | ||
30 | #include "talkgenerator.h" | ||
31 | |||
32 | class VoiceFileCreator :public QObject | ||
33 | { | ||
34 | Q_OBJECT | ||
35 | public: | ||
36 | VoiceFileCreator(QObject* parent); | ||
37 | |||
38 | //start creation | ||
39 | bool createVoiceFile(); | ||
40 | |||
41 | void setMountPoint(QString mountpoint) {m_mountpoint =mountpoint; } | ||
42 | void setLang(QString name) { m_lang = name; } | ||
43 | void setWavtrimThreshold(int th){m_wavtrimThreshold = th;} | ||
44 | |||
45 | public slots: | ||
46 | void abort(); | ||
47 | |||
48 | signals: | ||
49 | void done(bool); | ||
50 | void aborted(); | ||
51 | void logItem(QString, int); //! set logger item | ||
52 | void logProgress(int, int); //! set progress bar. | ||
53 | |||
54 | private slots: | ||
55 | void downloadDone(bool error); | ||
56 | |||
57 | private: | ||
58 | |||
59 | void create(void); | ||
60 | void cleanup(); | ||
61 | |||
62 | HttpGet *getter; | ||
63 | QString m_filename; //the temporary file | ||
64 | QString m_mountpoint; //mountpoint of the device | ||
65 | QString m_path; //path where the wav and mp3 files are stored to | ||
66 | int m_targetid; //the target id | ||
67 | QString m_lang; // the language which will be spoken | ||
68 | QString m_versionstring; // version string to be used for logging | ||
69 | int m_wavtrimThreshold; | ||
70 | int m_voiceformat; | ||
71 | |||
72 | bool m_abort; | ||
73 | QList<TalkGenerator::TalkEntry> m_talkList; | ||
74 | }; | ||
75 | |||
76 | #endif | ||
77 | |||
diff --git a/utils/rbutilqt/base/zipinstaller.cpp b/utils/rbutilqt/base/zipinstaller.cpp new file mode 100644 index 0000000000..8de6c53b16 --- /dev/null +++ b/utils/rbutilqt/base/zipinstaller.cpp | |||
@@ -0,0 +1,204 @@ | |||
1 | /*************************************************************************** | ||
2 | * __________ __ ___. | ||
3 | * Open \______ \ ____ ____ | | _\_ |__ _______ ___ | ||
4 | * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / | ||
5 | * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < | ||
6 | * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ | ||
7 | * \/ \/ \/ \/ \/ | ||
8 | * | ||
9 | * Copyright (C) 2007 by Dominik Wenger | ||
10 | * | ||
11 | * All files in this archive are subject to the GNU General Public License. | ||
12 | * See the file COPYING in the source tree root for full license agreement. | ||
13 | * | ||
14 | * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY | ||
15 | * KIND, either express or implied. | ||
16 | * | ||
17 | ****************************************************************************/ | ||
18 | |||
19 | #include <QtCore> | ||
20 | #include "zipinstaller.h" | ||
21 | #include "utils.h" | ||
22 | #include "ziputil.h" | ||
23 | #include "Logger.h" | ||
24 | |||
25 | ZipInstaller::ZipInstaller(QObject* parent) : | ||
26 | QObject(parent), | ||
27 | m_unzip(true), m_usecache(false), m_getter(nullptr) | ||
28 | { | ||
29 | } | ||
30 | |||
31 | |||
32 | void ZipInstaller::install() | ||
33 | { | ||
34 | LOG_INFO() << "initializing installation"; | ||
35 | |||
36 | m_runner = 0; | ||
37 | connect(this, &ZipInstaller::cont, this, &ZipInstaller::installContinue); | ||
38 | m_url = m_urllist.at(m_runner); | ||
39 | m_logsection = m_loglist.at(m_runner); | ||
40 | m_logver = m_verlist.at(m_runner); | ||
41 | installStart(); | ||
42 | } | ||
43 | |||
44 | |||
45 | void ZipInstaller::abort() | ||
46 | { | ||
47 | LOG_INFO() << "Aborted"; | ||
48 | emit internalAborted(); | ||
49 | } | ||
50 | |||
51 | |||
52 | void ZipInstaller::installContinue() | ||
53 | { | ||
54 | LOG_INFO() << "continuing installation"; | ||
55 | |||
56 | m_runner++; // this gets called when a install finished, so increase first. | ||
57 | LOG_INFO() << "runner done:" << m_runner << "/" << m_urllist.size(); | ||
58 | if(m_runner < m_urllist.size()) { | ||
59 | emit logItem(tr("done."), LOGOK); | ||
60 | m_url = m_urllist.at(m_runner); | ||
61 | m_logsection = m_loglist.at(m_runner); | ||
62 | if(m_runner < m_verlist.size()) m_logver = m_verlist.at(m_runner); | ||
63 | else m_logver = ""; | ||
64 | installStart(); | ||
65 | } | ||
66 | else { | ||
67 | emit logItem(tr("Package installation finished successfully."), LOGOK); | ||
68 | emit done(false); | ||
69 | return; | ||
70 | } | ||
71 | } | ||
72 | |||
73 | |||
74 | void ZipInstaller::installStart() | ||
75 | { | ||
76 | LOG_INFO() << "starting installation"; | ||
77 | |||
78 | emit logItem(tr("Downloading file %1.%2").arg(QFileInfo(m_url).baseName(), | ||
79 | QFileInfo(m_url).completeSuffix()),LOGINFO); | ||
80 | |||
81 | // temporary file needs to be opened to get the filename | ||
82 | // make sure to get a fresh one on each run. | ||
83 | // making this a parent of the temporary file ensures the file gets deleted | ||
84 | // after the class object gets destroyed. | ||
85 | m_downloadFile = new QTemporaryFile(this); | ||
86 | m_downloadFile->open(); | ||
87 | m_file = m_downloadFile->fileName(); | ||
88 | m_downloadFile->close(); | ||
89 | // get the real file. | ||
90 | if(m_getter != nullptr) m_getter->deleteLater(); | ||
91 | m_getter = new HttpGet(this); | ||
92 | if(m_usecache) { | ||
93 | m_getter->setCache(true); | ||
94 | } | ||
95 | m_getter->setFile(m_downloadFile); | ||
96 | |||
97 | connect(m_getter, &HttpGet::done, this, &ZipInstaller::downloadDone); | ||
98 | connect(m_getter, &HttpGet::dataReadProgress, this, &ZipInstaller::logProgress); | ||
99 | connect(this, &ZipInstaller::internalAborted, m_getter, &HttpGet::abort); | ||
100 | |||
101 | m_getter->getFile(QUrl(m_url)); | ||
102 | } | ||
103 | |||
104 | |||
105 | void ZipInstaller::downloadDone(bool error) | ||
106 | { | ||
107 | LOG_INFO() << "download done, error:" << error; | ||
108 | QStringList zipContents; // needed later | ||
109 | // update progress bar | ||
110 | |||
111 | emit logProgress(1, 1); | ||
112 | if(m_getter->httpResponse() != 200 && !m_getter->isCached()) { | ||
113 | emit logItem(tr("Download error: received HTTP error %1\n%2") | ||
114 | .arg(m_getter->httpResponse()).arg(m_getter->errorString()), | ||
115 | LOGERROR); | ||
116 | emit done(true); | ||
117 | return; | ||
118 | } | ||
119 | if(m_getter->isCached()) | ||
120 | emit logItem(tr("Cached file used."), LOGINFO); | ||
121 | if(error) { | ||
122 | emit logItem(tr("Download error: %1").arg(m_getter->errorString()), LOGERROR); | ||
123 | emit done(true); | ||
124 | return; | ||
125 | } | ||
126 | else emit logItem(tr("Download finished."),LOGOK); | ||
127 | QCoreApplication::processEvents(); | ||
128 | if(m_unzip) { | ||
129 | // unzip downloaded file | ||
130 | LOG_INFO() << "about to unzip" << m_file << "to" << m_mountpoint; | ||
131 | |||
132 | emit logItem(tr("Extracting file."), LOGINFO); | ||
133 | QCoreApplication::processEvents(); | ||
134 | |||
135 | ZipUtil zip(this); | ||
136 | connect(&zip, &ZipUtil::logProgress, this, &ZipInstaller::logProgress); | ||
137 | connect(&zip, &ZipUtil::logItem, this, &ZipInstaller::logItem); | ||
138 | zip.open(m_file, QuaZip::mdUnzip); | ||
139 | // check for free space. Make sure after installation will still be | ||
140 | // some room for operating (also includes calculation mistakes due to | ||
141 | // cluster sizes on the player). | ||
142 | if((qint64)Utils::filesystemFree(m_mountpoint) | ||
143 | < (zip.totalUncompressedSize( | ||
144 | Utils::filesystemSize(m_mountpoint, Utils::FilesystemClusterSize)) | ||
145 | + 1000000)) { | ||
146 | emit logItem(tr("Not enough disk space! Aborting."), LOGERROR); | ||
147 | emit logProgress(1, 1); | ||
148 | emit done(true); | ||
149 | return; | ||
150 | } | ||
151 | zipContents = zip.files(); | ||
152 | if(!zip.extractArchive(m_mountpoint)) { | ||
153 | emit logItem(tr("Extraction failed!"), LOGERROR); | ||
154 | emit logProgress(1, 1); | ||
155 | emit done(true); | ||
156 | return; | ||
157 | } | ||
158 | zip.close(); | ||
159 | } | ||
160 | else { | ||
161 | if (m_target.isEmpty()) | ||
162 | m_target = QUrl(m_url).fileName(); | ||
163 | QString destfile = m_mountpoint + "/" + m_target; | ||
164 | // only copy the downloaded file to the output location / name | ||
165 | emit logItem(tr("Installing file."), LOGINFO); | ||
166 | LOG_INFO() << "saving downloaded file (no extraction) to" << destfile; | ||
167 | |||
168 | m_downloadFile->open(); // copy fails if file is not opened (filename issue?) | ||
169 | // make sure the required path is existing | ||
170 | QString path = QFileInfo(destfile).absolutePath(); | ||
171 | QDir p; | ||
172 | p.mkpath(path); | ||
173 | // QFile::copy() doesn't overwrite files, so remove old one first | ||
174 | // TODO: compare old and new file and fail if those are different. | ||
175 | QFile(destfile).remove(); | ||
176 | if(!m_downloadFile->copy(destfile)) { | ||
177 | emit logItem(tr("Installing file failed."), LOGERROR); | ||
178 | emit done(true); | ||
179 | return; | ||
180 | } | ||
181 | |||
182 | // add file to log | ||
183 | zipContents.append(m_target); | ||
184 | } | ||
185 | if(m_logver.isEmpty()) { | ||
186 | // if no version info is set use the timestamp of the server file. | ||
187 | m_logver = m_getter->timestamp().toString(Qt::ISODate); | ||
188 | } | ||
189 | |||
190 | emit logItem(tr("Creating installation log"),LOGINFO); | ||
191 | QSettings installlog(m_mountpoint + "/.rockbox/rbutil.log", QSettings::IniFormat, nullptr); | ||
192 | |||
193 | installlog.beginGroup(m_logsection); | ||
194 | for(int i = 0; i < zipContents.size(); i++) | ||
195 | { | ||
196 | installlog.setValue(zipContents.at(i), m_logver); | ||
197 | } | ||
198 | installlog.endGroup(); | ||
199 | installlog.sync(); | ||
200 | |||
201 | emit cont(); | ||
202 | } | ||
203 | |||
204 | |||
diff --git a/utils/rbutilqt/base/zipinstaller.h b/utils/rbutilqt/base/zipinstaller.h new file mode 100644 index 0000000000..59a0f785d9 --- /dev/null +++ b/utils/rbutilqt/base/zipinstaller.h | |||
@@ -0,0 +1,88 @@ | |||
1 | /*************************************************************************** | ||
2 | * __________ __ ___. | ||
3 | * Open \______ \ ____ ____ | | _\_ |__ _______ ___ | ||
4 | * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / | ||
5 | * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < | ||
6 | * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ | ||
7 | * \/ \/ \/ \/ \/ | ||
8 | * | ||
9 | * Copyright (C) 2007 by Dominik Wenger | ||
10 | * | ||
11 | * This program is free software; you can redistribute it and/or | ||
12 | * modify it under the terms of the GNU General Public License | ||
13 | * as published by the Free Software Foundation; either version 2 | ||
14 | * of the License, or (at your option) any later version. | ||
15 | * | ||
16 | * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY | ||
17 | * KIND, either express or implied. | ||
18 | * | ||
19 | ****************************************************************************/ | ||
20 | |||
21 | |||
22 | #ifndef ZIPINSTALLER_H | ||
23 | #define ZIPINSTALLER_H | ||
24 | |||
25 | #include <QtCore> | ||
26 | |||
27 | #include "progressloggerinterface.h" | ||
28 | #include "httpget.h" | ||
29 | #include "Logger.h" | ||
30 | |||
31 | /** Install a file or zip. | ||
32 | * Downloads file(s) from a given URL, and installs by either extracting or | ||
33 | * copying it to the target path set by setMountpoint(). | ||
34 | */ | ||
35 | class ZipInstaller : public QObject | ||
36 | { | ||
37 | Q_OBJECT | ||
38 | public: | ||
39 | ZipInstaller(QObject* parent); | ||
40 | ~ZipInstaller(){} | ||
41 | void install(void); | ||
42 | void setMountPoint(QString mountpoint) {m_mountpoint = mountpoint;} | ||
43 | void setUrl(QString url){m_urllist = QStringList(url);} | ||
44 | void setUrl(QStringList url) { m_urllist = url; } | ||
45 | void setLogSection(QString name) {m_loglist = QStringList(name);} | ||
46 | void setLogSection(QStringList name) { m_loglist = name; } | ||
47 | void setLogVersion(QString v = "") | ||
48 | { m_verlist = QStringList(v); LOG_INFO() << m_verlist;} | ||
49 | void setLogVersion(QStringList v) | ||
50 | { m_verlist = v; LOG_INFO() << m_verlist;} | ||
51 | /** Change between copy and unzip mode. */ | ||
52 | void setUnzip(bool i) { m_unzip = i; } | ||
53 | /** Set target filename for copy mode. | ||
54 | * If not set the filename part of the download URL is used. */ | ||
55 | void setTarget(QString t) { m_target = t; } | ||
56 | void setCache(bool c) { m_usecache = c; } | ||
57 | |||
58 | public slots: | ||
59 | void abort(void); | ||
60 | |||
61 | private slots: | ||
62 | void downloadDone(bool); | ||
63 | void installStart(void); | ||
64 | void installContinue(void); | ||
65 | |||
66 | signals: | ||
67 | void done(bool error); | ||
68 | void cont(); | ||
69 | void logItem(QString, int); //! set logger item | ||
70 | void logProgress(int, int); //! set progress bar. | ||
71 | void internalAborted(void); | ||
72 | |||
73 | private: | ||
74 | QString m_url, m_file, m_mountpoint, m_logsection, m_logver; | ||
75 | QStringList m_urllist, m_loglist, m_verlist; | ||
76 | bool m_unzip; | ||
77 | QString m_target; | ||
78 | int m_runner; | ||
79 | bool m_usecache; | ||
80 | |||
81 | HttpGet *m_getter; | ||
82 | QTemporaryFile *m_downloadFile; | ||
83 | }; | ||
84 | |||
85 | |||
86 | |||
87 | #endif | ||
88 | |||
diff --git a/utils/rbutilqt/base/ziputil.cpp b/utils/rbutilqt/base/ziputil.cpp new file mode 100644 index 0000000000..45119f7d99 --- /dev/null +++ b/utils/rbutilqt/base/ziputil.cpp | |||
@@ -0,0 +1,302 @@ | |||
1 | /*************************************************************************** | ||
2 | * __________ __ ___. | ||
3 | * Open \______ \ ____ ____ | | _\_ |__ _______ ___ | ||
4 | * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / | ||
5 | * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < | ||
6 | * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ | ||
7 | * \/ \/ \/ \/ \/ | ||
8 | * | ||
9 | * Copyright (C) 2011 Dominik Riebeling | ||
10 | * | ||
11 | * All files in this archive are subject to the GNU General Public License. | ||
12 | * See the file COPYING in the source tree root for full license agreement. | ||
13 | * | ||
14 | * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY | ||
15 | * KIND, either express or implied. | ||
16 | * | ||
17 | ****************************************************************************/ | ||
18 | |||
19 | #include <QtCore> | ||
20 | #include <QDebug> | ||
21 | #include "ziputil.h" | ||
22 | #include "progressloggerinterface.h" | ||
23 | #include "Logger.h" | ||
24 | |||
25 | #include "quazip/quazip.h" | ||
26 | #include "quazip/quazipfile.h" | ||
27 | #include "quazip/quazipfileinfo.h" | ||
28 | |||
29 | |||
30 | ZipUtil::ZipUtil(QObject* parent) : ArchiveUtil(parent) | ||
31 | { | ||
32 | m_zip = nullptr; | ||
33 | } | ||
34 | |||
35 | |||
36 | ZipUtil::~ZipUtil() | ||
37 | { | ||
38 | if(m_zip) { | ||
39 | delete m_zip; | ||
40 | } | ||
41 | } | ||
42 | |||
43 | //! @brief open zip file. | ||
44 | //! @param zipfile path to zip file | ||
45 | //! @param mode open mode (see QuaZip::Mode) | ||
46 | //! @return true on success, false otherwise | ||
47 | bool ZipUtil::open(QString& zipfile, QuaZip::Mode mode) | ||
48 | { | ||
49 | m_zip = new QuaZip(zipfile); | ||
50 | return m_zip->open(mode); | ||
51 | } | ||
52 | |||
53 | |||
54 | //! @brief close zip file. | ||
55 | //! @return true on success, false otherwise | ||
56 | bool ZipUtil::close(void) | ||
57 | { | ||
58 | if(!m_zip) { | ||
59 | return false; | ||
60 | } | ||
61 | |||
62 | int error = UNZ_OK; | ||
63 | if(m_zip->isOpen()) { | ||
64 | m_zip->close(); | ||
65 | error = m_zip->getZipError(); | ||
66 | } | ||
67 | delete m_zip; | ||
68 | m_zip = nullptr; | ||
69 | return (error == UNZ_OK) ? true : false; | ||
70 | } | ||
71 | |||
72 | |||
73 | //! @brief extract currently opened archive | ||
74 | //! @brief dest path to extract archive to, can be filename when extracting a | ||
75 | //! single file. | ||
76 | //! @brief file file to extract from archive, full archive if empty. | ||
77 | //! @return true on success, false otherwise | ||
78 | bool ZipUtil::extractArchive(const QString& dest, QString file) | ||
79 | { | ||
80 | LOG_INFO() << "extractArchive" << dest << file; | ||
81 | bool result = true; | ||
82 | if(!m_zip) { | ||
83 | return false; | ||
84 | } | ||
85 | QuaZipFile *currentFile = new QuaZipFile(m_zip); | ||
86 | int entries = m_zip->getEntriesCount(); | ||
87 | int current = 0; | ||
88 | // construct the filename when extracting a single file from an archive. | ||
89 | // if the given destination is a full path use it as output name, | ||
90 | // otherwise use it as path to place the file as named in the archive. | ||
91 | QString singleoutfile; | ||
92 | if(!file.isEmpty() && QFileInfo(dest).isDir()) { | ||
93 | singleoutfile = dest + "/" + file; | ||
94 | } | ||
95 | else if(!file.isEmpty()){ | ||
96 | singleoutfile = dest; | ||
97 | } | ||
98 | for(bool more = m_zip->goToFirstFile(); more; more = m_zip->goToNextFile()) | ||
99 | { | ||
100 | ++current; | ||
101 | // if the entry is a path ignore it. Path existence is ensured separately. | ||
102 | if(m_zip->getCurrentFileName().split("/").last() == "") | ||
103 | continue; | ||
104 | // some tools set the MS-DOS file attributes. Check those for D flag, | ||
105 | // since in some cases a folder entry does not end with a / | ||
106 | QuaZipFileInfo fi; | ||
107 | currentFile->getFileInfo(&fi); | ||
108 | if(fi.externalAttr & 0x10) // FAT entry bit 4 indicating directory | ||
109 | continue; | ||
110 | |||
111 | QString outfilename; | ||
112 | if(!singleoutfile.isEmpty() | ||
113 | && QFileInfo(m_zip->getCurrentFileName()).fileName() == file) { | ||
114 | outfilename = singleoutfile; | ||
115 | } | ||
116 | else if(singleoutfile.isEmpty()) { | ||
117 | outfilename = dest + "/" + m_zip->getCurrentFileName(); | ||
118 | } | ||
119 | if(outfilename.isEmpty()) | ||
120 | continue; | ||
121 | QFile outputFile(outfilename); | ||
122 | // make sure the output path exists | ||
123 | if(!QDir().mkpath(QFileInfo(outfilename).absolutePath())) { | ||
124 | result = false; | ||
125 | emit logItem(tr("Creating output path failed"), LOGERROR); | ||
126 | LOG_INFO() << "creating output path failed for:" | ||
127 | << outfilename; | ||
128 | break; | ||
129 | } | ||
130 | if(!outputFile.open(QFile::WriteOnly)) { | ||
131 | result = false; | ||
132 | emit logItem(tr("Creating output file failed"), LOGERROR); | ||
133 | LOG_INFO() << "creating output file failed:" | ||
134 | << outfilename; | ||
135 | break; | ||
136 | } | ||
137 | currentFile->open(QIODevice::ReadOnly); | ||
138 | outputFile.write(currentFile->readAll()); | ||
139 | if(currentFile->getZipError() != UNZ_OK) { | ||
140 | result = false; | ||
141 | emit logItem(tr("Error during Zip operation"), LOGERROR); | ||
142 | LOG_INFO() << "QuaZip error:" << currentFile->getZipError() | ||
143 | << "on file" << currentFile->getFileName(); | ||
144 | break; | ||
145 | } | ||
146 | currentFile->close(); | ||
147 | outputFile.close(); | ||
148 | |||
149 | emit logProgress(current, entries); | ||
150 | } | ||
151 | delete currentFile; | ||
152 | emit logProgress(1, 1); | ||
153 | |||
154 | return result; | ||
155 | } | ||
156 | |||
157 | |||
158 | //! @brief append a folder to current archive | ||
159 | //! @param source source folder | ||
160 | //! @param basedir base folder for archive. Will get stripped from zip paths. | ||
161 | //! @return true on success, false otherwise | ||
162 | bool ZipUtil::appendDirToArchive(QString& source, QString& basedir) | ||
163 | { | ||
164 | bool result = true; | ||
165 | if(!m_zip || !m_zip->isOpen()) { | ||
166 | LOG_INFO() << "Zip file not open!"; | ||
167 | return false; | ||
168 | } | ||
169 | // get a list of all files and folders. Needed for progress info and avoids | ||
170 | // recursive calls. | ||
171 | QDirIterator iterator(source, QDirIterator::Subdirectories); | ||
172 | QStringList fileList; | ||
173 | while(iterator.hasNext()) { | ||
174 | iterator.next(); | ||
175 | // skip folders, we can't add them. | ||
176 | if(!QFileInfo(iterator.filePath()).isDir()) { | ||
177 | fileList.append(iterator.filePath()); | ||
178 | } | ||
179 | } | ||
180 | LOG_INFO() << "Adding" << fileList.size() << "files to archive"; | ||
181 | |||
182 | int max = fileList.size(); | ||
183 | for(int i = 0; i < max; i++) { | ||
184 | QString current = fileList.at(i); | ||
185 | if(!appendFileToArchive(current, basedir)) { | ||
186 | LOG_ERROR() << "Error appending file" << current | ||
187 | << "to archive" << m_zip->getZipName(); | ||
188 | result = false; | ||
189 | break; | ||
190 | } | ||
191 | emit logProgress(i, max); | ||
192 | } | ||
193 | return result; | ||
194 | } | ||
195 | |||
196 | |||
197 | //! @brief append a single file to current archive | ||
198 | //! | ||
199 | bool ZipUtil::appendFileToArchive(QString& file, QString& basedir) | ||
200 | { | ||
201 | bool result = true; | ||
202 | if(!m_zip || !m_zip->isOpen()) { | ||
203 | LOG_ERROR() << "Zip file not open!"; | ||
204 | return false; | ||
205 | } | ||
206 | // skip folders, we can't add them. | ||
207 | QFileInfo fileinfo(file); | ||
208 | if(fileinfo.isDir()) { | ||
209 | return false; | ||
210 | } | ||
211 | QString infile = fileinfo.canonicalFilePath(); | ||
212 | QString newfile = infile; | ||
213 | newfile.remove(QDir(basedir).canonicalPath() + "/"); | ||
214 | |||
215 | QuaZipFile fout(m_zip); | ||
216 | QFile fin(file); | ||
217 | |||
218 | if(!fin.open(QFile::ReadOnly)) { | ||
219 | LOG_ERROR() << "Could not open file for reading:" << file; | ||
220 | return false; | ||
221 | } | ||
222 | if(!fout.open(QIODevice::WriteOnly, QuaZipNewInfo(newfile, infile))) { | ||
223 | fin.close(); | ||
224 | LOG_ERROR() << "Could not open file for writing:" << newfile; | ||
225 | return false; | ||
226 | } | ||
227 | |||
228 | result = (fout.write(fin.readAll()) < 0) ? false : true; | ||
229 | fin.close(); | ||
230 | fout.close(); | ||
231 | return result; | ||
232 | } | ||
233 | |||
234 | |||
235 | //! @brief calculate total size of extracted files | ||
236 | qint64 ZipUtil::totalUncompressedSize(unsigned int clustersize) | ||
237 | { | ||
238 | qint64 uncompressed = 0; | ||
239 | |||
240 | QList<QuaZipFileInfo> items = contentProperties(); | ||
241 | if(items.size() == 0) { | ||
242 | return -1; | ||
243 | } | ||
244 | int max = items.size(); | ||
245 | if(clustersize > 0) { | ||
246 | for(int i = 0; i < max; ++i) { | ||
247 | qint64 item = items.at(i).uncompressedSize; | ||
248 | uncompressed += (item + clustersize - (item % clustersize)); | ||
249 | } | ||
250 | } | ||
251 | else { | ||
252 | for(int i = 0; i < max; ++i) { | ||
253 | uncompressed += items.at(i).uncompressedSize; | ||
254 | } | ||
255 | } | ||
256 | if(clustersize > 0) { | ||
257 | LOG_INFO() << "calculation rounded to cluster size for each file:" | ||
258 | << clustersize; | ||
259 | } | ||
260 | LOG_INFO() << "size of archive files uncompressed:" | ||
261 | << uncompressed; | ||
262 | return uncompressed; | ||
263 | } | ||
264 | |||
265 | |||
266 | QStringList ZipUtil::files(void) | ||
267 | { | ||
268 | QList<QuaZipFileInfo> items = contentProperties(); | ||
269 | QStringList fileList; | ||
270 | if(items.size() == 0) { | ||
271 | return fileList; | ||
272 | } | ||
273 | int max = items.size(); | ||
274 | for(int i = 0; i < max; ++i) { | ||
275 | fileList.append(items.at(i).name); | ||
276 | } | ||
277 | return fileList; | ||
278 | } | ||
279 | |||
280 | |||
281 | QList<QuaZipFileInfo> ZipUtil::contentProperties() | ||
282 | { | ||
283 | QList<QuaZipFileInfo> items; | ||
284 | if(!m_zip || !m_zip->isOpen()) { | ||
285 | LOG_ERROR() << "Zip file not open!"; | ||
286 | return items; | ||
287 | } | ||
288 | QuaZipFileInfo info; | ||
289 | QuaZipFile currentFile(m_zip); | ||
290 | for(bool more = m_zip->goToFirstFile(); more; more = m_zip->goToNextFile()) | ||
291 | { | ||
292 | currentFile.getFileInfo(&info); | ||
293 | if(currentFile.getZipError() != UNZ_OK) { | ||
294 | LOG_ERROR() << "QuaZip error:" << currentFile.getZipError() | ||
295 | << "on file" << currentFile.getFileName(); | ||
296 | return QList<QuaZipFileInfo>(); | ||
297 | } | ||
298 | items.append(info); | ||
299 | } | ||
300 | return items; | ||
301 | } | ||
302 | |||
diff --git a/utils/rbutilqt/base/ziputil.h b/utils/rbutilqt/base/ziputil.h new file mode 100644 index 0000000000..25c3dce391 --- /dev/null +++ b/utils/rbutilqt/base/ziputil.h | |||
@@ -0,0 +1,53 @@ | |||
1 | /*************************************************************************** | ||
2 | * __________ __ ___. | ||
3 | * Open \______ \ ____ ____ | | _\_ |__ _______ ___ | ||
4 | * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / | ||
5 | * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < | ||
6 | * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ | ||
7 | * \/ \/ \/ \/ \/ | ||
8 | * | ||
9 | * Copyright (C) 2011 Dominik Riebeling | ||
10 | * | ||
11 | * All files in this archive are subject to the GNU General Public License. | ||
12 | * See the file COPYING in the source tree root for full license agreement. | ||
13 | * | ||
14 | * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY | ||
15 | * KIND, either express or implied. | ||
16 | * | ||
17 | ****************************************************************************/ | ||
18 | |||
19 | #ifndef ZIPUTIL_H | ||
20 | #define ZIPUTIL_H | ||
21 | |||
22 | #include <QtCore> | ||
23 | #include "archiveutil.h" | ||
24 | #include "quazip/quazip.h" | ||
25 | #include "quazip/quazipfile.h" | ||
26 | #include "quazip/quazipfileinfo.h" | ||
27 | |||
28 | class ZipUtil : public ArchiveUtil | ||
29 | { | ||
30 | Q_OBJECT | ||
31 | |||
32 | public: | ||
33 | ZipUtil(QObject* parent); | ||
34 | ~ZipUtil(); | ||
35 | bool open(QString& zipfile, QuaZip::Mode mode = QuaZip::mdUnzip); | ||
36 | virtual bool close(void); | ||
37 | virtual bool extractArchive(const QString& dest, QString file = ""); | ||
38 | bool appendDirToArchive(QString& source, QString& basedir); | ||
39 | bool appendFileToArchive(QString& file, QString& basedir); | ||
40 | qint64 totalUncompressedSize(unsigned int clustersize = 0); | ||
41 | virtual QStringList files(void); | ||
42 | |||
43 | signals: | ||
44 | void logProgress(int, int); | ||
45 | void logItem(QString, int); | ||
46 | |||
47 | private: | ||
48 | QList<QuaZipFileInfo> contentProperties(); | ||
49 | QuaZip* m_zip; | ||
50 | |||
51 | }; | ||
52 | #endif | ||
53 | |||