Skip to content

Commit

Permalink
Refactored service exceptions to allow for exceptions that block the …
Browse files Browse the repository at this point in the history
…originating service, or entire account, until cleared. Added front-end to show and dismiss these exceptions. Added extra exceptions for Dropbox quota errors and ST.mobi account expiry.
  • Loading branch information
cpfair committed Oct 5, 2013
1 parent b89f6ba commit 8c8aad9
Show file tree
Hide file tree
Showing 22 changed files with 1,411 additions and 1,238 deletions.
4 changes: 2 additions & 2 deletions stats_cron.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@
pendingSynchronizations = 0

usersWithErrors = db.users.aggregate([
{"$match": {"SyncErrorCount": {"$gt": 0}}},
{"$match": {"NonblockingSyncErrorCount": {"$gt": 0}}},
{"$group": {"_id": None, "count": {"$sum": 1}}}
])
if len(usersWithErrors["result"]) > 0:
Expand All @@ -38,7 +38,7 @@

totalErrors = db.users.aggregate([
{"$group": {"_id": None,
"total": {"$sum": "$SyncErrorCount"}}}
"total": {"$sum": "$NonblockingSyncErrorCount"}}}
])

if len(totalErrors["result"]) > 0:
Expand Down
6 changes: 4 additions & 2 deletions tapiriik/auth/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ def HasActivePayment(user):
return False

def ConnectService(user, serviceRecord):
from tapiriik.services import Service
from tapiriik.services import Service, UserExceptionType
existingUser = db.users.find_one({"_id": {'$ne': ObjectId(user["_id"])}, "ConnectedServices.ID": ObjectId(serviceRecord._id)})
if "ConnectedServices" not in user:
user["ConnectedServices"] = []
Expand All @@ -68,7 +68,8 @@ def ConnectService(user, serviceRecord):
user["FlowExceptions"] = []
user["FlowExceptions"] += existingUser["FlowExceptions"]
user["Email"] = user["Email"] if "Email" in user and user["Email"] is not None else (existingUser["Email"] if "Email" in existingUser else None)
user["SyncErrorCount"] = (user["SyncErrorCount"] if "SyncErrorCount" in user and user["SyncErrorCount"] is not None else 0) + (existingUser["SyncErrorCount"] if "SyncErrorCount" in existingUser and existingUser["SyncErrorCount"] is not None else 0)
user["NonblockingSyncErrorCount"] = (user["NonblockingSyncErrorCount"] if "NonblockingSyncErrorCount" in user and user["NonblockingSyncErrorCount"] is not None else 0) + (existingUser["NonblockingSyncErrorCount"] if "NonblockingSyncErrorCount" in existingUser and existingUser["NonblockingSyncErrorCount"] is not None else 0)
user["BlockingSyncErrorCount"] = (user["BlockingSyncErrorCount"] if "BlockingSyncErrorCount" in user and user["BlockingSyncErrorCount"] is not None else 0) + (existingUser["BlockingSyncErrorCount"] if "BlockingSyncErrorCount" in existingUser and existingUser["BlockingSyncErrorCount"] is not None else 0)
user["SyncExclusionCount"] = (user["SyncExclusionCount"] if "SyncExclusionCount" in user and user["SyncExclusionCount"] is not None else 0) + (existingUser["SyncExclusionCount"] if "SyncExclusionCount" in existingUser and existingUser["SyncExclusionCount"] is not None else 0)
user["Created"] = user["Created"] if user["Created"] < existingUser["Created"] else existingUser["Created"]
if "AncestorAccounts" not in user:
Expand All @@ -90,6 +91,7 @@ def ConnectService(user, serviceRecord):

db.users.update({"_id": user["_id"]}, user)
if delta or (hasattr(serviceRecord, "SyncErrors") and len(serviceRecord.SyncErrors) > 0): # also schedule an immediate sync if there is an outstanding error (i.e. user reconnected)
db.connections.update({"_id": serviceRecord._id}, {"$pull": {"SyncErrors": {"UserException.Type": UserExceptionType.Authorization}}}) # Pull all auth-related errors from the service so they don't continue to see them while the sync completes.
Sync.SetNextSyncIsExhaustive(user, True) # exhaustive, so it'll pick up activities from newly added services / ones lost during an error
if hasattr(serviceRecord, "SyncErrors") and len(serviceRecord.SyncErrors) > 0:
Sync.ScheduleImmediateSync(user)
Expand Down
620 changes: 311 additions & 309 deletions tapiriik/services/Dropbox/dropbox.py

Large diffs are not rendered by default.

10 changes: 7 additions & 3 deletions tapiriik/services/Endomondo/endomondo.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
from tapiriik.services.service_base import ServiceAuthenticationType, ServiceBase
from tapiriik.database import cachedb
from tapiriik.services.interchange import UploadedActivity, ActivityType, Waypoint, WaypointType, Location
from tapiriik.services.api import APIException, APIAuthorizationException, APIExcludeActivity
from tapiriik.services.api import APIException, APIExcludeActivity, UserException, UserExceptionType

from django.core.urlresolvers import reverse
from datetime import datetime, timedelta
Expand Down Expand Up @@ -84,7 +84,7 @@ def Authorize(self, email, password):

resp = requests.get("https://api.mobile.endomondo.com/mobile/auth", params=params)
if resp.text.strip() == "USER_UNKNOWN" or resp.text.strip() == "USER_EXISTS_PASSWORD_WRONG":
raise APIAuthorizationException("Invalid login")
raise APIException("Invalid login", block=True, user_exception=UserException(UserExceptionType.Authorization, intervention_required=True))
data = self._parseKVP(resp.text)
return (data["userId"], {"AuthToken": data["authToken"], "SecureToken": data["secureToken"]})

Expand Down Expand Up @@ -190,12 +190,16 @@ def DownloadActivityList(self, serviceRecord, exhaustive=False):
params = {"authToken": serviceRecord.Authorization["AuthToken"], "maxResults": 45, "before": before}
logger.debug("Req with " + str(params))
response = requests.get("http://api.mobile.endomondo.com/mobile/api/workout/list", params=params)

if response.status_code != 200:
if response.status_code == 401 or response.status_code == 403:
raise APIAuthorizationException("No authorization to retrieve activity list")
raise APIException("No authorization to retrieve activity list", block=True, user_exception=UserException(UserExceptionType.Authorization, intervention_required=True))
raise APIException("Unable to retrieve activity list " + str(response))
data = response.json()

if "error" in data and data["error"]["type"] == "AUTH_FAILED":
raise APIException("No authorization to retrieve activity list", block=True, user_exception=UserException(UserExceptionType.Authorization, intervention_required=True))

track_ids = []
this_page_activities = []

Expand Down
6 changes: 3 additions & 3 deletions tapiriik/services/GarminConnect/garminconnect.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
from tapiriik.services.service_base import ServiceAuthenticationType, ServiceBase
from tapiriik.database import cachedb
from tapiriik.services.interchange import UploadedActivity, ActivityType, Waypoint, WaypointType, Location
from tapiriik.services.api import APIException, APIAuthorizationException, APIWarning, APIExcludeActivity
from tapiriik.services.api import APIException, APIWarning, APIExcludeActivity, UserException, UserExceptionType
from tapiriik.services.tcx import TCXIO
from tapiriik.services.sessioncache import SessionCache

Expand Down Expand Up @@ -75,7 +75,7 @@ def _get_cookies(self, record=None, email=None, password=None):
preResp = requests.get("https://connect.garmin.com/signin")
resp = requests.post("https://connect.garmin.com/signin", data=params, allow_redirects=False, cookies=preResp.cookies)
if resp.status_code != 302: # yep
raise APIAuthorizationException("Invalid login")
raise APIException("Invalid login", block=True, user_exception=UserException(UserExceptionType.Authorization, intervention_required=True))
if record:
self._sessionCache.Set(record.ExternalID, preResp.cookies)
return preResp.cookies
Expand All @@ -88,7 +88,7 @@ def Authorize(self, email, password):
cookies = self._get_cookies(email=email, password=password)
username = requests.get("http://connect.garmin.com/user/username", cookies=cookies).json()["username"]
if not len(username):
raise APIAuthorizationException("Unable to retrieve username")
raise APIException("Unable to retrieve username", block=True, user_exception=UserException(UserExceptionType.Authorization, intervention_required=True))
return (username, {}, {"Email": CredentialStore.Encrypt(email), "Password": CredentialStore.Encrypt(password)})


Expand Down
12 changes: 6 additions & 6 deletions tapiriik/services/RunKeeper/runkeeper.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
from tapiriik.settings import WEB_ROOT, RUNKEEPER_CLIENT_ID, RUNKEEPER_CLIENT_SECRET, AGGRESSIVE_CACHE
from tapiriik.services.service_base import ServiceAuthenticationType, ServiceBase
from tapiriik.services.service_record import ServiceRecord
from tapiriik.services.api import APIException, APIAuthorizationException, APIExcludeActivity
from tapiriik.services.api import APIException, UserException, UserExceptionType, APIExcludeActivity
from tapiriik.services.interchange import UploadedActivity, ActivityType, WaypointType, Waypoint, Location
from tapiriik.database import cachedb
from django.core.urlresolvers import reverse
Expand Down Expand Up @@ -52,7 +52,7 @@ def RetrieveAuthorizationToken(self, req, level):

response = requests.post("https://runkeeper.com/apps/token", data=urllib.parse.urlencode(params), headers={"Content-Type": "application/x-www-form-urlencoded"})
if response.status_code != 200:
raise APIAuthorizationException("Invalid code")
raise APIException("Invalid code")
token = response.json()["access_token"]

# hacky, but also totally their fault for not giving the user id in the token req
Expand Down Expand Up @@ -81,7 +81,7 @@ def _getAPIUris(self, serviceRecord):

if response.status_code != 200:
if response.status_code == 401 or response.status_code == 403:
raise APIAuthorizationException("No authorization to retrieve user URLs")
raise APIException("No authorization to retrieve user URLs", block=True, user_exception=UserException(UserExceptionType.Authorization, intervention_required=True))
raise APIException("Unable to retrieve user URLs" + str(response))

uris = response.json()
Expand All @@ -107,7 +107,7 @@ def DownloadActivityList(self, serviceRecord, exhaustive=False):
response = requests.get(pageUri, headers=self._apiHeaders(serviceRecord))
if response.status_code != 200:
if response.status_code == 401 or response.status_code == 403:
raise APIAuthorizationException("No authorization to retrieve activity list")
raise APIException("No authorization to retrieve activity list", block=True, user_exception=UserException(UserExceptionType.Authorization, intervention_required=True))
raise APIException("Unable to retrieve activity list " + str(response) + " " + response.text)
data = response.json()
allItems += data["items"]
Expand Down Expand Up @@ -156,7 +156,7 @@ def DownloadActivity(self, serviceRecord, activity):
response = requests.get("https://api.runkeeper.com" + activityID, headers=self._apiHeaders(serviceRecord))
if response.status_code != 200:
if response.status_code == 401 or response.status_code == 403:
raise APIAuthorizationException("No authorization to download activity" + activityID)
raise APIException("No authorization to download activity" + activityID, block=True, user_exception=UserException(UserExceptionType.Authorization, intervention_required=True))
raise APIException("Unable to download activity " + activityID + " response " + str(response) + " " + response.text)
ridedata = response.json()
ridedata["Owner"] = serviceRecord.ExternalID
Expand Down Expand Up @@ -210,7 +210,7 @@ def UploadActivity(self, serviceRecord, activity):

if response.status_code != 201:
if response.status_code == 401 or response.status_code == 403:
raise APIAuthorizationException("No authorization to upload activity " + activity.UID)
raise APIException("No authorization to upload activity " + activity.UID, block=True, user_exception=UserException(UserExceptionType.Authorization, intervention_required=True))
raise APIException("Unable to upload activity " + activity.UID + " response " + str(response) + " " + response.text)

def _createUploadData(self, activity):
Expand Down
6 changes: 4 additions & 2 deletions tapiriik/services/SportTracks/sporttracks.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
from tapiriik.settings import WEB_ROOT, SPORTTRACKS_OPENFIT_ENDPOINT
from tapiriik.services.service_base import ServiceAuthenticationType, ServiceBase
from tapiriik.services.interchange import UploadedActivity, ActivityType, Waypoint, WaypointType, Location
from tapiriik.services.api import APIException, APIAuthorizationException, APIExcludeActivity
from tapiriik.services.api import APIException, UserException, UserExceptionType, APIExcludeActivity
from tapiriik.services.sessioncache import SessionCache

from django.core.urlresolvers import reverse
Expand Down Expand Up @@ -148,7 +148,7 @@ def _get_cookies_and_uid(self, record=None, email=None, password=None):
params = {"username": email, "password": password}
resp = requests.post(self.OpenFitEndpoint + "/user/login", data=json.dumps(params), allow_redirects=False, headers={"Accept": "application/json", "Content-Type": "application/json"})
if resp.status_code != 200:
raise APIAuthorizationException("Invalid login")
raise APIException("Invalid login", block=True, user_exception=UserException(UserExceptionType.Authorization, intervention_required=True))

retval = (resp.cookies, int(resp.json()["user"]["uid"]))
if record:
Expand Down Expand Up @@ -353,6 +353,8 @@ def stream_append(stream, wp, data):
cookies = self._get_cookies(record=serviceRecord)
upload_resp = requests.post(self.OpenFitEndpoint + "/fitnessActivities.json", data=json.dumps(activityData), cookies=cookies, headers={"Content-Type": "application/json"})
if upload_resp.status_code != 200:
if upload_resp.status_code == 401:
raise APIException("ST.mobi trial expired", block=True, user_exception=UserException(UserExceptionType.AccountExpired, intervention_required=True))
raise APIException("Unable to upload activity %s" % upload_resp.text)


Loading

0 comments on commit 8c8aad9

Please sign in to comment.