def __init__(self,filename = None, data_path = None): #Version constants self.version ="1.10.0-dev" #Process command line options self.startup_options = self.get_options() #Setup logging self.environment = Environment(platform.get_platform(), self.startup_options.conf_dir) self.environment.create_directories() self.set_logging(self.startup_options.log_level, self.startup_options.log_type) logging.debug('>>') logging.debug("PyTrainer version %s" % (self.version)) self.data_path = data_path self.date = Date() self.ddbb = None # Checking profile logging.debug('Checking configuration and profile...') self.profile = Profile(self.environment, self.data_path,self) self.uc = UC() self.windowmain = None self.ddbb = DDBB(self.profile, self) logging.debug('connecting to DDBB') self.ddbb.connect() initialize_data(self.ddbb, self.environment.conf_dir) self._sport_service = SportService(self.ddbb) self.record = Record(self._sport_service, data_path, self) self.athlete = Athlete(data_path,self) self.stats = Stats(self._sport_service, self) pool_size = self.profile.getIntValue("pytraining","activitypool_size", default=1) self.activitypool = ActivityPool(self, size=pool_size) #preparamos la ventana principal self.windowmain = Main(self._sport_service, data_path,self,self.version, gpxDir=self.profile.gpxdir) self.date = Date(self.windowmain.calendar) self.waypoint = Waypoint(data_path,self) self.extension = Extension(data_path, self) self.plugins = Plugins(data_path, self) self.importdata = Importdata(self._sport_service, data_path, self, self.profile) self.loadPlugins() self.loadExtensions() self.windowmain.setup() self.windowmain.on_calendar_selected(None) self.refreshMainSportList() self.windowmain.run() logging.debug('<<')
def __init__(self, sport_service, data_path=None, parent=None): logging.debug(">>") self._sport_service = sport_service self.parent = parent self.pytrainer_main = parent self._equipment_service = EquipmentService(self.pytrainer_main.ddbb) self.data_path = data_path logging.debug("setting date...") self.date = Date() logging.debug("<<")
class Record: def __init__(self, sport_service, data_path=None, parent=None): logging.debug(">>") self._sport_service = sport_service self.parent = parent self.pytrainer_main = parent self._equipment_service = EquipmentService(self.pytrainer_main.ddbb) self.data_path = data_path logging.debug("setting date...") self.date = Date() logging.debug("<<") def newRecord( self, date, title=None, distance=None, time=None, upositive=None, unegative=None, bpm=None, calories=None, comment=None, ): logging.debug(">>") sports = self._sport_service.get_all_sports() self.recordwindow = WindowRecord( self._equipment_service, self.data_path, sports, self, self.format_date(date), title, distance, time, upositive, unegative, bpm, calories, comment, ) self.recordwindow.run() logging.debug("<<") def newMultiRecord(self, activities): logging.debug(">>") sports = self._sport_service.get_all_sports() self.recordwindow = WindowRecord( self._equipment_service, self.data_path, sports, parent=self, windowTitle=_("Modify details before importing"), ) self.recordwindow.populateMultiWindow(activities) self.recordwindow.run() return self.recordwindow.getActivityData() logging.debug("<<") def editRecord(self, id_record): logging.debug(">>") activity = self.pytrainer_main.activitypool.get_activity(id_record) record_equipment = self.get_record_equipment(id_record) sports = self._sport_service.get_all_sports() self.recordwindow = WindowRecord( self._equipment_service, self.data_path, sports, self, None, windowTitle=_("Edit Entry"), equipment=record_equipment, ) self.recordwindow.setValuesFromActivity(activity) logging.debug("launching window") self.recordwindow.run() logging.debug("<<") def removeRecord(self, id_record): logging.debug(">>") record = self.pytrainer_main.ddbb.delete("records", 'id_record="%s"' % id_record) laps = self.pytrainer_main.ddbb.delete("laps", 'record="%s"' % id_record) logging.debug("removed record " + str(id_record) + " (and associated laps) from DB") gpxfile = self.pytrainer_main.profile.gpxdir + "/%d.gpx" % int(id_record) if os.path.isfile(gpxfile): os.remove(gpxfile) logging.debug("removed gpxfile " + gpxfile) logging.debug("<<") def pace_to_float(self, value): """Take a mm:ss or mm.ss and return float""" try: value = float(value) except: if ":" in value: # 'mm:ss' found mins, sec = value.split(":") value = float(mins + "." + "%02d" % round(int(sec) * 5 / 3)) elif "," in value: value = float(value.replace(",", ".")) else: logging.error("Wrong value provided: %s" % value) value = None return value def pace_from_float(self, value, fromDB=False): """Helper to generate mm:ss from float representation mm.ss (or mm,ss?)""" # Check that value supplied is a float try: _value = "%0.2f" % float(value) except ValueError: _value = str(value) if fromDB: # paces in DB are stored in mixed format -> 4:30 as 4.3 (NOT as 4.5 aka 'decimal') pace = _value else: mins, sec_dec = _value.split(".") pace = mins + ":" + "%02d" % round(int(sec_dec) * 3 / 5) return pace def _formatRecordNew(self, list_options): """20.07.2008 - dgranda New records handle date_time_utc field which is transparent when updating, so logic method has been splitted args: list with keys and values without valid format returns: keys and values matching DB schema""" logging.debug(">>") time = self.date.time2second(list_options["rcd_time"]) average = self.parseFloatRecord(list_options["rcd_average"]) keys = "date,sport,distance,time,beats,comments,average,calories,title,upositive,unegative,maxspeed,maxpace,pace,maxbeats,date_time_utc,date_time_local, duration" if list_options["rcd_beats"] == "": list_options["rcd_beats"] = 0 # retrieving sport id (adding sport if it doesn't exist yet) sport_id = self.getSportId(list_options["rcd_sport"], add=True) values = ( list_options["rcd_date"], sport_id, self.parseFloatRecord(list_options["rcd_distance"]), time, self.parseFloatRecord(list_options["rcd_beats"]), list_options["rcd_comments"], average, self.parseFloatRecord(list_options["rcd_calories"]), list_options["rcd_title"], self.parseFloatRecord(list_options["rcd_upositive"]), self.parseFloatRecord(list_options["rcd_unegative"]), self.parseFloatRecord(list_options["rcd_maxvel"]), self.pace_to_float(list_options["rcd_maxpace"]), self.pace_to_float(list_options["rcd_pace"]), self.parseFloatRecord(list_options["rcd_maxbeats"]), list_options["date_time_utc"], list_options["date_time_local"], time, ) logging.debug("<<") return keys, values def insertRecord(self, list_options, laps=None, equipment=None): logging.debug(">>") # Create entry for activity in records table if list_options is None: logging.info("No data provided, abort adding entry") return None logging.debug("list_options: " + str(list_options)) cells, values = self._formatRecordNew(list_options) self.pytrainer_main.ddbb.insert("records", cells, values) logging.debug("DB updated: " + str(cells) + " | " + str(values)) id_record = self.pytrainer_main.ddbb.lastRecord("records") # Create entry(s) for activity in laps table if laps is not None: for lap in laps: lap["record"] = id_record # Add reference to entry in record table lap_keys = ", ".join(map(str, lap.keys())) lap_values = lap.values() self.insertLaps(lap_keys, lap.values()) if equipment is not None: for equipment_id in equipment: self._insert_record_equipment(id_record, equipment_id) gpxOrig = list_options["rcd_gpxfile"] if os.path.isfile(gpxOrig): gpxDest = self.pytrainer_main.profile.gpxdir gpxNew = gpxDest + "/%d.gpx" % id_record # Leave original file in place... # shutil.move(gpxOrig, gpxNew) # logging.debug('Moving '+gpxOrig+' to '+gpxNew) shutil.copy(gpxOrig, gpxNew) logging.debug("Copying " + gpxOrig + " to " + gpxNew) # self.parent.refreshListRecords() logging.debug("<<") return self.pytrainer_main.ddbb.lastRecord("records") def insertNewRecord(self, gpxOrig, entry): # TODO consolidate with insertRecord """29.03.2008 - dgranda Moves GPX file to store destination and updates database args: path to source GPX file""" logging.debug("--") (list_options, gpx_laps) = self.summaryFromGPX(gpxOrig, entry) if list_options is None: return None return self.insertRecord(list_options, laps=gpx_laps) def lapsFromGPX(self, gpx): logging.debug(">>") laps = [] gpxLaps = gpx.getLaps() for lap in gpxLaps: lap_number = gpxLaps.index(lap) tmp_lap = {} tmp_lap["record"] = "" tmp_lap["lap_number"] = lap_number tmp_lap["elapsed_time"] = lap[0] tmp_lap["distance"] = lap[4] tmp_lap["start_lat"] = lap[5] tmp_lap["start_lon"] = lap[6] tmp_lap["end_lat"] = lap[1] tmp_lap["end_lon"] = lap[2] tmp_lap["calories"] = lap[3] tmp_lap["intensity"] = lap[7] tmp_lap["avg_hr"] = lap[8] tmp_lap["max_hr"] = lap[9] tmp_lap["max_speed"] = lap[10] tmp_lap["laptrigger"] = lap[11] tmp_lap["comments"] = "" laps.append(tmp_lap) logging.debug("<<") return laps def summaryFromGPX(self, gpxOrig, entry): """29.03.2008 - dgranda Retrieves info which will be stored in DB from GPX file args: path to source GPX file returns: list with fields and values, list of laps """ logging.debug(">>") gpx = Gpx(self.data_path, gpxOrig) distance, time, maxspeed, maxheartrate = gpx.getMaxValues() # if time == 0: #invalid record # print "Invalid record" # return (None, None) upositive, unegative = gpx.getUnevenness() if time > 0: speed = distance * 3600 / time time_hhmmss = [time // 3600, (time / 60) % 60, time % 60] else: speed = 0 time_hhmmss = [0, 0, 0] summaryRecord = {} summaryRecord["rcd_gpxfile"] = gpxOrig summaryRecord["rcd_sport"] = entry[0] summaryRecord["rcd_date"] = gpx.getDate() summaryRecord["rcd_calories"] = gpx.getCalories() summaryRecord["rcd_comments"] = "" summaryRecord["rcd_title"] = "" summaryRecord["rcd_time"] = time_hhmmss # ToDo: makes no sense to work with arrays summaryRecord["rcd_distance"] = "%0.2f" % distance if speed == 0: summaryRecord["rcd_pace"] = "0" else: summaryRecord["rcd_pace"] = "%d.%02d" % ((3600 / speed) / 60, (3600 / speed) % 60) if maxspeed == 0: summaryRecord["rcd_maxpace"] = "0" else: summaryRecord["rcd_maxpace"] = "%d.%02d" % ((3600 / maxspeed) / 60, (3600 / maxspeed) % 60) summaryRecord["rcd_average"] = speed summaryRecord["rcd_maxvel"] = maxspeed summaryRecord["rcd_beats"] = gpx.getHeartRateAverage() summaryRecord["rcd_maxbeats"] = maxheartrate summaryRecord["rcd_upositive"] = upositive summaryRecord["rcd_unegative"] = unegative if entry[1] == "": # coming from new track dialog (file opening) #TODO This if-else needs checking summaryRecord["date_time_utc"], summaryRecord["date_time_local"] = gpx.getStartTimeFromGPX(gpxOrig) # else: # coming from GPS device # summaryRecord["date_time_utc"] = entry[1] # summaryRecord["date_time_local"] = entry[1] # print "#TODO fix record summaryRecord local and utc time..." # logging.debug("summary: " + str(summaryRecord)) laps = self.lapsFromGPX(gpx) logging.debug("<<") return summaryRecord, laps def updateRecord( self, list_options, id_record, equipment=None ): # ToDo: update only fields that can change if GPX file is present logging.debug(">>") # Remove activity from pool so data is updated self.pytrainer_main.activitypool.remove_activity(id_record) gpxfile = self.pytrainer_main.profile.gpxdir + "/%d.gpx" % int(id_record) gpxOrig = list_options["rcd_gpxfile"] if os.path.isfile(gpxOrig): if gpxfile != gpxOrig: shutil.copy2(gpxOrig, gpxfile) else: if list_options["rcd_gpxfile"] == "": logging.debug("Activity not based in GPX file") # ein? logging.debug("Updating bbdd") cells, values = self._formatRecordNew(list_options) self.pytrainer_main.ddbb.update("records", cells, values, " id_record=%d" % int(id_record)) if equipment is not None: self._update_record_equipment(id_record, equipment) self.pytrainer_main.refreshListView() logging.debug("<<") def parseFloatRecord(self, string): logging.debug("--") if string != "": try: return float(string.replace(",", ",")) except: return float(string) else: return 0 def getrecordInfo(self, id_record): logging.debug("--") if id_record is None or id_record == "": return [] return self.pytrainer_main.ddbb.select( "records,sports", "sports.name,date,distance,time,beats,comments,average,calories,id_record,title,upositive,unegative,maxspeed,maxpace,pace,maxbeats,date_time_utc,date_time_local", 'id_record="%s" and records.sport=sports.id_sports' % id_record, ) def format_date(self, date): return date.strftime("%Y-%m-%d") def getrecordList(self, date, id_sport=None): logging.debug("--") if not id_sport: # outer join on sport id to workaround bug where sport reference is null on records from GPX import return self.pytrainer_main.ddbb.select( "records left outer join sports on records.sport=sports.id_sports", "sports.name,date,distance,time,beats,comments,average,calories,id_record,maxspeed,maxbeats,date_time_utc,date_time_local,upositive,unegative", 'date="%s" ' % self.format_date(date), ) else: return self.pytrainer_main.ddbb.select( "records,sports", "sports.name,date,distance,time,beats,comments,average,calories,id_record,maxspeed,maxbeats,date_time_utc,date_time_local,upositive,unegative", 'date="%s" and sports.id_sports="%s" and records.sport=sports.id_sports' % (self.format_date(date), id_sport), ) def getLaps(self, id_record): logging.debug("--") laps = self.pytrainer_main.ddbb.select( "laps", "id_lap, record, elapsed_time, distance, start_lat, start_lon, end_lat, end_lon, calories, lap_number, intensity, max_speed, avg_hr, max_hr, laptrigger, comments", 'record="%s"' % id_record, ) if laps is None or laps == []: # No laps stored - update DB logging.debug("No laps in DB for record %d" % id_record) # print ("No laps in DB for record %d" % id_record) gpx_dest = self.pytrainer_main.profile.gpxdir gpxfile = gpx_dest + "/%d.gpx" % id_record gpx = Gpx(self.data_path, gpxfile) laps = self.lapsFromGPX(gpx) if laps is not None: for lap in laps: lap["record"] = id_record # Add reference to entry in record table lap_keys = ", ".join(map(str, lap.keys())) lap_values = lap.values() self.insertLaps(lap_keys, lap.values()) # Try to get lap info again #TODO? refactor laps = self.pytrainer_main.ddbb.select( "laps", "id_lap, record, elapsed_time, distance, start_lat, start_lon, end_lat, end_lon, calories, lap_number, intensity, max_speed, avg_hr, max_hr, laptrigger, comments", 'record="%s"' % id_record, ) return laps def insertLaps(self, cells, values): logging.debug("--") logging.debug("Adding lap information: " + ", ".join(map(str, values))) self.pytrainer_main.ddbb.insert("laps", cells, values) def _insert_record_equipment(self, record_id, equipment_id): self.pytrainer_main.ddbb.insert("record_equipment", "record_id, equipment_id", [record_id, equipment_id]) def _update_record_equipment(self, record_id, equipment_ids): self.pytrainer_main.ddbb.delete("record_equipment", "record_id={0}".format(record_id)) for id in equipment_ids: self._insert_record_equipment(record_id, id) def get_record_equipment(self, record_id): record_equipment = [] results = self.pytrainer_main.ddbb.select("record_equipment", "equipment_id", "record_id={0}".format(record_id)) for row in results: id = row[0] equipment_item = self._equipment_service.get_equipment_item(id) record_equipment.append(equipment_item) return record_equipment def getrecordPeriod(self, date_range, sport=None): # TODO This is essentially the same as getrecordPeriodSport (except date ranges) - need to look at merging the two date_ini = self.format_date(date_range.start_date) date_end = self.format_date(date_range.end_date) tables = "records,sports" if not sport: condition = 'date>="%s" and date<="%s" and records.sport=sports.id_sports' % (date_ini, date_end) else: condition = 'date>="%s" and date<="%s" and records.sport=sports.id_sports and sports.id_sports="%s"' % ( date_ini, date_end, sport, ) return self.pytrainer_main.ddbb.select( tables, "date,distance,time,beats,comments,average,calories,maxspeed,maxbeats, sports.name,upositive,unegative", condition, ) def getrecordPeriodSport(self, date_ini, date_end, sport): if not sport: tables = "records" condition = 'date>"%s" and date<"%s"' % (date_ini, date_end) else: tables = "records,sports" condition = 'date>"%s" and date<"%s" and records.sport=sports.id_sports and sports.id_sports="%s"' % ( date_ini, date_end, sport, ) return self.pytrainer_main.ddbb.select( tables, "date,distance,time,beats,comments,average,calories,maxspeed,maxbeats,upositive,unegative", condition, ) def _get_sport(self, sport_name): return self._sport_service.get_sport_by_name(sport_name) def getSportMet(self, sport_name): """Deprecated: use sport.met""" logging.debug("--") return self._get_sport(sport_name).met def getSportWeight(self, sport_name): """Deprecated: use sport.weight""" logging.debug("--") return self._get_sport(sport_name).weight def getSportId(self, sport_name, add=None): """Deprecated: use sport_service.get_sport_by_name() Get the id of a sport by name, optionally adding a new sport if none exists with the given name. arguments: sport_name: sport's name to get id for add: whether the sport should be added if not found returns: id for sport with given name or None""" if sport_name is None: return None sport = self._get_sport(sport_name) if sport is None: logging.debug("No sport with name: '%s'", str(sport_name)) if add is not None: logging.debug("Adding sport '%s'", str(sport_name)) new_sport = Sport() new_sport.name = unicode(sport_name) sport = self._sport_service.store_sport(new_sport) return None if sport is None else sport.id def getAllrecord(self): logging.debug("--") return self.pytrainer_main.ddbb.select("records", "date,distance,time,beats,comments,average,calories") def getAllRecordList(self): logging.debug("--") return self.pytrainer_main.ddbb.select( "records,sports", "date,distance,average,title,sports.name,id_record,time,beats,calories", "sports.id_sports = records.sport order by date desc", ) def getRecordListByCondition(self, condition): logging.debug("--") if condition is None: return self.getAllRecordList() else: logging.debug("condition: %s" % condition) return self.pytrainer_main.ddbb.select( "records,sports", "date,distance,average,title,sports.name,id_record,time,beats,calories", "sports.id_sports = records.sport and %s order by date desc" % condition, ) def getRecordDayList(self, date, id_sport=None): logging.debug(">>") logging.debug("Retrieving data for " + str(date)) # Why is looking for all days of the same month? if not id_sport: records = self.pytrainer_main.ddbb.select( "records", "date", "date LIKE '" + str(date.year) + "-" + date.strftime("%m") + "-%'" ) else: records = self.pytrainer_main.ddbb.select( "records", "date", 'date LIKE "%d-%0.2d-%%" and sport="%s"' % (date.year, date.month, id_sport) ) logging.debug("Found " + str(len(records)) + " entries") day_list = [] for i in records: record = str(i[0]).split("-") logging.debug("date:" + str(i[0])) day_list.append(record[2]) logging.debug("<<") return day_list def actualize_fromgpx(self, gpxfile): # TODO remove? - should never have multiple tracks per GPX file logging.debug(">>") logging.debug("loading file: " + gpxfile) gpx = Gpx(self.data_path, gpxfile) tracks = gpx.getTrackRoutes() if len(tracks) == 1: logging.debug("Just 1 track") self._actualize_fromgpx(gpx) elif len(tracks) > 1: logging.debug("Found " + str(len(tracks)) + " tracks") self._select_trkfromgpx(gpxfile, tracks) else: msg = _("pytrainer can't import data from your gpx file") from gui.warning import Warning warning = Warning(self.data_path) warning.set_text(msg) warning.run() logging.debug("<<") def _actualize_fromgpx(self, gpx): logging.debug(">>") distance, time, maxspeed, maxheartrate = gpx.getMaxValues() upositive, unegative = gpx.getUnevenness() heartrate = gpx.getHeartRateAverage() date = gpx.getDate() calories = gpx.getCalories() start_time = gpx.getStart_time() self.recordwindow.rcd_date.set_text(date) self.recordwindow.rcd_starttime.set_text(start_time) self.recordwindow.rcd_upositive.set_text(str(upositive)) self.recordwindow.rcd_unegative.set_text(str(unegative)) self.recordwindow.rcd_beats.set_text(str(heartrate)) self.recordwindow.rcd_calories.set_text(str(calories)) self.recordwindow.set_distance(distance) self.recordwindow.set_maxspeed(maxspeed) self.recordwindow.set_maxhr(maxheartrate) self.recordwindow.set_recordtime(time / 60.0 / 60.0) self.recordwindow.on_calcavs_clicked(None) self.recordwindow.on_calccalories_clicked(None) self.recordwindow.rcd_maxpace.set_text("%d.%02d" % ((3600 / maxspeed) / 60, (3600 / maxspeed) % 60)) logging.debug("<<") def __actualize_fromgpx(self, gpxfile, name=None): logging.debug(">>") gpx = Gpx(self.data_path, gpxfile, name) self._actualize_fromgpx(gpx) logging.debug("<<") def _select_trkfromgpx(self, gpxfile, tracks): # TODO remove? - should never have multiple tracks per GPX file logging.debug(">>") logging.debug("Track dialog " + self.data_path + "|" + gpxfile) selectrckdialog = DialogSelectTrack(self.data_path, tracks, self.__actualize_fromgpx, gpxfile) logging.debug("Launching window...") selectrckdialog.run() logging.debug("<<") def importFromGPX(self, gpxFile, sport): """ Add a record from a valid pytrainer type GPX file """ logging.debug(">>") entry_id = None if not os.path.isfile(gpxFile): logging.error("Invalid file: " + gpxFile) else: logging.info("Retrieving data from " + gpxFile) if not sport: sport = "import" entry = [sport, ""] entry_id = self.insertNewRecord(gpxFile, entry) if entry_id is None: logging.error("Entry not created for file %s" % gpxFile) else: logging.info("Entry %d has been added" % entry_id) logging.debug("<<") return entry_id
class pyTrainer: def __init__(self,filename = None, data_path = None): #Version constants self.version ="1.10.0-dev" #Process command line options self.startup_options = self.get_options() #Setup logging self.environment = Environment(platform.get_platform(), self.startup_options.conf_dir) self.environment.create_directories() self.set_logging(self.startup_options.log_level, self.startup_options.log_type) logging.debug('>>') logging.debug("PyTrainer version %s" % (self.version)) self.data_path = data_path self.date = Date() self.ddbb = None # Checking profile logging.debug('Checking configuration and profile...') self.profile = Profile(self.environment, self.data_path,self) self.uc = UC() self.windowmain = None self.ddbb = DDBB(self.profile, self) logging.debug('connecting to DDBB') self.ddbb.connect() initialize_data(self.ddbb, self.environment.conf_dir) self._sport_service = SportService(self.ddbb) self.record = Record(self._sport_service, data_path, self) self.athlete = Athlete(data_path,self) self.stats = Stats(self._sport_service, self) pool_size = self.profile.getIntValue("pytraining","activitypool_size", default=1) self.activitypool = ActivityPool(self, size=pool_size) #preparamos la ventana principal self.windowmain = Main(self._sport_service, data_path,self,self.version, gpxDir=self.profile.gpxdir) self.date = Date(self.windowmain.calendar) self.waypoint = Waypoint(data_path,self) self.extension = Extension(data_path, self) self.plugins = Plugins(data_path, self) self.importdata = Importdata(self._sport_service, data_path, self, self.profile) self.loadPlugins() self.loadExtensions() self.windowmain.setup() self.windowmain.on_calendar_selected(None) self.refreshMainSportList() self.windowmain.run() logging.debug('<<') def get_options(self): ''' Define usage and accepted options for command line startup returns: options - dict with option: value pairs ''' usage = '''usage: %prog [options] For more help on valid options try: %prog -h ''' parser = OptionParser(usage=usage) parser.set_defaults(log_level=logging.ERROR, validate=False, equip=False, newgraph=True, conf_dir=None, log_type="file") parser.add_option("-d", "--debug", action="store_const", const=logging.DEBUG, dest="log_level", help="enable logging at debug level") parser.add_option("-i", "--info", action="store_const", const=logging.INFO, dest="log_level", help="enable logging at info level") parser.add_option("-w", "--warn", action="store_const", const=logging.WARNING, dest="log_level", help="enable logging at warning level") parser.add_option("--valid", action="store_true", dest="validate", help="enable validation of files imported by plugins (details at info or debug logging level) - note plugin must support validation") parser.add_option("--oldgraph", action="store_false", dest="newgraph", help="Turn off new graphing approach") parser.add_option("--newgraph", action="store_true", dest="newgraph", help="Deprecated Option: Turn on new graphing approach") parser.add_option("--confdir", dest="conf_dir", help="Specify the directory where application configuration will be stored.") parser.add_option("--logtype", dest="log_type", metavar="TYPE", type="choice" , choices=["file", "console"], help="Specify where logging should be output to. TYPE is one of 'file' (default), or 'console'.") (options, args) = parser.parse_args() return options def set_logging(self, level, log_type): '''Setup rotating log file with customized format''' if("console" == log_type): handler = logging.StreamHandler(sys.stdout) else: handler = logging.handlers.RotatingFileHandler(self.environment.log_file, maxBytes=100000, backupCount=5) formatter = logging.Formatter('%(asctime)s|%(levelname)s|%(module)s|%(funcName)s|%(message)s') handler.setFormatter(formatter) logging.getLogger('').addHandler(handler) self.set_logging_level(self.startup_options.log_level) def set_logging_level(self, level): '''Set level of information written to log''' logging.debug("Setting logger to level: "+ str(level)) logging.getLogger('').setLevel(level) def quit(self): logging.debug('--') logging.info("Exit!") #self.webservice.stop() self.windowmain.gtk_main_quit() logging.shutdown() sys.exit() # Any nonzero value is considered "abnormal termination" by shells and the like def loadPlugins(self): logging.debug('>>') activeplugins = self.plugins.getActivePlugins() if (len(activeplugins)<1): logging.info("No active plugins") else: for plugin in activeplugins: txtbutton = self.plugins.loadPlugin(plugin) self.windowmain.addImportPlugin(txtbutton) logging.debug('<<') def loadExtensions(self): logging.debug('>>') activeextensions = self.extension.getActiveExtensions() if (len(activeextensions)<1): logging.info("No active extensions") else: for extension in activeextensions: txtbutton = self.extension.loadExtension(extension) self.windowmain.addExtension(txtbutton) logging.debug('<<') def runPlugin(self,widget,pathPlugin): logging.debug('>>') self.pluginClass = self.plugins.importClass(pathPlugin) pluginFiles = self.pluginClass.run() if pluginFiles is not None: logging.debug("Plugin returned %d files" % (len(pluginFiles)) ) #process returned GPX files for (pluginFile, sport) in pluginFiles: if os.path.isfile(pluginFile): logging.info('File exists. Size: %d. Sport: %s' % (os.path.getsize(pluginFile), sport)) if self.record.importFromGPX(pluginFile, sport) is None: logging.error("Error importing file "+pluginFile) else: logging.error('File '+pluginFile+' not valid') else: logging.debug("No files returned from Plugin") self.refreshListRecords() self.refreshGraphView("day") logging.debug('<<') def runExtension(self,extension,id): logging.debug('>>') #print("Extension id: %s" % str(id)) activity = self.activitypool.get_activity(id) txtbutton,pathExtension,type = extension self.extensionClass = self.extension.importClass(pathExtension) self.extensionClass.run(id, activity) #if type == "record": # #Si es record le tenemos que crear el googlemaps, el gpx y darle el id de la bbdd # alert = self.extension.runExtension(pathExtension,id) logging.debug('<<') def refreshMainSportList(self): logging.debug('>>') sports = self._sport_service.get_all_sports() self.windowmain.updateSportList(sports) logging.debug('<<') def refreshGraphView(self, view, sport=None): logging.debug('>>') if self.windowmain is None: logging.debug("First call to refreshGraphView") logging.debug('<<') return date_selected = self.date.getDate() if view=="record": logging.debug('record view') if self.windowmain.recordview.get_current_page()==0: self.refreshRecordGraphView("info") elif self.windowmain.recordview.get_current_page()==1: self.refreshRecordGraphView("graphs") elif self.windowmain.recordview.get_current_page()==2: self.refreshRecordGraphView("map") elif self.windowmain.recordview.get_current_page()==3: self.refreshRecordGraphView("heartrate") elif self.windowmain.recordview.get_current_page()==4: self.refreshRecordGraphView("analytics") elif view=="day": logging.debug('day view') sport = self.windowmain.activeSport sport_id = self.record.getSportId(sport) record_list = self.record.getrecordList(date_selected, sport_id) self.windowmain.actualize_dayview(record_list=record_list) #selected,iter = self.windowmain.recordTreeView.get_selection().get_selected() elif view=="week": logging.debug('week view') date_range = DateRange.for_week_containing(date_selected) sport = self.windowmain.activeSport sport_id = self.record.getSportId(sport) record_list = self.record.getrecordPeriod(date_range, sport_id) self.windowmain.actualize_weekview(record_list, date_range) elif view=="month": logging.debug('month view') date_range = DateRange.for_month_containing(date_selected) sport = self.windowmain.activeSport sport_id = self.record.getSportId(sport) record_list = self.record.getrecordPeriod(date_range, sport_id) nameMonth, daysInMonth = self.date.getNameMonth(date_selected) self.windowmain.actualize_monthview(record_list, nameMonth) self.windowmain.actualize_monthgraph(record_list, daysInMonth) elif view=="year": logging.debug('year view') date_range = DateRange.for_year_containing(date_selected) sport = self.windowmain.activeSport sport_id = self.record.getSportId(sport) record_list = self.record.getrecordPeriod(date_range, sport_id) self.windowmain.actualize_yearview(record_list, date_selected.year) self.windowmain.actualize_yeargraph(record_list) elif view=="listview": logging.debug('list view') self.refreshListView() elif view=="athlete": logging.debug('athlete view') self.windowmain.on_athleteview_activate() elif view=="stats": logging.debug('stats view') self.windowmain.on_statsview_activate() else: print "Unknown view %s" % view logging.debug('<<') def refreshRecordGraphView(self, view, id_record=None): logging.debug('>>') logging.info('Working on '+view+' graph') if id_record is not None: #Refresh called for a specific record #Select correct record in treeview model = self.windowmain.recordTreeView.get_model() #Loop through all records in treeview looking for the correct one to highlight for i,row in enumerate(model): if row[0] == id_record: self.windowmain.recordTreeView.set_cursor(i) else: selected,iter = self.windowmain.recordTreeView.get_selection().get_selected() if iter: id_record = selected.get_value(iter,0) else: id_record = None view="info" activity = self.activitypool.get_activity(id_record) if view=="info": self.windowmain.actualize_recordview(activity) if view=="graphs": self.windowmain.actualize_recordgraph(activity) if view=="map": self.refreshMapView() if view=="heartrate": self.windowmain.actualize_heartrategraph(activity) self.windowmain.actualize_hrview(activity) if view=="analytics": self.windowmain.actualize_analytics(activity) logging.debug('<<') def refreshMapView(self, full_screen=False): logging.debug('>>') if self.windowmain is None: logging.debug('Called before windowmain initialisation') logging.debug('<<') return selected,iter = self.windowmain.recordTreeView.get_selection().get_selected() id_record = selected.get_value(iter,0) activity = self.activitypool.get_activity(id_record) logging.debug('Trying to show map for record '+str(id_record)) self.windowmain.actualize_map(activity, full_screen) logging.debug('<<') def refreshListRecords(self): logging.debug('>>') #Refresh list view #self.refreshListView() # old variant self.refreshListView(self.windowmain.listsearch.condition) #Refresh list records date = self.date.getDate() sport = self.windowmain.activeSport id_sport = self.record.getSportId(sport) record_ids = self.record.getrecordList(date, id_sport) self.windowmain.actualize_recordTreeView(record_ids) #Mark the monthly calendar to show which days have activity? record_list = self.record.getRecordDayList(date, id_sport) self.windowmain.actualize_calendar(record_list) logging.debug('<<') def refreshAthleteView(self): logging.debug('>>') self.athlete.refresh() self.windowmain.actualize_athleteview(self.athlete) logging.debug('<<') def refreshStatsView(self): logging.debug('>>') self.stats.refresh() self.windowmain.actualize_statsview(self.stats, self.record.getAllRecordList()) logging.debug('<<') def refreshListView(self,condition=None): logging.debug('>>') record_list = self.record.getRecordListByCondition(condition) self.windowmain.actualize_listview(record_list) logging.debug('<<') def refreshWaypointView(self,default_waypoint=None,redrawmap=1): logging.debug('>>') waypoint_list = self.waypoint.getAllWaypoints() self.windowmain.actualize_waypointview(waypoint_list,default_waypoint,redrawmap) logging.debug('<<') def editExtensions(self): logging.debug('>>') before = self.extension.getActiveExtensions() self.extension.manageExtensions() after = self.extension.getActiveExtensions() self.setExtensions(before, after) logging.debug('<<') def importData(self): logging.debug('>>') activeplugins_before = self.plugins.getActivePlugins() self.importdata.runImportdata() activeplugins_after = self.plugins.getActivePlugins() #Need to check for plugins that have been disabled (were active and now are not) self.setMenuPlugins(activeplugins_before, activeplugins_after) self.refreshListRecords() self.refreshGraphView(self.windowmain.selected_view) logging.debug('<<') def editGpsPlugins(self): logging.debug('>>') activeplugins_before = self.plugins.getActivePlugins() self.plugins.managePlugins() activeplugins_after = self.plugins.getActivePlugins() #Need to check for plugins that have been disabled (were active and now are not) self.setMenuPlugins(activeplugins_before, activeplugins_after) logging.debug('<<') def setMenuPlugins(self, activeplugins_before, activeplugins_after): logging.debug('>>') #Need to check for plugins that have been disabled (were active and now are not) for plugin in activeplugins_before: if plugin not in activeplugins_after: #disabled plugin -> need to unload plugin txtbutton = self.plugins.loadPlugin(plugin) self.windowmain.removeImportPlugin(txtbutton) #Need to check for plugins that have been enabled (were not active and now are) for plugin in activeplugins_after: if plugin not in activeplugins_before: #new active plugin -> need to load plugin txtbutton = self.plugins.loadPlugin(plugin) self.windowmain.addImportPlugin(txtbutton) logging.debug('<<') def setExtensions(self, before, after): logging.debug('>>') #Need to check for extensions that have been disabled (were active and now are not) for extension in before: if extension not in after: #disabled extension -> need to unload extension print "Need to disable extension %s " % extension txtbutton = self.extension.loadExtension(extension) self.windowmain.removeExtension(txtbutton) #Need to check for plugins that have been enabled (were not active and now are) for extension in after: if extension not in before: #new active extension -> need to load extension logging.debug("Enabling extension %s " % extension) txtbutton = self.extension.loadExtension(extension) self.windowmain.addExtension(txtbutton) logging.debug('<<') def newRecord(self,title=None,distance=None,time=None,upositive=None,unegative=None,bpm=None,calories=None,comment=None,view=None): logging.debug('>>') date = self.date.getDate() self.record.newRecord(date, title, distance, time, upositive, unegative, bpm, calories, comment) self.refreshListRecords() if view is not None: self.refreshGraphView(view) logging.debug('<<') def editRecord(self, id_record, view=None): logging.debug("Editing record with id: '%s'", id_record) self.record.editRecord(id_record) self.refreshListRecords() if view is not None: self.refreshGraphView(view) logging.debug('<<') def removeRecord(self, id_record, confirm = False, view=None): logging.debug('>>') if confirm: self.record.removeRecord(id_record) else: msg = _("Delete this database entry?") params = [id_record,True] warning = Warning(self.data_path,self.removeRecord,params) warning.set_text(msg) warning.run() self.refreshListRecords() if view is not None: self.refreshGraphView(view) logging.debug('<<') def removeWaypoint(self,id_waypoint, confirm = False): logging.debug('>>') if confirm: self.waypoint.removeWaypoint(id_waypoint) self.refreshWaypointView() else: msg = _("Delete this waypoint?") params = [id_waypoint,True] warning = Warning(self.data_path,self.removeWaypoint,params) warning.set_text(msg) warning.run() logging.debug('<<') def updateWaypoint(self,id_waypoint,lat,lon,name,desc,sym): logging.debug('>>') self.waypoint.updateWaypoint(id_waypoint,lat,lon,name,desc,sym) self.refreshWaypointView(id_waypoint) logging.debug('<<') def exportCsv(self): logging.debug('>>') from save import Save save = Save(self.data_path, self.record) save.run() logging.debug('<<') def editProfile(self): logging.debug('>>') self.profile.editProfile(self._sport_service) self.activitypool.clear_pool() self.windowmain.setup() logging.debug('<<')
class Record: def __init__(self, sport_service, data_path=None, parent=None): logging.debug('>>') self._sport_service = sport_service self.parent = parent self.pytrainer_main = parent self._equipment_service = EquipmentService(self.pytrainer_main.ddbb) self.data_path = data_path logging.debug('setting date...') self.date = Date() logging.debug('<<') def newRecord(self, date, title=None, distance=None, time=None, upositive=None, unegative=None, bpm=None, calories=None, comment=None): logging.debug('>>') sports = self._sport_service.get_all_sports() self.recordwindow = WindowRecord(self._equipment_service, self.data_path, sports, self, self.format_date(date), title, distance, time, upositive, unegative, bpm, calories, comment) self.recordwindow.run() logging.debug('<<') def newMultiRecord(self, activities): logging.debug('>>') sports = self._sport_service.get_all_sports() self.recordwindow = WindowRecord( self._equipment_service, self.data_path, sports, parent=self, windowTitle=_("Modify details before importing")) self.recordwindow.populateMultiWindow(activities) self.recordwindow.run() return self.recordwindow.getActivityData() logging.debug('<<') def editRecord(self, id_record): logging.debug('>>') activity = self.pytrainer_main.activitypool.get_activity(id_record) record_equipment = self.get_record_equipment(id_record) sports = self._sport_service.get_all_sports() self.recordwindow = WindowRecord(self._equipment_service, self.data_path, sports, self, None, windowTitle=_("Edit Entry"), equipment=record_equipment) self.recordwindow.setValuesFromActivity(activity) logging.debug('launching window') self.recordwindow.run() logging.debug('<<') def removeRecord(self, id_record): logging.debug('>>') record = self.pytrainer_main.ddbb.delete( "records", "id_record=\"%s\"" % id_record) laps = self.pytrainer_main.ddbb.delete("laps", "record=\"%s\"" % id_record) logging.debug('removed record ' + str(id_record) + ' (and associated laps) from DB') gpxfile = self.pytrainer_main.profile.gpxdir + "/%d.gpx" % int( id_record) if os.path.isfile(gpxfile): os.remove(gpxfile) logging.debug('removed gpxfile ' + gpxfile) logging.debug('<<') def pace_to_float(self, value): '''Take a mm:ss or mm.ss and return float''' try: value = float(value) except: if ":" in value: # 'mm:ss' found mins, sec = value.split(":") value = float(mins + "." + "%02d" % round(int(sec) * 5 / 3)) elif "," in value: value = float(value.replace(',', '.')) else: logging.error("Wrong value provided: %s" % value) value = None return value def pace_from_float(self, value, fromDB=False): '''Helper to generate mm:ss from float representation mm.ss (or mm,ss?)''' #Check that value supplied is a float try: _value = "%0.2f" % float(value) except ValueError: _value = str(value) if fromDB: # paces in DB are stored in mixed format -> 4:30 as 4.3 (NOT as 4.5 aka 'decimal') pace = _value else: mins, sec_dec = _value.split(".") pace = mins + ":" + "%02d" % round(int(sec_dec) * 3 / 5) return pace def _formatRecordNew(self, list_options): """20.07.2008 - dgranda New records handle date_time_utc field which is transparent when updating, so logic method has been splitted args: list with keys and values without valid format returns: keys and values matching DB schema""" logging.debug('>>') time = self.date.time2second(list_options["rcd_time"]) average = self.parseFloatRecord(list_options["rcd_average"]) keys = "date,sport,distance,time,beats,comments,average,calories,title,upositive,unegative,maxspeed,maxpace,pace,maxbeats,date_time_utc,date_time_local, duration" if (list_options["rcd_beats"] == ""): list_options["rcd_beats"] = 0 #retrieving sport id (adding sport if it doesn't exist yet) sport_id = self.getSportId(list_options["rcd_sport"], add=True) values = ( list_options["rcd_date"], sport_id, self.parseFloatRecord(list_options["rcd_distance"]), time, self.parseFloatRecord(list_options["rcd_beats"]), list_options["rcd_comments"], average, self.parseFloatRecord(list_options["rcd_calories"]), list_options["rcd_title"], self.parseFloatRecord(list_options["rcd_upositive"]), self.parseFloatRecord(list_options["rcd_unegative"]), self.parseFloatRecord(list_options["rcd_maxvel"]), self.pace_to_float(list_options["rcd_maxpace"]), self.pace_to_float(list_options["rcd_pace"]), self.parseFloatRecord(list_options["rcd_maxbeats"]), list_options["date_time_utc"], list_options["date_time_local"], time, ) logging.debug('<<') return keys, values def insertRecord(self, list_options, laps=None, equipment=None): logging.debug('>>') #Create entry for activity in records table if list_options is None: logging.info('No data provided, abort adding entry') return None logging.debug('list_options: ' + str(list_options)) cells, values = self._formatRecordNew(list_options) self.pytrainer_main.ddbb.insert("records", cells, values) logging.debug('DB updated: ' + str(cells) + ' | ' + str(values)) id_record = self.pytrainer_main.ddbb.lastRecord("records") #Create entry(s) for activity in laps table if laps is not None: for lap in laps: lap['record'] = id_record #Add reference to entry in record table lap_keys = ", ".join(map(str, lap.keys())) lap_values = lap.values() self.insertLaps(lap_keys, lap.values()) if equipment is not None: for equipment_id in equipment: self._insert_record_equipment(id_record, equipment_id) gpxOrig = list_options["rcd_gpxfile"] if os.path.isfile(gpxOrig): gpxDest = self.pytrainer_main.profile.gpxdir gpxNew = gpxDest + "/%d.gpx" % id_record #Leave original file in place... #shutil.move(gpxOrig, gpxNew) #logging.debug('Moving '+gpxOrig+' to '+gpxNew) shutil.copy(gpxOrig, gpxNew) logging.debug('Copying ' + gpxOrig + ' to ' + gpxNew) #self.parent.refreshListRecords() logging.debug('<<') return self.pytrainer_main.ddbb.lastRecord("records") def insertNewRecord(self, gpxOrig, entry): #TODO consolidate with insertRecord """29.03.2008 - dgranda Moves GPX file to store destination and updates database args: path to source GPX file""" logging.debug('--') (list_options, gpx_laps) = self.summaryFromGPX(gpxOrig, entry) if list_options is None: return None return self.insertRecord(list_options, laps=gpx_laps) def lapsFromGPX(self, gpx): logging.debug('>>') laps = [] gpxLaps = gpx.getLaps() for lap in gpxLaps: lap_number = gpxLaps.index(lap) tmp_lap = {} tmp_lap['record'] = "" tmp_lap['lap_number'] = lap_number tmp_lap['elapsed_time'] = lap[0] tmp_lap['distance'] = lap[4] tmp_lap['start_lat'] = lap[5] tmp_lap['start_lon'] = lap[6] tmp_lap['end_lat'] = lap[1] tmp_lap['end_lon'] = lap[2] tmp_lap['calories'] = lap[3] tmp_lap['intensity'] = lap[7] tmp_lap['avg_hr'] = lap[8] tmp_lap['max_hr'] = lap[9] tmp_lap['max_speed'] = lap[10] tmp_lap['laptrigger'] = lap[11] tmp_lap['comments'] = "" laps.append(tmp_lap) logging.debug('<<') return laps def summaryFromGPX(self, gpxOrig, entry): """29.03.2008 - dgranda Retrieves info which will be stored in DB from GPX file args: path to source GPX file returns: list with fields and values, list of laps """ logging.debug('>>') gpx = Gpx(self.data_path, gpxOrig) distance, time, maxspeed, maxheartrate = gpx.getMaxValues() #if time == 0: #invalid record # print "Invalid record" # return (None, None) upositive, unegative = gpx.getUnevenness() if time > 0: speed = distance * 3600 / time time_hhmmss = [time // 3600, (time / 60) % 60, time % 60] else: speed = 0 time_hhmmss = [0, 0, 0] summaryRecord = {} summaryRecord['rcd_gpxfile'] = gpxOrig summaryRecord['rcd_sport'] = entry[0] summaryRecord['rcd_date'] = gpx.getDate() summaryRecord['rcd_calories'] = gpx.getCalories() summaryRecord['rcd_comments'] = '' summaryRecord['rcd_title'] = '' summaryRecord[ 'rcd_time'] = time_hhmmss #ToDo: makes no sense to work with arrays summaryRecord['rcd_distance'] = "%0.2f" % distance if speed == 0: summaryRecord['rcd_pace'] = "0" else: summaryRecord['rcd_pace'] = "%d.%02d" % ((3600 / speed) / 60, (3600 / speed) % 60) if maxspeed == 0: summaryRecord['rcd_maxpace'] = "0" else: summaryRecord['rcd_maxpace'] = "%d.%02d" % ((3600 / maxspeed) / 60, (3600 / maxspeed) % 60) summaryRecord['rcd_average'] = speed summaryRecord['rcd_maxvel'] = maxspeed summaryRecord['rcd_beats'] = gpx.getHeartRateAverage() summaryRecord['rcd_maxbeats'] = maxheartrate summaryRecord['rcd_upositive'] = upositive summaryRecord['rcd_unegative'] = unegative if entry[ 1] == "": # coming from new track dialog (file opening) #TODO This if-else needs checking summaryRecord['date_time_utc'], summaryRecord[ 'date_time_local'] = gpx.getStartTimeFromGPX(gpxOrig) # else: # coming from GPS device # summaryRecord['date_time_utc'] = entry[1] # summaryRecord['date_time_local'] = entry[1] # print "#TODO fix record summaryRecord local and utc time..." # logging.debug('summary: ' + str(summaryRecord)) laps = self.lapsFromGPX(gpx) logging.debug('<<') return summaryRecord, laps def updateRecord( self, list_options, id_record, equipment=None ): # ToDo: update only fields that can change if GPX file is present logging.debug('>>') #Remove activity from pool so data is updated self.pytrainer_main.activitypool.remove_activity(id_record) gpxfile = self.pytrainer_main.profile.gpxdir + "/%d.gpx" % int( id_record) gpxOrig = list_options["rcd_gpxfile"] if os.path.isfile(gpxOrig): if gpxfile != gpxOrig: shutil.copy2(gpxOrig, gpxfile) else: if (list_options["rcd_gpxfile"] == ""): logging.debug('Activity not based in GPX file') # ein? logging.debug('Updating bbdd') cells, values = self._formatRecordNew(list_options) self.pytrainer_main.ddbb.update("records", cells, values, " id_record=%d" % int(id_record)) if equipment is not None: self._update_record_equipment(id_record, equipment) self.pytrainer_main.refreshListView() logging.debug('<<') def parseFloatRecord(self, string): logging.debug('--') if string != "": try: return float(string.replace(",", ",")) except: return float(string) else: return 0 def getrecordInfo(self, id_record): logging.debug('--') if id_record is None or id_record == "": return [] return self.pytrainer_main.ddbb.select( "records,sports", "sports.name,date,distance,time,beats,comments,average,calories,id_record,title,upositive,unegative,maxspeed,maxpace,pace,maxbeats,date_time_utc,date_time_local", "id_record=\"%s\" and records.sport=sports.id_sports" % id_record) def format_date(self, date): return date.strftime("%Y-%m-%d") def getrecordList(self, date, id_sport=None): logging.debug('--') if not id_sport: # outer join on sport id to workaround bug where sport reference is null on records from GPX import return self.pytrainer_main.ddbb.select( "records left outer join sports on records.sport=sports.id_sports", "sports.name,date,distance,time,beats,comments,average,calories,id_record,maxspeed,maxbeats,date_time_utc,date_time_local,upositive,unegative", "date=\"%s\" " % self.format_date(date)) else: return self.pytrainer_main.ddbb.select( "records,sports", "sports.name,date,distance,time,beats,comments,average,calories,id_record,maxspeed,maxbeats,date_time_utc,date_time_local,upositive,unegative", "date=\"%s\" and sports.id_sports=\"%s\" and records.sport=sports.id_sports" % (self.format_date(date), id_sport)) def getLaps(self, id_record): logging.debug('--') laps = self.pytrainer_main.ddbb.select( "laps", "id_lap, record, elapsed_time, distance, start_lat, start_lon, end_lat, end_lon, calories, lap_number, intensity, max_speed, avg_hr, max_hr, laptrigger, comments", "record=\"%s\"" % id_record) if laps is None or laps == []: #No laps stored - update DB logging.debug("No laps in DB for record %d" % id_record) #print ("No laps in DB for record %d" % id_record) gpx_dest = self.pytrainer_main.profile.gpxdir gpxfile = gpx_dest + "/%d.gpx" % id_record gpx = Gpx(self.data_path, gpxfile) laps = self.lapsFromGPX(gpx) if laps is not None: for lap in laps: lap['record'] = id_record #Add reference to entry in record table lap_keys = ", ".join(map(str, lap.keys())) lap_values = lap.values() self.insertLaps(lap_keys, lap.values()) #Try to get lap info again #TODO? refactor laps = self.pytrainer_main.ddbb.select( "laps", "id_lap, record, elapsed_time, distance, start_lat, start_lon, end_lat, end_lon, calories, lap_number, intensity, max_speed, avg_hr, max_hr, laptrigger, comments", "record=\"%s\"" % id_record) return laps def insertLaps(self, cells, values): logging.debug('--') logging.debug("Adding lap information: " + ", ".join(map(str, values))) self.pytrainer_main.ddbb.insert("laps", cells, values) def _insert_record_equipment(self, record_id, equipment_id): self.pytrainer_main.ddbb.insert("record_equipment", "record_id, equipment_id", [record_id, equipment_id]) def _update_record_equipment(self, record_id, equipment_ids): self.pytrainer_main.ddbb.delete("record_equipment", "record_id={0}".format(record_id)) for id in equipment_ids: self._insert_record_equipment(record_id, id) def get_record_equipment(self, record_id): record_equipment = [] results = self.pytrainer_main.ddbb.select( "record_equipment", "equipment_id", "record_id={0}".format(record_id)) for row in results: id = row[0] equipment_item = self._equipment_service.get_equipment_item(id) record_equipment.append(equipment_item) return record_equipment def getrecordPeriod(self, date_range, sport=None): #TODO This is essentially the same as getrecordPeriodSport (except date ranges) - need to look at merging the two date_ini = self.format_date(date_range.start_date) date_end = self.format_date(date_range.end_date) tables = "records,sports" if not sport: condition = "date>=\"%s\" and date<=\"%s\" and records.sport=sports.id_sports" % ( date_ini, date_end) else: condition = "date>=\"%s\" and date<=\"%s\" and records.sport=sports.id_sports and sports.id_sports=\"%s\"" % ( date_ini, date_end, sport) return self.pytrainer_main.ddbb.select( tables, "date,distance,time,beats,comments,average,calories,maxspeed,maxbeats, sports.name,upositive,unegative", condition) def getrecordPeriodSport(self, date_ini, date_end, sport): if not sport: tables = "records" condition = "date>\"%s\" and date<\"%s\"" % (date_ini, date_end) else: tables = "records,sports" condition = "date>\"%s\" and date<\"%s\" and records.sport=sports.id_sports and sports.id_sports=\"%s\"" % ( date_ini, date_end, sport) return self.pytrainer_main.ddbb.select( tables, "date,distance,time,beats,comments,average,calories,maxspeed,maxbeats,upositive,unegative", condition) def _get_sport(self, sport_name): return self._sport_service.get_sport_by_name(sport_name) def getSportMet(self, sport_name): """Deprecated: use sport.met""" logging.debug('--') return self._get_sport(sport_name).met def getSportWeight(self, sport_name): """Deprecated: use sport.weight""" logging.debug('--') return self._get_sport(sport_name).weight def getSportId(self, sport_name, add=None): """Deprecated: use sport_service.get_sport_by_name() Get the id of a sport by name, optionally adding a new sport if none exists with the given name. arguments: sport_name: sport's name to get id for add: whether the sport should be added if not found returns: id for sport with given name or None""" if sport_name is None: return None sport = self._get_sport(sport_name) if sport is None: logging.debug("No sport with name: '%s'", str(sport_name)) if add is not None: logging.debug("Adding sport '%s'", str(sport_name)) new_sport = Sport() new_sport.name = unicode(sport_name) sport = self._sport_service.store_sport(new_sport) return None if sport is None else sport.id def getAllrecord(self): logging.debug('--') return self.pytrainer_main.ddbb.select( "records", "date,distance,time,beats,comments,average,calories") def getAllRecordList(self): logging.debug('--') return self.pytrainer_main.ddbb.select( "records,sports", "date,distance,average,title,sports.name,id_record,time,beats,calories", "sports.id_sports = records.sport order by date desc") def getRecordListByCondition(self, condition): logging.debug('--') if condition is None: return self.getAllRecordList() else: logging.debug("condition: %s" % condition) return self.pytrainer_main.ddbb.select( "records,sports", "date,distance,average,title,sports.name,id_record,time,beats,calories", "sports.id_sports = records.sport and %s order by date desc" % condition) def getRecordDayList(self, date, id_sport=None): logging.debug('>>') logging.debug('Retrieving data for ' + str(date)) # Why is looking for all days of the same month? if not id_sport: records = self.pytrainer_main.ddbb.select( "records", "date", "date LIKE '" + str(date.year) + "-" + date.strftime("%m") + "-%'") else: records = self.pytrainer_main.ddbb.select( "records", "date", "date LIKE \"%d-%0.2d-%%\" and sport=\"%s\"" % (date.year, date.month, id_sport)) logging.debug('Found ' + str(len(records)) + ' entries') day_list = [] for i in records: record = str(i[0]).split("-") logging.debug('date:' + str(i[0])) day_list.append(record[2]) logging.debug('<<') return day_list def actualize_fromgpx( self, gpxfile ): #TODO remove? - should never have multiple tracks per GPX file logging.debug('>>') logging.debug('loading file: ' + gpxfile) gpx = Gpx(self.data_path, gpxfile) tracks = gpx.getTrackRoutes() if len(tracks) == 1: logging.debug('Just 1 track') self._actualize_fromgpx(gpx) elif len(tracks) > 1: logging.debug('Found ' + str(len(tracks)) + ' tracks') self._select_trkfromgpx(gpxfile, tracks) else: msg = _("pytrainer can't import data from your gpx file") from gui.warning import Warning warning = Warning(self.data_path) warning.set_text(msg) warning.run() logging.debug('<<') def _actualize_fromgpx(self, gpx): logging.debug('>>') distance, time, maxspeed, maxheartrate = gpx.getMaxValues() upositive, unegative = gpx.getUnevenness() heartrate = gpx.getHeartRateAverage() date = gpx.getDate() calories = gpx.getCalories() start_time = gpx.getStart_time() self.recordwindow.rcd_date.set_text(date) self.recordwindow.rcd_starttime.set_text(start_time) self.recordwindow.rcd_upositive.set_text(str(upositive)) self.recordwindow.rcd_unegative.set_text(str(unegative)) self.recordwindow.rcd_beats.set_text(str(heartrate)) self.recordwindow.rcd_calories.set_text(str(calories)) self.recordwindow.set_distance(distance) self.recordwindow.set_maxspeed(maxspeed) self.recordwindow.set_maxhr(maxheartrate) self.recordwindow.set_recordtime(time / 60.0 / 60.0) self.recordwindow.on_calcavs_clicked(None) self.recordwindow.on_calccalories_clicked(None) self.recordwindow.rcd_maxpace.set_text( "%d.%02d" % ((3600 / maxspeed) / 60, (3600 / maxspeed) % 60)) logging.debug('<<') def __actualize_fromgpx(self, gpxfile, name=None): logging.debug('>>') gpx = Gpx(self.data_path, gpxfile, name) self._actualize_fromgpx(gpx) logging.debug('<<') def _select_trkfromgpx( self, gpxfile, tracks ): #TODO remove? - should never have multiple tracks per GPX file logging.debug('>>') logging.debug('Track dialog ' + self.data_path + '|' + gpxfile) selectrckdialog = DialogSelectTrack(self.data_path, tracks, self.__actualize_fromgpx, gpxfile) logging.debug('Launching window...') selectrckdialog.run() logging.debug('<<') def importFromGPX(self, gpxFile, sport): """ Add a record from a valid pytrainer type GPX file """ logging.debug('>>') entry_id = None if not os.path.isfile(gpxFile): logging.error("Invalid file: " + gpxFile) else: logging.info('Retrieving data from ' + gpxFile) if not sport: sport = "import" entry = [sport, ""] entry_id = self.insertNewRecord(gpxFile, entry) if entry_id is None: logging.error("Entry not created for file %s" % gpxFile) else: logging.info("Entry %d has been added" % entry_id) logging.debug('<<') return entry_id
class pyTrainer: def __init__(self, filename=None, data_path=None): #Version constants self.version = "1.10.0-dev" #Process command line options self.startup_options = self.get_options() #Setup logging self.environment = Environment(platform.get_platform(), self.startup_options.conf_dir) self.environment.create_directories() self.set_logging(self.startup_options.log_level, self.startup_options.log_type) logging.debug('>>') logging.debug("PyTrainer version %s" % (self.version)) self.data_path = data_path self.date = Date() self.ddbb = None # Checking profile logging.debug('Checking configuration and profile...') self.profile = Profile(self.environment, self.data_path, self) self.uc = UC() self.windowmain = None self.ddbb = DDBB(self.profile, self) logging.debug('connecting to DDBB') self.ddbb.connect() initialize_data(self.ddbb, self.environment.conf_dir) self._sport_service = SportService(self.ddbb) self.record = Record(self._sport_service, data_path, self) self.athlete = Athlete(data_path, self) self.stats = Stats(self._sport_service, self) pool_size = self.profile.getIntValue("pytraining", "activitypool_size", default=1) self.activitypool = ActivityPool(self, size=pool_size) #preparamos la ventana principal self.windowmain = Main(self._sport_service, data_path, self, self.version, gpxDir=self.profile.gpxdir) self.date = Date(self.windowmain.calendar) self.waypoint = Waypoint(data_path, self) self.extension = Extension(data_path, self) self.plugins = Plugins(data_path, self) self.importdata = Importdata(self._sport_service, data_path, self, self.profile) self.loadPlugins() self.loadExtensions() self.windowmain.setup() self.windowmain.on_calendar_selected(None) self.refreshMainSportList() self.windowmain.run() logging.debug('<<') def get_options(self): ''' Define usage and accepted options for command line startup returns: options - dict with option: value pairs ''' usage = '''usage: %prog [options] For more help on valid options try: %prog -h ''' parser = OptionParser(usage=usage) parser.set_defaults(log_level=logging.ERROR, validate=False, equip=False, newgraph=True, conf_dir=None, log_type="file") parser.add_option("-d", "--debug", action="store_const", const=logging.DEBUG, dest="log_level", help="enable logging at debug level") parser.add_option("-i", "--info", action="store_const", const=logging.INFO, dest="log_level", help="enable logging at info level") parser.add_option("-w", "--warn", action="store_const", const=logging.WARNING, dest="log_level", help="enable logging at warning level") parser.add_option( "--valid", action="store_true", dest="validate", help= "enable validation of files imported by plugins (details at info or debug logging level) - note plugin must support validation" ) parser.add_option("--oldgraph", action="store_false", dest="newgraph", help="Turn off new graphing approach") parser.add_option( "--newgraph", action="store_true", dest="newgraph", help="Deprecated Option: Turn on new graphing approach") parser.add_option( "--confdir", dest="conf_dir", help= "Specify the directory where application configuration will be stored." ) parser.add_option( "--logtype", dest="log_type", metavar="TYPE", type="choice", choices=["file", "console"], help= "Specify where logging should be output to. TYPE is one of 'file' (default), or 'console'." ) (options, args) = parser.parse_args() return options def set_logging(self, level, log_type): '''Setup rotating log file with customized format''' if ("console" == log_type): handler = logging.StreamHandler(sys.stdout) else: handler = logging.handlers.RotatingFileHandler( self.environment.log_file, maxBytes=100000, backupCount=5) formatter = logging.Formatter( '%(asctime)s|%(levelname)s|%(module)s|%(funcName)s|%(message)s') handler.setFormatter(formatter) logging.getLogger('').addHandler(handler) self.set_logging_level(self.startup_options.log_level) def set_logging_level(self, level): '''Set level of information written to log''' logging.debug("Setting logger to level: " + str(level)) logging.getLogger('').setLevel(level) def quit(self): logging.debug('--') logging.info("Exit!") #self.webservice.stop() self.windowmain.gtk_main_quit() logging.shutdown() sys.exit( ) # Any nonzero value is considered "abnormal termination" by shells and the like def loadPlugins(self): logging.debug('>>') activeplugins = self.plugins.getActivePlugins() if (len(activeplugins) < 1): logging.info("No active plugins") else: for plugin in activeplugins: txtbutton = self.plugins.loadPlugin(plugin) self.windowmain.addImportPlugin(txtbutton) logging.debug('<<') def loadExtensions(self): logging.debug('>>') activeextensions = self.extension.getActiveExtensions() if (len(activeextensions) < 1): logging.info("No active extensions") else: for extension in activeextensions: txtbutton = self.extension.loadExtension(extension) self.windowmain.addExtension(txtbutton) logging.debug('<<') def runPlugin(self, widget, pathPlugin): logging.debug('>>') self.pluginClass = self.plugins.importClass(pathPlugin) pluginFiles = self.pluginClass.run() if pluginFiles is not None: logging.debug("Plugin returned %d files" % (len(pluginFiles))) #process returned GPX files for (pluginFile, sport) in pluginFiles: if os.path.isfile(pluginFile): logging.info('File exists. Size: %d. Sport: %s' % (os.path.getsize(pluginFile), sport)) if self.record.importFromGPX(pluginFile, sport) is None: logging.error("Error importing file " + pluginFile) else: logging.error('File ' + pluginFile + ' not valid') else: logging.debug("No files returned from Plugin") self.refreshListRecords() self.refreshGraphView("day") logging.debug('<<') def runExtension(self, extension, id): logging.debug('>>') #print("Extension id: %s" % str(id)) activity = self.activitypool.get_activity(id) txtbutton, pathExtension, type = extension self.extensionClass = self.extension.importClass(pathExtension) self.extensionClass.run(id, activity) #if type == "record": # #Si es record le tenemos que crear el googlemaps, el gpx y darle el id de la bbdd # alert = self.extension.runExtension(pathExtension,id) logging.debug('<<') def refreshMainSportList(self): logging.debug('>>') sports = self._sport_service.get_all_sports() self.windowmain.updateSportList(sports) logging.debug('<<') def refreshGraphView(self, view, sport=None): logging.debug('>>') if self.windowmain is None: logging.debug("First call to refreshGraphView") logging.debug('<<') return date_selected = self.date.getDate() if view == "record": logging.debug('record view') if self.windowmain.recordview.get_current_page() == 0: self.refreshRecordGraphView("info") elif self.windowmain.recordview.get_current_page() == 1: self.refreshRecordGraphView("graphs") elif self.windowmain.recordview.get_current_page() == 2: self.refreshRecordGraphView("map") elif self.windowmain.recordview.get_current_page() == 3: self.refreshRecordGraphView("heartrate") elif self.windowmain.recordview.get_current_page() == 4: self.refreshRecordGraphView("analytics") elif view == "day": logging.debug('day view') sport = self.windowmain.activeSport sport_id = self.record.getSportId(sport) record_list = self.record.getrecordList(date_selected, sport_id) self.windowmain.actualize_dayview(record_list=record_list) #selected,iter = self.windowmain.recordTreeView.get_selection().get_selected() elif view == "week": logging.debug('week view') date_range = DateRange.for_week_containing(date_selected) sport = self.windowmain.activeSport sport_id = self.record.getSportId(sport) record_list = self.record.getrecordPeriod(date_range, sport_id) self.windowmain.actualize_weekview(record_list, date_range) elif view == "month": logging.debug('month view') date_range = DateRange.for_month_containing(date_selected) sport = self.windowmain.activeSport sport_id = self.record.getSportId(sport) record_list = self.record.getrecordPeriod(date_range, sport_id) nameMonth, daysInMonth = self.date.getNameMonth(date_selected) self.windowmain.actualize_monthview(record_list, nameMonth) self.windowmain.actualize_monthgraph(record_list, daysInMonth) elif view == "year": logging.debug('year view') date_range = DateRange.for_year_containing(date_selected) sport = self.windowmain.activeSport sport_id = self.record.getSportId(sport) record_list = self.record.getrecordPeriod(date_range, sport_id) self.windowmain.actualize_yearview(record_list, date_selected.year) self.windowmain.actualize_yeargraph(record_list) elif view == "listview": logging.debug('list view') self.refreshListView() elif view == "athlete": logging.debug('athlete view') self.windowmain.on_athleteview_activate() elif view == "stats": logging.debug('stats view') self.windowmain.on_statsview_activate() else: print "Unknown view %s" % view logging.debug('<<') def refreshRecordGraphView(self, view, id_record=None): logging.debug('>>') logging.info('Working on ' + view + ' graph') if id_record is not None: #Refresh called for a specific record #Select correct record in treeview model = self.windowmain.recordTreeView.get_model() #Loop through all records in treeview looking for the correct one to highlight for i, row in enumerate(model): if row[0] == id_record: self.windowmain.recordTreeView.set_cursor(i) else: selected, iter = self.windowmain.recordTreeView.get_selection( ).get_selected() if iter: id_record = selected.get_value(iter, 0) else: id_record = None view = "info" activity = self.activitypool.get_activity(id_record) if view == "info": self.windowmain.actualize_recordview(activity) if view == "graphs": self.windowmain.actualize_recordgraph(activity) if view == "map": self.refreshMapView() if view == "heartrate": self.windowmain.actualize_heartrategraph(activity) self.windowmain.actualize_hrview(activity) if view == "analytics": self.windowmain.actualize_analytics(activity) logging.debug('<<') def refreshMapView(self, full_screen=False): logging.debug('>>') if self.windowmain is None: logging.debug('Called before windowmain initialisation') logging.debug('<<') return selected, iter = self.windowmain.recordTreeView.get_selection( ).get_selected() id_record = selected.get_value(iter, 0) activity = self.activitypool.get_activity(id_record) logging.debug('Trying to show map for record ' + str(id_record)) self.windowmain.actualize_map(activity, full_screen) logging.debug('<<') def refreshListRecords(self): logging.debug('>>') #Refresh list view #self.refreshListView() # old variant self.refreshListView(self.windowmain.listsearch.condition) #Refresh list records date = self.date.getDate() sport = self.windowmain.activeSport id_sport = self.record.getSportId(sport) record_ids = self.record.getrecordList(date, id_sport) self.windowmain.actualize_recordTreeView(record_ids) #Mark the monthly calendar to show which days have activity? record_list = self.record.getRecordDayList(date, id_sport) self.windowmain.actualize_calendar(record_list) logging.debug('<<') def refreshAthleteView(self): logging.debug('>>') self.athlete.refresh() self.windowmain.actualize_athleteview(self.athlete) logging.debug('<<') def refreshStatsView(self): logging.debug('>>') self.stats.refresh() self.windowmain.actualize_statsview(self.stats, self.record.getAllRecordList()) logging.debug('<<') def refreshListView(self, condition=None): logging.debug('>>') record_list = self.record.getRecordListByCondition(condition) self.windowmain.actualize_listview(record_list) logging.debug('<<') def refreshWaypointView(self, default_waypoint=None, redrawmap=1): logging.debug('>>') waypoint_list = self.waypoint.getAllWaypoints() self.windowmain.actualize_waypointview(waypoint_list, default_waypoint, redrawmap) logging.debug('<<') def editExtensions(self): logging.debug('>>') before = self.extension.getActiveExtensions() self.extension.manageExtensions() after = self.extension.getActiveExtensions() self.setExtensions(before, after) logging.debug('<<') def importData(self): logging.debug('>>') activeplugins_before = self.plugins.getActivePlugins() self.importdata.runImportdata() activeplugins_after = self.plugins.getActivePlugins() #Need to check for plugins that have been disabled (were active and now are not) self.setMenuPlugins(activeplugins_before, activeplugins_after) self.refreshListRecords() self.refreshGraphView(self.windowmain.selected_view) logging.debug('<<') def editGpsPlugins(self): logging.debug('>>') activeplugins_before = self.plugins.getActivePlugins() self.plugins.managePlugins() activeplugins_after = self.plugins.getActivePlugins() #Need to check for plugins that have been disabled (were active and now are not) self.setMenuPlugins(activeplugins_before, activeplugins_after) logging.debug('<<') def setMenuPlugins(self, activeplugins_before, activeplugins_after): logging.debug('>>') #Need to check for plugins that have been disabled (were active and now are not) for plugin in activeplugins_before: if plugin not in activeplugins_after: #disabled plugin -> need to unload plugin txtbutton = self.plugins.loadPlugin(plugin) self.windowmain.removeImportPlugin(txtbutton) #Need to check for plugins that have been enabled (were not active and now are) for plugin in activeplugins_after: if plugin not in activeplugins_before: #new active plugin -> need to load plugin txtbutton = self.plugins.loadPlugin(plugin) self.windowmain.addImportPlugin(txtbutton) logging.debug('<<') def setExtensions(self, before, after): logging.debug('>>') #Need to check for extensions that have been disabled (were active and now are not) for extension in before: if extension not in after: #disabled extension -> need to unload extension print "Need to disable extension %s " % extension txtbutton = self.extension.loadExtension(extension) self.windowmain.removeExtension(txtbutton) #Need to check for plugins that have been enabled (were not active and now are) for extension in after: if extension not in before: #new active extension -> need to load extension logging.debug("Enabling extension %s " % extension) txtbutton = self.extension.loadExtension(extension) self.windowmain.addExtension(txtbutton) logging.debug('<<') def newRecord(self, title=None, distance=None, time=None, upositive=None, unegative=None, bpm=None, calories=None, comment=None, view=None): logging.debug('>>') date = self.date.getDate() self.record.newRecord(date, title, distance, time, upositive, unegative, bpm, calories, comment) self.refreshListRecords() if view is not None: self.refreshGraphView(view) logging.debug('<<') def editRecord(self, id_record, view=None): logging.debug("Editing record with id: '%s'", id_record) self.record.editRecord(id_record) self.refreshListRecords() if view is not None: self.refreshGraphView(view) logging.debug('<<') def removeRecord(self, id_record, confirm=False, view=None): logging.debug('>>') if confirm: self.record.removeRecord(id_record) else: msg = _("Delete this database entry?") params = [id_record, True] warning = Warning(self.data_path, self.removeRecord, params) warning.set_text(msg) warning.run() self.refreshListRecords() if view is not None: self.refreshGraphView(view) logging.debug('<<') def removeWaypoint(self, id_waypoint, confirm=False): logging.debug('>>') if confirm: self.waypoint.removeWaypoint(id_waypoint) self.refreshWaypointView() else: msg = _("Delete this waypoint?") params = [id_waypoint, True] warning = Warning(self.data_path, self.removeWaypoint, params) warning.set_text(msg) warning.run() logging.debug('<<') def updateWaypoint(self, id_waypoint, lat, lon, name, desc, sym): logging.debug('>>') self.waypoint.updateWaypoint(id_waypoint, lat, lon, name, desc, sym) self.refreshWaypointView(id_waypoint) logging.debug('<<') def exportCsv(self): logging.debug('>>') from save import Save save = Save(self.data_path, self.record) save.run() logging.debug('<<') def editProfile(self): logging.debug('>>') self.profile.editProfile(self._sport_service) self.activitypool.clear_pool() self.windowmain.setup() logging.debug('<<')