from .integrations import SyncIntegration, NoTokenError from rowers.models import User, Rower, Workout, TombStone from rowingdata import rowingdata import numpy as np import datetime import json import urllib from rowers.utils import dologging, uniqify, custom_exception_handler from django.utils import timezone import requests from pytz.exceptions import UnknownTimeZoneError import pytz from rowsandall_app.settings import ( C2_CLIENT_ID, C2_REDIRECT_URI, C2_CLIENT_SECRET, UPLOAD_SERVICE_URL, UPLOAD_SERVICE_SECRET ) from rowers.tasks import ( handle_c2_import_stroke_data, handle_c2_sync, handle_c2_async_workout, handle_c2_getworkout ) import django_rq queue = django_rq.get_queue('default') queuelow = django_rq.get_queue('low') queuehigh = django_rq.get_queue('high') from rowers.utils import myqueue, dologging from requests import Request, Session import rowers.mytypes as mytypes from rowers.rower_rules import is_workout_user, ispromember def default(o): # pragma: no cover if isinstance(o, numpy.int64): return int(o) raise TypeError # convert datetime object to seconds def makeseconds(t): seconds = t.hour*3600.+t.minute*60.+t.second+0.1*int(t.microsecond/1.e5) return seconds # convert our weight class code to Concept2 weight class code def c2wc(weightclass): if (weightclass == "lwt"): # pragma: no cover res = "L" else: res = "H" return res class C2Integration(SyncIntegration): def __init__(self, *args, **kwargs): super(C2Integration, self).__init__(*args, **kwargs) self.oauth_data = { 'client_id': C2_CLIENT_ID, 'client_secret': C2_CLIENT_SECRET, 'redirect_uri': C2_REDIRECT_URI, 'autorization_uri': "https://log.concept2.com/oauth/authorize", 'content_type': 'application/x-www-form-urlencoded', 'tokenname': 'c2token', 'refreshtokenname': 'c2refreshtoken', 'expirydatename': 'tokenexpirydate', 'bearer_auth': True, 'base_url': "https://log.concept2.com/oauth/access_token", 'scope': 'write', } def get_name(self): return "Concept2 Logbook" def get_shortname(self): return "c2" def get_token(self, code, *args, **kwargs): messg = '' scope = "user:read,results:write" post_data = {"grant_type": "authorization_code", "code": code, "redirect_uri": C2_REDIRECT_URI, "client_secret": C2_CLIENT_SECRET, "client_id": C2_CLIENT_ID, } headers = {'user-agent': 'sanderroosendaal'} url = "https://log.concept2.com/oauth/access_token" s = Session() req = Request('POST', url, data=post_data, headers=headers) prepped = req.prepare() prepped.body += "&scope=" prepped.body += scope response = s.send(prepped) token_json = response.json() try: status_code = response.status_code except AttributeError: # pragma: no cover return (0, response.text) if status_code == 200: thetoken = token_json['access_token'] expires_in = token_json['expires_in'] refresh_token = token_json['refresh_token'] else: # pragma: no cover raise NoTokenError("No Token") return (thetoken, expires_in, refresh_token, messg) def open(self, *args, **kwargs): return super(C2Integration, self).open(*args, **kwargs) def token_refresh(self, *args, **kwargs): return super(C2Integration, self).token_refresh(*args, **kwargs) def make_authorization_url(self, *args, **kwargs): scope = "user:read,results:write" params = {"client_id": C2_CLIENT_ID, "response_type": "code", "redirect_uri": C2_REDIRECT_URI} url = "https://log.concept2.com/oauth/authorize?" + \ urllib.parse.urlencode(params) url += "&scope="+scope return url def get_userid(self, *args, **kwargs): _ = self.open() access_token = self.rower.c2token authorizationstring = str('Bearer ' + access_token) headers = {'Authorization': authorizationstring, 'user-agent': 'sanderroosendaal', 'Content-Type': 'application/json'} url = "https://log.concept2.com/api/users/me" try: response = requests.get(url, headers=headers) except: # pragma: no cover return 0 try: me_json = response.json() except: # pragma: no cover return 0 try: res = me_json['data']['id'] except KeyError: # pragma: no cover return 0 return res def createworkoutdata(self, w, *args, **kwargs) -> dict: filename = w.csvfilename try: row = rowingdata(csvfile=filename) except IOError: # pragma: no cover return 0 try: averagehr = int(row.df[' HRCur (bpm)'].mean()) maxhr = int(row.df[' HRCur (bpm)'].max()) except (ValueError, KeyError): # pragma: no cover averagehr = 0 maxhr = 0 # Calculate intervalstats itime, idist, itype = row.intervalstats_values() lapnames = range(len(itime)) nrintervals = len(itime) try: lapnames = row.df[' lapIdx'].unique() except KeyError: # pragma: no cover pass if len(lapnames) != nrintervals: newlapnames = [] for name in lapnames: newlapnames += [name, name] lapnames = newlapnames intervaldata = [] for i in range(nrintervals): if itime[i] > 0: mask = (row.df[' lapIdx'] == lapnames[i]) & ( row.df[' WorkoutState'] == itype[i]) try: spmav = int(row.df[' Cadence (stokes/min)'] [mask].mean().astype(int)) hrav = int(row.df[' HRCur (bpm)'][mask].mean().astype(int)) except AttributeError: # pragma: no cover try: spmav = int(row.df[' Cadence (stokes/min)'][mask].mean()) hrav = int(row.df[' HRCur (bpm)'][mask].mean()) except ValueError: spmav = 0 try: hrav = int(row.df[' HRCur (bpm)'][mask].mean()) except ValuError: hrav = 0 intervaldict = { 'type': 'distance', 'time': int(10*itime[i]), 'distance': int(idist[i]), 'heart_rate': { 'average': hrav, }, 'stroke_rate': spmav, } intervaldata.append(intervaldict) # adding diff, trying to see if this is valid t = 10*row.df.loc[:, 'TimeStamp (sec)'].values - \ 10*row.df.loc[:, 'TimeStamp (sec)'].iloc[0] try: t[0] = t[1] except IndexError: # pragma: no cover pass d = 10*row.df.loc[:, ' Horizontal (meters)'].values try: d[0] = d[1] except IndexError: # pragma: no cover pass p = abs(10*row.df.loc[:, ' Stroke500mPace (sec/500m)'].values) p = np.clip(p, 0, 3600) if w.workouttype == 'bike': # pragma: no cover p = 2.0*p t = t.astype(int) d = d.astype(int) p = p.astype(int) spm = row.df[' Cadence (stokes/min)'].astype(int) try: spm[0] = spm[1] except (KeyError, IndexError): # pragma: no cover spm = 0*t try: hr = row.df[' HRCur (bpm)'].astype(int) except ValueError: # pragma: no cover hr = 0*d stroke_data = [] t = t.tolist() d = d.tolist() p = p.tolist() spm = spm.tolist() hr = hr.tolist() for i in range(len(t)): thisrecord = {"t": t[i], "d": d[i], "p": p[i], "spm": spm[i], "hr": hr[i]} stroke_data.append(thisrecord) try: durationstr = datetime.datetime.strptime( str(w.duration), "%H:%M:%S.%f") except ValueError: durationstr = datetime.datetime.strptime(str(w.duration), "%H:%M:%S") workouttype = w.workouttype if workouttype in mytypes.otwtypes: workouttype = 'water' if w.timezone == 'tzutc()': # pragma: no cover w.timezone = 'UTC' w.save() try: wendtime = w.startdatetime.astimezone(pytz.timezone( w.timezone))+datetime.timedelta(seconds=makeseconds(durationstr)) except UnknownTimeZoneError: wendtime = w.startdatetime.astimezone(pytz.utc)+datetime.timedelta(seconds=makeseconds(durationstr)) data = { "type": mytypes.c2mapping[workouttype], # w.startdatetime.isoformat(), "date": wendtime.strftime('%Y-%m-%d %H:%M:%S'), "stroke_count": int(row.stroke_count), "timezone": w.timezone, "distance": int(w.distance), "time": int(10*makeseconds(durationstr)), "weight_class": c2wc(w.weightcategory), "comments": w.notes, 'stroke_rate': int(row.df[' Cadence (stokes/min)'].mean()), 'drag_factor': int(row.dragfactor), "heart_rate": { "average": averagehr, "max": maxhr, }, "stroke_data": stroke_data, 'workout': { 'splits': intervaldata, } } return data def workout_export(self, workout, *args, **kwargs) -> str: try: if mytypes.c2mapping[workout.workouttype] is None: # pragma: no cover return "0" except KeyError: # pragma: no cover return "0" _ = self.open() r = self.rower user = self.user if (is_workout_user(user, workout)): c2userid = self.get_userid() if not c2userid: # pragma: no cover raise NoTokenError("User has no token") dologging('c2_log.log', 'Upload to C2 user {userid}'.format(userid=user.id)) data = self.createworkoutdata(workout) dologging('c2_log.log', json.dumps(data)) if data == 0: # pragma: no cover return 0 authorizationstring = str('Bearer ' + r.c2token) headers = {'Authorization': authorizationstring, 'user-agent': 'sanderroosendaal', 'Content-Type': 'application/json'} url = "https://log.concept2.com/api/users/%s/results" % (c2userid) _ = myqueue(queue, handle_c2_sync, workout.id, url, headers, json.dumps(data, default=default)) c2id = 0 return c2id def get_workout(self, id, *args, **kwargs): _ = self.open() _ = myqueue(queuehigh, handle_c2_getworkout, self.user.id, self.rower.c2token, id, self.rower.defaulttimezone) return 1 def get_workouts(self, *args, **kwargs): page = kwargs.get('page',1) try: _ = self.open() except NoTokenError: return 0 # this part to get_workout_list workouts = self.get_workout_list(page=page) for workout in workouts: c2id = workout['id'] if workout['new'] == 'NEW': self.get_workout(c2id) return 1 # should be unified to workout list def get_workout_list(self, *args, **kwargs): page = kwargs.get('page',1) r = self.rower if (r.c2token == '') or (r.c2token is None): # pragma: no cover s = "Token doesn't exist. Need to authorize" return custom_exception_handler(401, s) elif (timezone.now() > r.tokenexpirydate): # pragma: no cover s = "Token expired. Needs to refresh." return custom_exception_handler(401, s) # ready to fetch. Hurray authorizationstring = str('Bearer ' + r.c2token) headers = {'Authorization': authorizationstring, 'user-agent': 'sanderroosendaal', 'Content-Type': 'application/json'} url = "https://log.concept2.com/api/users/me/results" url += "?page={page}".format(page=page) res = requests.get(url, headers=headers) if (res.status_code != 200): # pragma: no cover return [] workouts = [] c2ids = [item['id'] for item in res.json()['data']] knownc2ids = uniqify([ w.uploadedtoc2 for w in Workout.objects.filter(user=self.rower) ]) tombstones = [ t.uploadedtoc2 for t in TombStone.objects.filter(user=self.rower) ] parkedids = [] try: with open('c2blocked.json', 'r') as c2blocked: jsondata = json.load(c2blocked) parkedids = jsondata['ids'] except: # pragma: no cover pass knownc2ids = uniqify(knownc2ids+tombstones+parkedids) for item in res.json()['data']: d = item['distance'] i = item['id'] ttot = item['time_formatted'] s = item['date'] r = item['type'] s2 = item['source'] c = item['comments'] if i in knownc2ids: nnn = '' else: # pragma: no cover nnn = 'NEW' keys = ['id', 'distance', 'duration', 'starttime', 'rowtype', 'source', 'name', 'new'] values = [i, d, ttot, s, r, s2, c, nnn] ress = dict(zip(keys, values)) workouts.append(ress) return workouts