~ubuntu-branches/ubuntu/natty/pytrainer/natty-proposed

« back to all changes in this revision

Viewing changes to plugins/garmintools_full/garmintools_full.py

  • Committer: Bazaar Package Importer
  • Author(s): Alessio Treglia
  • Date: 2010-02-04 06:07:11 UTC
  • mfrom: (4.1.3 sid)
  • Revision ID: james.westby@ubuntu.com-20100204060711-25n5aw66w5egeiph
Tags: 1.7.1-1ubuntu1
* Merge from debian testing, remaining changes:
  - debian/control:
    + Replace Depends on iceweasel with firefox | abrowser.
    + Bump python-dev,debhelper build-dependencies.
    - Drop dependency on python-glade2 (libglade -> gtkbuilder transition).
  - debian/rules:
    + Append --install-laoyut=deb to setup.py install to prevent a build
      failure with Python 2.6.

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
#!/usr/bin/python
 
2
# -*- coding: iso-8859-1 -*-
 
3
 
 
4
#Copyright (C) Fiz Vazquez vud1@sindominio.net
 
5
 
 
6
#This program is free software; you can redistribute it and/or
 
7
#modify it under the terms of the GNU General Public License
 
8
#as published by the Free Software Foundation; either version 2
 
9
#of the License, or (at your option) any later version.
 
10
 
 
11
#This program is distributed in the hope that it will be useful,
 
12
#but WITHOUT ANY WARRANTY; without even the implied warranty of
 
13
#MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
14
#GNU General Public License for more details.
 
15
 
 
16
#You should have received a copy of the GNU General Public License
 
17
#along with this program; if not, write to the Free Software
 
18
#Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 
19
 
 
20
import os
 
21
import sys
 
22
import logging
 
23
import fnmatch
 
24
import commands
 
25
import StringIO
 
26
from lxml import etree
 
27
from pytrainer.lib.xmlUtils import XMLParser
 
28
import dateutil.parser
 
29
from datetime import date, timedelta, datetime
 
30
from dateutil.tz import * # for tzutc()
 
31
import traceback
 
32
 
 
33
class garmintools_full():
 
34
        """ Plugin to import from a Garmin device using garmintools
 
35
                Checks each activity to see if any entries are in the database with the same start time
 
36
                Creates GPX files for each activity not in the database
 
37
 
 
38
                Note: using lxml see http://codespeak.net/lxml
 
39
        """
 
40
        def __init__(self, parent = None, validate=False):
 
41
                self.parent = parent
 
42
                self.confdir = self.parent.conf.getValue("confdir")
 
43
                self.tmpdir = self.parent.conf.getValue("tmpdir")
 
44
                # Tell garmintools where to save retrieved data from GPS device
 
45
                os.environ['GARMIN_SAVE_RUNS']=self.tmpdir
 
46
                self.data_path = os.path.dirname(__file__)
 
47
                self.validate = validate
 
48
                self.sport = self.getConfValue("Force_sport_to")
 
49
                self.deltaDays = self.getConfValue("Not_older_days")
 
50
                if self.deltaDays is None:
 
51
                        logging.info("Delta days not set, retrieving complete history, defaulting to 0")
 
52
                        self.deltaDays = 0
 
53
                #so far hardcoded to False - dg 20100104
 
54
                #self.legacyComp = self.getConfValue("Legacy_comparison")
 
55
                self.maxGap = self.getConfValue("Max_gap_seconds")
 
56
                if self.maxGap is None:
 
57
                        logging.info("No gap defined, strict comparison")
 
58
                        self.maxGap = 0
 
59
 
 
60
        def getConfValue(self, confVar):
 
61
                info = XMLParser(self.data_path+"/conf.xml")
 
62
                code = info.getValue("pytrainer-plugin","plugincode")
 
63
                plugindir = self.parent.conf.getValue("plugindir")
 
64
                if not os.path.isfile(plugindir+"/"+code+"/conf.xml"):
 
65
                        value = None
 
66
                else:
 
67
                        info = XMLParser(plugindir+"/"+code+"/conf.xml")
 
68
                        value = info.getValue("pytrainer-plugin",confVar)
 
69
                return value
 
70
 
 
71
        def run(self):
 
72
                logging.debug(">>")
 
73
                importFiles = []
 
74
                if self.checkLoadedModule():
 
75
                        numError = self.getDeviceInfo()
 
76
                        if numError >= 0:
 
77
                                #TODO Remove Zenity below
 
78
                                outgps = commands.getstatusoutput("garmin_save_runs | zenity --progress --pulsate --text='Loading Data' auto-close")
 
79
                                if outgps[0]==0: 
 
80
                                        # now we should have a lot of gmn (binary) files under $GARMIN_SAVE_RUNS
 
81
                                        foundFiles = self.searchFiles(self.tmpdir, "gmn")
 
82
                                        logging.info("Retrieved "+str(len(foundFiles))+" entries from GPS device")
 
83
                                        # Trying to minimize number of files to dump
 
84
                                        if int(self.deltaDays) > 0:
 
85
                                                selectedFiles = self.discardOld(foundFiles)
 
86
                                        else:
 
87
                                                logging.info("Retrieving complete history from GPS device")
 
88
                                                selectedFiles = foundFiles
 
89
                                        if len(selectedFiles) > 0:
 
90
                                                logging.info("Dumping "+str(len(selectedFiles))+" binary files found")
 
91
                                                dumpFiles = self.dumpBinaries(selectedFiles)
 
92
                                                self.listStringDBUTC = self.parent.parent.ddbb.select("records","date_time_utc")
 
93
                                                if self.maxGap > 0:
 
94
                                                        logging.info("Starting import. Comparison will be made with "+str(self.maxGap)+" seconds interval")
 
95
                                                else:
 
96
                                                        logging.info("Starting import. Comparison will be strict")
 
97
                                                importFiles = self.importEntries(dumpFiles)
 
98
                                        else:
 
99
                                                logging.info("No new entries to add")
 
100
                                else:
 
101
                                        logging.error("Error when retrieving data from GPS device")
 
102
                        else:
 
103
                                #TODO Remove Zenity below
 
104
                                if numError == -1:
 
105
                                        os.popen("zenity --error --text='No Garmin device found\nCheck your configuration'");
 
106
                                elif numError == -2:
 
107
                                        os.popen("zenity --error --text='Can not find garmintools binaries\nCheck your configuration'")                 
 
108
                else: #No garmin device found
 
109
                                #TODO Remove Zenity below
 
110
                                os.popen("zenity --error --text='Can not handle Garmin device (wrong module loaded)\nCheck your configuration'");
 
111
                logging.info("Entries to import: "+str(len(importFiles)))
 
112
                logging.debug("<<")
 
113
                return importFiles
 
114
 
 
115
        def discardOld(self, listEntries):
 
116
                logging.debug(">>")
 
117
                tempList = []
 
118
                logging.info("Discarding entries older than "+str(self.deltaDays)+" days")
 
119
                limit = datetime.now() - timedelta(days = int(self.deltaDays))
 
120
                for entry in listEntries:
 
121
                        filename = os.path.split(entry)[1].rstrip(".gmn")
 
122
                        filenameDateTime = datetime.strptime(filename,"%Y%m%dT%H%M%S")
 
123
                        logging.debug("Entry time: "+str(filenameDateTime)+" | limit: "+str(limit))
 
124
                        if filenameDateTime < limit:
 
125
                                logging.debug("Discarding old entry: "+str(filenameDateTime))
 
126
                        else:
 
127
                                tempList.append(entry)
 
128
                logging.debug("<<")
 
129
                return tempList
 
130
 
 
131
        def importEntries(self, entries):
 
132
                # modified from garmintools plugin written by jb
 
133
                logging.debug(">>")
 
134
                logging.debug("Selected files: "+str(entries))
 
135
                importfiles = []
 
136
                for filename in entries:
 
137
                        if self.valid_input_file(filename):
 
138
                                #Garmin dump files are not valid xml - need to load into a xmltree
 
139
                                #read file into string
 
140
                                with open(filename, 'r') as f:
 
141
                                        xmlString = f.read()
 
142
                                fileString = StringIO.StringIO("<root>"+xmlString+"</root>")
 
143
                                #parse string as xml
 
144
                                tree = etree.parse(fileString)
 
145
                                #if not self.inDatabase(tree, filename):
 
146
                                if not self.entryExists(tree, filename):
 
147
                                        sport = self.getSport(tree)
 
148
                                        gpxfile = "%s/garmintools-%d.gpx" % (self.tmpdir, len(importfiles))                                     
 
149
                                        self.createGPXfile(gpxfile, tree)
 
150
                                        importfiles.append((gpxfile, sport))
 
151
                                else:
 
152
                                        logging.debug("%s already present. Skipping import." % (filename,) )
 
153
                        else:
 
154
                                logging.error("File %s failed validation" % (filename))
 
155
                logging.debug("<<")
 
156
                return importfiles
 
157
 
 
158
        def valid_input_file(self, filename):
 
159
                """ Function to validate input file if requested"""
 
160
                if not self.validate:  #not asked to validate
 
161
                        logging.debug("Not validating %s" % (filename) )
 
162
                        return True
 
163
                else:
 
164
                        logging.debug("Cannot validate garmintools dump files yet")
 
165
                        return True
 
166
                        '''xslfile = os.path.realpath(self.parent.parent.data_path)+ "/schemas/GarminTrainingCenterDatabase_v2.xsd"
 
167
                        from lib.xmlValidation import xmlValidator
 
168
                        validator = xmlValidator()
 
169
                        return validator.validateXSL(filename, xslfile)'''
 
170
 
 
171
        def entryExists(self, tree, filename):
 
172
                logging.debug(">>")
 
173
                stringStartDatetime = self.detailsFromFile(tree) # this time is localtime! (with timezone offset)
 
174
                exists = False
 
175
                if stringStartDatetime is not None:
 
176
                        startDatetime = dateutil.parser.parse(stringStartDatetime)
 
177
                        # converting to utc for proper comparison with date_time_utc
 
178
                        stringStartUTC = startDatetime.astimezone(tzutc()).strftime("%Y-%m-%dT%H:%M:%SZ")
 
179
                        if self.checkDupe(stringStartUTC, self.listStringDBUTC, int(self.maxGap)):
 
180
                                exists = True
 
181
                        else:
 
182
                                logging.info("Marking "+str(filename)+" | "+str(stringStartUTC)+" to import")
 
183
                                exists = False
 
184
                else:
 
185
                        logging.debug("Not able to find start time, please check "+str(filename))
 
186
                        exists = True # workaround for old/not correct entries (will crash at some point during import process otherwise)
 
187
                logging.debug("<<")
 
188
                return exists
 
189
 
 
190
        def checkDupe(self, stringStartUTC, listStringStartUTC, gap):
 
191
                """ Checks if there is any startUTC in DB between provided startUTC plus a defined gap:
 
192
                        Check for same day (as baselined to UTC)
 
193
                        startDatetime + delta (~ 3 mins) >= listDatetime[x]
 
194
                        args:
 
195
                                stringStartUTC
 
196
                                listStringStartUTC
 
197
                                gap
 
198
                        returns: True if any coincidence is found. False otherwise"""
 
199
                logging.debug(">>")
 
200
                found = False
 
201
                if gap > 0:
 
202
                        # Retrieve date from 2010-01-14T11:34:49Z
 
203
                        stringStartDate = stringStartUTC[0:10]
 
204
                        for entry in listStringStartUTC:
 
205
                                #logging.debug("start: "+str(startDatetime)+" | entry: "+str(entry)+" | gap: "+str(datetimePlusDelta))
 
206
                                if entry[0] is not None:                
 
207
                                        if str(entry[0]).startswith(stringStartDate):
 
208
                                                deltaGap = timedelta(seconds=gap)
 
209
                                                datetimeStartUTC = datetime.strptime(stringStartUTC,"%Y-%m-%dT%H:%M:%SZ")
 
210
                                                datetimeStartUTCDB = datetime.strptime(entry[0],"%Y-%m-%dT%H:%M:%SZ")
 
211
                                                datetimePlusDelta = datetimeStartUTC + deltaGap
 
212
                                                if datetimeStartUTC <= datetimeStartUTCDB and datetimeStartUTCDB <= datetimePlusDelta:
 
213
                                                        found = True
 
214
                                                        logging.debug("Found: "+str(stringStartUTC)+" <= "+str(entry[0])+" <= "+str(datetimePlusDelta))
 
215
                                                        break
 
216
                else:
 
217
                        if (stringStartUTC,) in listStringStartUTC: # strange way to store results from DB
 
218
                                found = True            
 
219
                logging.debug("<<")
 
220
                return found
 
221
 
 
222
        def getSport(self, tree):
 
223
                #return sport from file or overide if present
 
224
                if self.sport:
 
225
                        return self.sport
 
226
                root = tree.getroot()
 
227
                sportElement = root.find(".//run")
 
228
                try:
 
229
                        sport = sportElement.get("sport")
 
230
                        sport = sport.capitalize()
 
231
                except:
 
232
                        sport = "import"
 
233
                return sport
 
234
 
 
235
        def detailsFromFile(self, tree):
 
236
                root = tree.getroot()
 
237
                #Find first point
 
238
                pointElement = root.find(".//point")
 
239
                if pointElement is not None:
 
240
                        stringStartDatetime = pointElement.get("time")
 
241
                        return stringStartDatetime
 
242
                return None
 
243
 
 
244
        def createGPXfile(self, gpxfile, tree):
 
245
                """ Function to transform a Garmintools dump file to a valid GPX+ file
 
246
                """
 
247
                xslt_doc = etree.parse(self.data_path+"/translate.xsl")
 
248
                transform = etree.XSLT(xslt_doc)
 
249
                result_tree = transform(tree)
 
250
                result_tree.write(gpxfile, xml_declaration=True)
 
251
 
 
252
        def dumpBinaries(self, listFiles):
 
253
                logging.debug(">>")
 
254
                dumpFiles=[]
 
255
                for filename in listFiles:
 
256
                        outdump = filename.replace('.gmn', '.dump')
 
257
                        logging.debug("outdump: "+str(outdump))
 
258
                        result = commands.getstatusoutput("garmin_dump %s > %s" %(filename,outdump))
 
259
                        if result[0] == 0:
 
260
                                dumpFiles.append(outdump)
 
261
                        else:
 
262
                                logging.error("Error when creating dump of "+str(filename)+": "+str(result))
 
263
                logging.debug("<<")
 
264
                return dumpFiles
 
265
 
 
266
        def searchFiles(self, rootPath, extension):
 
267
                logging.debug(">>")
 
268
                foundFiles=[]
 
269
                logging.debug("rootPath: "+str(rootPath))
 
270
                result = commands.getstatusoutput("find %s -name *.%s" %(rootPath,extension))
 
271
                if result[0] == 0:
 
272
                        foundFiles = result[1].splitlines()
 
273
                        #logging.debug("Found files: "+str(foundFiles))
 
274
                        logging.info ("Found files: "+str(len(foundFiles)))
 
275
                else:
 
276
                        logging.error("Not able to locate files from GPS: "+str(result))
 
277
                logging.debug("<<")
 
278
                return foundFiles
 
279
 
 
280
        def getDeviceInfo(self):
 
281
                logging.debug(">>")
 
282
                result = commands.getstatusoutput('garmin_get_info')
 
283
                logging.debug("Returns "+str(result))
 
284
                numError = 0
 
285
                if result[0] == 0:
 
286
                        if result[1] != "garmin unit could not be opened!":
 
287
                                try:
 
288
                                        #ToDo: review, always get "lxml.etree.XMLSyntaxError: PCDATA invalid Char value 28, line 6, column 29" error
 
289
                                        xmlString = result[1].rstrip()
 
290
                                        logging.debug("xmlString: "+str(xmlString))
 
291
                                        prueba = etree.XMLID(xmlString)
 
292
                                        logging.debug("Prueba: "+str(prueba))
 
293
                                        tree = etree.fromstring(xmlString)
 
294
                                        description = self.getProductDesc(tree)
 
295
                                        if description is not None:
 
296
                                                logging.info("Found "+str(description))
 
297
                                        else:
 
298
                                                raise Exception
 
299
                                except:
 
300
                                        logging.error("Not able to identify GPS device. Continuing anyway...")
 
301
                                        pass
 
302
                        else:
 
303
                                logging.error(result[1])
 
304
                                numError = -1
 
305
                else:
 
306
                        logging.error("Can not find garmintools binaries, please check your installation")
 
307
                        numError = -2
 
308
                logging.debug("<<")
 
309
                return numError
 
310
 
 
311
        def getProductDesc(self, tree):
 
312
                root = tree.getroot()
 
313
                pointProduct = root.find(".//garmin_product")
 
314
                if pointProduct is not None:
 
315
                        desc = pointProduct.get("product_description")
 
316
                        return desc
 
317
                return None
 
318
 
 
319
        def checkLoadedModule(self):
 
320
                try:
 
321
                        outmod = commands.getstatusoutput('/sbin/lsmod | grep garmin_gps')
 
322
                        if outmod[0]==256:      #there is no garmin_gps module loaded
 
323
                                return True
 
324
                        else:
 
325
                                return False
 
326
                except:
 
327
                        return False
 
328
 
 
329
        def createUserdirBackup(self):
 
330
                logging.debug('>>')
 
331
                result = commands.getstatusoutput('tar -cvzf '+os.environ['HOME']+'/pytrainer_`date +%Y%m%d_%H%M`.tar.gz '+self.confdir)
 
332
                if result[0] != 0:
 
333
                        raise Exception, "Copying current user directory does not work, error #"+str(result)
 
334
                else:
 
335
                        logging.info('User directory backup successfully created')
 
336
                logging.debug('<<')
 
337