Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

start on little tool to back up data from catch.

right now only does notes json output.
media and one-note-per-file support next.
  • Loading branch information...
commit 354262b05620022af8071db05113ec2af2c16a57 1 parent 3bffed8
@niallo authored
Showing with 175 additions and 0 deletions.
  1. +25 −0 LICENSE
  2. +1 −0  README
  3. +149 −0 backup.py
View
25 LICENSE
@@ -0,0 +1,25 @@
+Copyright (c) 2011 Niall O'Higgins
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+
+* Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+
+* Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in the
+ documentation and/or other materials provided with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
View
1  README
@@ -0,0 +1 @@
+Tool to back up notes and media items from Catch account
View
149 backup.py
@@ -0,0 +1,149 @@
+#!/usr/bin/env python2.7
+
+"""
+
+Backup notes and media from Catch account to local disk
+
+:copyright: Copyright 2011 Niall O'Higgins
+:license: BSD, see LICENSE for details.
+
+"""
+
+import argparse
+import base64
+import datetime
+import getpass
+import httplib
+import json
+import sqlite3
+import sys
+import urllib
+
+API = "api.catch.com"
+
+def get_username():
+ ''' Read username from terminal '''
+ while True:
+ sys.stdout.write("Username: ")
+ username = sys.stdin.readline().strip()
+ if username:
+ break
+
+ return username
+
+def get_password():
+ ''' Read password from terminal '''
+ while True:
+ password = getpass.getpass("Password: ")
+ if password:
+ break
+
+ return password
+
+class UsernameRequired(Exception):
+ pass
+
+class PasswordRequired(Exception):
+ pass
+
+class FilenameRequired(Exception):
+ pass
+
+class NoDataError(Exception):
+ pass
+
+
+class CatchBackup(object):
+
+ def __init__(self, username=None, password=None):
+
+ if not username:
+ raise UsernameRequired()
+ if not password:
+ raise PasswordRequired()
+
+ self.username = username
+ self.password = password
+ self.raw_data = None
+ self.cooked_data = None
+
+ def _make_basic_auth_header(self):
+ ''' Basic auth '''
+ return {"Authorization":"Basic %s" %(
+ base64.b64encode("%s:%s" %(self.username, self.password)))}
+
+ def fetch_data(self):
+
+ ''' Fetch the raw unstructured workout data from Catch API. Other
+ methods will parse this into structured '''
+
+ self.conn = httplib.HTTPSConnection(API)
+ headers = self._make_basic_auth_header()
+
+ req = self.conn.request("GET", "/v2/notes.json?full=1", headers=headers)
+
+ res = self.conn.getresponse()
+
+ if res.status != 200:
+ sys.stderr.write("%d response from server.\n Reason: %s" %(
+ res.status,
+ res.reason))
+ sys.exit(1)
+
+ data = res.read()
+
+ self.raw_data = data
+ notes = json.loads(data)
+ res.close()
+
+ def parse_rfc3339(s):
+ if not s: return None
+ return datetime.datetime.strptime(s, '%Y-%m-%dT%H:%M:%S.%fZ')
+
+ # convert timestamps to native Python types
+ for note in notes["notes"]:
+ note["created_at"] = parse_rfc3339(note.get("created_at"))
+ note["modified_at"] = parse_rfc3339(note.get("modified_at"))
+ note["reminder_at"] = parse_rfc3339(note.get("reminder_at"))
+
+ self.cooked_data = notes
+
+ return notes
+
+ def dump_notes(self, filename=None):
+ if not filename:
+ raise FilenameRequired()
+
+ if not self.raw_data:
+ raise NoDataError()
+
+ f = open(filename, "w")
+ f.write(self.raw_data)
+ f.close()
+
+def main():
+ parser = argparse.ArgumentParser(description='Backup data from Catch API')
+ parser.add_argument('-f', '--file', dest='outfile',
+ help='JSON data file to write (default: notes.json)',
+ default="notes.json")
+
+ parser.add_argument('-u', '--username', dest='username', help='username to use')
+
+
+ args = parser.parse_args()
+
+ if not args.username:
+ args.username = get_username()
+ args.password = get_password()
+
+ cb = CatchBackup(username=args.username, password=args.password)
+
+ sys.stdout.write("Fetching notes...\n")
+ data = cb.fetch_data()
+
+ sys.stdout.write("Writing notes to %s\n" %(args.outfile))
+ cb.dump_notes(filename=args.outfile)
+ sys.stdout.write("Backup complete!\n")
+
+if __name__ == "__main__":
+ main()
Please sign in to comment.
Something went wrong with that request. Please try again.