1 /**********************************************************************************************
2 Copyright (C) 2014 Oliver Eichler <oliver.eichler@gmx.de>
3
4 This program is free software: you can redistribute it and/or modify
5 it under the terms of the GNU General Public License as published by
6 the Free Software Foundation, either version 3 of the License, or
7 (at your option) any later version.
8
9 This program is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 GNU General Public License for more details.
13
14 You should have received a copy of the GNU General Public License
15 along with this program. If not, see <http://www.gnu.org/licenses/>.
16
17 **********************************************************************************************/
18
19 #include "canvas/CCanvas.h"
20 #include "device/CDeviceGarmin.h"
21 #include "device/CDeviceGarminArchive.h"
22 #include "gis/CGisListWks.h"
23 #include "gis/fit/CFitProject.h"
24 #include "gis/gpx/CGpxProject.h"
25 #include "gis/tcx/CTcxProject.h"
26 #include "gis/wpt/CGisItemWpt.h"
27
28 #include <QtWidgets>
29 #include <QtXml>
30
CDeviceGarmin(const QString & path,const QString & key,const QString & model,const QString & garminDeviceXml,QTreeWidget * parent)31 CDeviceGarmin::CDeviceGarmin(const QString& path, const QString& key, const QString& model, const QString& garminDeviceXml, QTreeWidget* parent)
32 : IDevice(path, eTypeGarmin, key, parent)
33 , cntImages(0)
34 {
35 setText(CGisListWks::eColumnName, "Garmin");
36
37
38 QFile file(QDir(path).absoluteFilePath(garminDeviceXml));
39 if(!file.open(QIODevice::ReadOnly))
40 {
41 qDebug() << "failed to open" << dir.absoluteFilePath(garminDeviceXml);
42 }
43
44 QDomDocument dom;
45 QString msg;
46 int line;
47 int column;
48 if(!dom.setContent(&file, false, &msg, &line, &column))
49 {
50 qDebug() << QString("Failed to read: %1\nline %2, column %3:\n %4").arg(file.fileName()).arg(line).arg(column).arg(msg);
51 }
52
53 file.close();
54
55 const QDomElement& xmlDevice = dom.documentElement();
56 const QDomNode& xmlModel = xmlDevice.namedItem("Model");
57
58 id = xmlDevice.namedItem("Id").toElement().text().trimmed();
59 description = xmlModel.namedItem("Description").toElement().text().trimmed();
60 partno = xmlModel.namedItem("PartNumber").toElement().text().trimmed();
61
62 setText(CGisListWks::eColumnName, QString("%1 (%2)").arg(description, model));
63 setToolTip(CGisListWks::eColumnName, QString("%1 (%2, %3)").arg(description, partno, model));
64
65 const QDomNode& xmlMassStorageMode = xmlDevice.namedItem("MassStorageMode");
66 const QDomNodeList& xmlDataTypes = xmlMassStorageMode.toElement().elementsByTagName("DataType");
67
68 const int N = xmlDataTypes.count();
69 for(int n = 0; n < N; n++)
70 {
71 const QDomNode& xmlDataType = xmlDataTypes.item(n);
72 const QDomNode& xmlName = xmlDataType.namedItem("Name");
73 const QDomNode& xmlFile = xmlDataType.namedItem("File");
74 const QDomNode& xmlLocation = xmlFile.namedItem("Location");
75 const QDomNode& xmlPath = xmlLocation.namedItem("Path");
76
77 QString name = xmlName.toElement().text().trimmed();
78
79 if(name == "GPSData")
80 {
81 pathGpx = xmlPath.toElement().text().trimmed();
82 }
83 else if(name == "GeotaggedPhotos")
84 {
85 pathPictures = xmlPath.toElement().text().trimmed();
86 }
87 else if(name == "GeocachePhotos")
88 {
89 pathSpoilers = xmlPath.toElement().text().trimmed();
90 }
91 else if(name == "FIT_TYPE_4")
92 {
93 pathActivities = xmlPath.toElement().text().trimmed();
94 }
95 else if(name == "FIT_TYPE_6")
96 {
97 // courses
98 pathCourses = xmlPath.toElement().text().trimmed();
99 }
100 else if(name == "FIT_TYPE_8")
101 {
102 pathLocations = xmlPath.toElement().text().trimmed();
103 }
104 else if(name == "Adventures")
105 {
106 pathAdventures = xmlPath.toElement().text().trimmed();
107 }
108 else if(name == "FitnessCourses")
109 {
110 pathTcx = xmlPath.toElement().text().trimmed();
111 }
112 }
113
114 qDebug() << dir.absoluteFilePath(pathGpx);
115 qDebug() << dir.absoluteFilePath(pathPictures);
116 qDebug() << dir.absoluteFilePath(pathSpoilers);
117 qDebug() << dir.absoluteFilePath(pathActivities);
118 qDebug() << dir.absoluteFilePath(pathCourses);
119 qDebug() << dir.absoluteFilePath(pathLocations);
120 qDebug() << dir.absoluteFilePath(pathAdventures);
121 qDebug() << dir.absoluteFilePath(pathTcx);
122
123
124 if(!dir.exists(pathGpx))
125 {
126 dir.mkpath(pathGpx);
127 }
128 if(!dir.exists(pathPictures))
129 {
130 dir.mkpath(pathPictures);
131 }
132 if(!dir.exists(pathSpoilers))
133 {
134 dir.mkpath(pathSpoilers);
135 }
136 if(!pathAdventures.isEmpty() && !dir.exists(pathAdventures))
137 {
138 dir.mkpath(pathAdventures);
139 }
140 if(!pathTcx.isEmpty() && !dir.exists(pathTcx))
141 {
142 dir.mkpath(pathTcx);
143 }
144
145 createProjectsFromFiles(pathGpx, "gpx");
146 createProjectsFromFiles(pathGpx + "/Current", "gpx");
147
148 QDir dirArchive(dir.absoluteFilePath(pathGpx + "/Archive"));
149 if(dirArchive.exists() && (dirArchive.entryList(QStringList("*.gpx")).count() != 0))
150 {
151 archive = new CDeviceGarminArchive(dir.absoluteFilePath(pathGpx + "/Archive"), this);
152 }
153
154 createProjectsFromFiles(pathActivities, "fit");
155 createProjectsFromFiles(pathCourses, "fit");
156 createProjectsFromFiles(pathLocations, "fit");
157 if(!pathTcx.isEmpty())
158 {
159 createProjectsFromFiles(pathTcx, "tcx");
160 }
161 }
162
createProjectsFromFiles(QString subdirecoty,QString fileEnding)163 void CDeviceGarmin::createProjectsFromFiles(QString subdirecoty, QString fileEnding)
164 {
165 QDir dirLoop(dir.absoluteFilePath(subdirecoty));
166 qDebug() << "reading files from device: " << dirLoop.path();
167 const QStringList& entries = dirLoop.entryList(QStringList("*." + fileEnding));
168 for(const QString& entry : entries)
169 {
170 const QString filename = dirLoop.absoluteFilePath(entry);
171 IGisProject* project = nullptr;
172 if(fileEnding == "fit")
173 {
174 project = new CFitProject(filename, this);
175 }
176 else if(fileEnding == "gpx")
177 {
178 project = new CGpxProject(filename, this);
179 }
180 else if(fileEnding == "tcx")
181 {
182 project = new CTcxProject(filename, this);
183 }
184
185 if(project && !project->isValid())
186 {
187 delete project;
188 }
189 }
190 }
191
~CDeviceGarmin()192 CDeviceGarmin::~CDeviceGarmin()
193 {
194 }
195
insertCopyOfProject(IGisProject * project)196 void CDeviceGarmin::insertCopyOfProject(IGisProject* project)
197 {
198 if(description.startsWith("EDGE 5", Qt::CaseInsensitive))
199 {
200 insertCopyOfProjectAsTcx(project);
201 }
202 else
203 {
204 insertCopyOfProjectAsGpx(project);
205 }
206 }
207
reorderProjects(IGisProject * project)208 void CDeviceGarmin::reorderProjects(IGisProject* project)
209 {
210 // move new project to top of any sub-folder/sub-device item
211 int newIdx = NOIDX;
212 const int myIdx = childCount() - 1;
213 for(int i = myIdx - 1; i >= 0; i--)
214 {
215 IDevice* device = dynamic_cast<IDevice*>(child(i));
216 if(0 == device)
217 {
218 break;
219 }
220
221 newIdx = i;
222 }
223
224 if(newIdx != NOIDX)
225 {
226 takeChild(myIdx);
227 insertChild(newIdx, project);
228 }
229 }
230
simplifiedName(IGisProject * project)231 QString CDeviceGarmin::simplifiedName(IGisProject* project)
232 {
233 QString name = project->getName();
234 return name.remove(QRegExp("[^A-Za-z0-9_]"));
235 }
236
createFileName(IGisProject * project,const QString & path,const QString & suffix)237 QString CDeviceGarmin::createFileName(IGisProject* project, const QString& path, const QString& suffix)
238 {
239 QDir dirTarget = dir.absoluteFilePath(path);
240 return dirTarget.absoluteFilePath(simplifiedName(project) + suffix);
241 }
242
insertCopyOfProjectAsTcx(IGisProject * project)243 void CDeviceGarmin::insertCopyOfProjectAsTcx(IGisProject* project)
244 {
245 QString filename = createFileName(project, pathTcx, ".tcx");
246
247 if(testForExternalProject(filename))
248 {
249 return;
250 }
251
252 CTcxProject* tcx = new CTcxProject(filename, project, this);
253 if(!tcx->isValid())
254 {
255 delete tcx;
256 return;
257 }
258
259
260 if(!tcx->save())
261 {
262 delete tcx;
263 CCanvas::restoreOverrideCursor("~CSelectProjectDialog");
264 return;
265 }
266
267
268 // move new project to top of any sub-folder/sub-device item
269 reorderProjects(tcx);
270 }
271
insertCopyOfProjectAsGpx(IGisProject * project)272 void CDeviceGarmin::insertCopyOfProjectAsGpx(IGisProject* project)
273 {
274 QString filename = createFileName(project, pathGpx, ".gpx");
275
276 if(testForExternalProject(filename))
277 {
278 return;
279 }
280
281 CGpxProject* gpx = new CGpxProject(filename, project, this);
282 if(!gpx->isValid())
283 {
284 delete gpx;
285 return;
286 }
287
288 if(!gpx->save())
289 {
290 delete gpx;
291 return;
292 }
293
294 createAdventureFromProject(project, pathGpx + "/" + simplifiedName(project) + ".gpx");
295
296 // move new project to top of any sub-folder/sub-device item
297 reorderProjects(gpx);
298 }
299
saveImages(CGisItemWpt & wpt)300 void CDeviceGarmin::saveImages(CGisItemWpt& wpt)
301 {
302 if(wpt.isGeocache())
303 {
304 QString name = wpt.getName();
305 quint32 size = name.size();
306 QString path = QString("%1/%2/%3").arg(name.at(size - 1)).arg(name.at(size - 2)).arg(name);
307
308 const QDir dirSpoilers(dir.absoluteFilePath(pathSpoilers));
309 const QDir dirCache(dirSpoilers.absoluteFilePath(path));
310 const QList<CGisItemWpt::image_t>& images = wpt.getImages();
311
312 if(!dirCache.exists())
313 {
314 dirCache.mkpath(dirCache.absolutePath());
315 }
316
317 QString filename;
318 for(const CGisItemWpt::image_t& image : images)
319 {
320 filename = image.info;
321 filename = filename.remove(QRegExp("[^A-Za-z0-9_]"));
322
323 if(!filename.endsWith("jpg"))
324 {
325 filename += ".jpg";
326 }
327 image.pixmap.save(dirCache.absoluteFilePath(filename));
328 }
329 }
330 else
331 {
332 const QDir dirImages(dir.absoluteFilePath(pathPictures));
333 const QString& key = wpt.getKey().project;
334 const QList<CGisItemWpt::image_t>& images = wpt.getImages();
335 QList<IGisItem::link_t> links;
336
337 QString filename;
338 for(const CGisItemWpt::image_t& image : images)
339 {
340 filename = QString("%1.%2.jpg").arg(key).arg(cntImages);
341 image.pixmap.save(dirImages.absoluteFilePath(filename));
342
343 IGisItem::link_t link;
344 link.uri = pathPictures + "/" + filename;
345 link.text = tr("Picture%1").arg(cntImages);
346 link.type = "Garmin";
347
348 links << link;
349
350 cntImages++;
351 }
352
353 wpt.appendLinks(links);
354 }
355 }
356
loadImages(CGisItemWpt & wpt)357 void CDeviceGarmin::loadImages(CGisItemWpt& wpt)
358 {
359 if(wpt.isGeocache())
360 {
361 QString name = wpt.getName();
362 quint32 size = name.size();
363 QString path = QString("%1/%2/%3").arg(name.at(size - 1)).arg(name.at(size - 2)).arg(name);
364
365 const QDir dirSpoilers(dir.absoluteFilePath(pathSpoilers));
366 const QDir dirCache(dirSpoilers.absoluteFilePath(path));
367
368 QList<CGisItemWpt::image_t> images;
369 const QStringList& entries = dirCache.entryList(QStringList("*.jpg"), QDir::Files);
370 for(const QString& file : entries)
371 {
372 CGisItemWpt::image_t image;
373 image.pixmap.load(dirCache.absoluteFilePath(file));
374
375 if(!image.pixmap.isNull())
376 {
377 image.fileName = file;
378 image.info = QFileInfo(file).completeBaseName();
379 images << image;
380 }
381 }
382
383 if(!images.isEmpty())
384 {
385 wpt.appendImages(images);
386 }
387 }
388 else
389 {
390 const QList<IGisItem::link_t>& links = wpt.getLinks();
391 QList<CGisItemWpt::image_t> images;
392
393 for(const IGisItem::link_t& link : links)
394 {
395 if(link.type != "Garmin")
396 {
397 continue;
398 }
399 CGisItemWpt::image_t image;
400 image.fileName = link.text;
401 image.pixmap.load(dir.absoluteFilePath(link.uri.toString()));
402
403 images << image;
404 }
405
406 if(!images.isEmpty())
407 {
408 wpt.appendImages(images);
409 wpt.removeLinksByType("Garmin");
410 }
411 }
412 }
413
startSavingProject(IGisProject * project)414 void CDeviceGarmin::startSavingProject(IGisProject* project)
415 {
416 cntImages = 0;
417 }
418
aboutToRemoveProject(IGisProject * project)419 void CDeviceGarmin::aboutToRemoveProject(IGisProject* project)
420 {
421 // remove images attached to project
422 const QString& key = project->getKey();
423 const QDir dirImages(dir.absoluteFilePath(pathPictures));
424 const QStringList& entries = dirImages.entryList(QStringList("*.jpg"), QDir::Files);
425 for(const QString& entry : entries)
426 {
427 QString filename = dirImages.absoluteFilePath(entry);
428 QFileInfo fi(filename);
429
430 if(fi.baseName() == key)
431 {
432 QFile::remove(filename);
433 }
434 }
435
436 // remove geo cache spoiler images
437 const int N = project->childCount();
438 for(int n = 0; n < N; n++)
439 {
440 CGisItemWpt* wpt = dynamic_cast<CGisItemWpt*>(project->child(n));
441 if(wpt && wpt->isGeocache())
442 {
443 QString name = wpt->getName();
444 quint32 size = name.size();
445 QString path = QString("%1/%2/%3").arg(name.at(size - 1)).arg(name.at(size - 2)).arg(name);
446
447 QDir dirSpoilers(dir.absoluteFilePath(pathSpoilers));
448 QDir dirCache(dirSpoilers.absoluteFilePath(path));
449
450 dirCache.removeRecursively();
451 }
452 }
453
454 if(!pathAdventures.isEmpty())
455 {
456 const QDir dirAdventures(dir.absoluteFilePath(pathAdventures));
457 QString filename = dirAdventures.absoluteFilePath(key + ".adv");
458 QFile::remove(filename);
459 }
460 }
461
462