from .integrations import SyncIntegration, NoTokenError, create_or_update_syncrecord, get_known_ids from rowers.models import User, Rower, Workout, TombStone from django.db.utils import IntegrityError from rowingdata import rowingdata, rowingdata_pl 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, 'authorization_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 except: 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 ValueError: 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() r = self.rower record = create_or_update_syncrecord(r, None, c2id=id) _ = 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) count = 0 for workout in workouts: c2id = workout['id'] if workout['new'] == 'NEW': self.get_workout(c2id) count+= 1 return count # 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 = get_known_ids(r, 'c2id') 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