#!/usr/bin/python
#
# This is a python program.  It needs to be run using the python interpreter.
# On most systems, this can be accomplished by typing:  "python THISFILE"  (e.g python getNotesaver.py )
#
doc="""  This program interacts with the iPhone to save the notes taken on the device, and to
upload notes generated on another computer.  The backup and restore functions should work on almost
any operating system.  The ability to inspect or modify the notes depends on other tools that 
may on may not be present and might have to be installed.
  This tool can interact with the iPhone over either the USB connection (as iTunes does)
or via an internet connection, in which case it depends on your having installed ssh/scp
on the iPhone. 
  This application also has the ability to patch the iPhone display to change the font size
in which the name of the local cell phone carrier is displayed.  This feature does not fit, but
the program had all the scaffolding in place for doing this.  This allows people using
a carrier whose name is longer to have the whole name appear in the upper-left corner of
their phone.  (Currently only supported via ssh/scp, not iphuc.)
  This program was written by Gregory Dudek, free for non-commercial use.
"""
# Change log:
#  1.0.1: Caught keyboard interrupt handling on failed scp login.
#  1.0.2: Minor changes to documentation, default preferences.
#  1.1.0: Auto-check for updated version
#  1.2.0: Added ability to patch SpringBoard via scp.  Also better overall modularization.
#  1.3.0: Generalize usb/scp file transfer

ORIGIN="Program from http://www.dudek.org/blog/75  by G. Dudek."
VERSION="1.3.2"


# These preferences will later be stored in getNotesaver.rc
# in which case those settings override these defaults.
USE_SSH = -1
USER="mobile"
PHONE_IP = "192.168.0.1"
IPHUC_PATH = "iphuc"
PREFS = "getNotesaver.rc"
NOTES = "notes.db"
debug=0
print_on_save=1
next_version_check = 0

cpatch=0  # patch carrier font size in phone.

import sys,os

import re
inquotes = re.compile(r'''\s*(".*?"|'.*?')(.*)''')
badchars = re.compile(r'''^[^'," \[\]\(\)#]+$''')
##commented_line = re.compile(r'''\s*([^#]*)\s*(#.*)''')
paramfinder = re.compile(r'''(?:'.*?')|(?:".*?")|(?:[^'",\s][^,]*)''') 
unquoted = re.compile(r'''
    ([^\#,"'\(\)\[\]][^\#,\]\)]*)  # value
    \s*                         # whitespace - XXX not caught
    ([\#,\)\]].*)?                  # rest of the line
    $''', re.VERBOSE)
def elem_quote(member, nonquote=True, stringify=False, encoding=None):
    """
    Simple method to add the most appropriate quote to an element - either single 
    quotes or double quotes.
    If ``nonquote`` is set to ``True`` (the default), then if member contains none 
    of ``'," []()#;`` then it isn't quoted at all.
    If ``stringify`` is set to ``True`` (the default is ``False``) then non string 
    (unicode or byte-string) values will be first converted to strings using the 
    ``str`` function. Otherwise elem_quote raises a ``TypeError``.
    """
    if not isinstance(member, basestring):
        if stringify:
            member = str(member)
        else:
            # FIXME: is this the appropriate error message ?
            raise TypeError('Can only quote strings. "%s"' % str(member))
    if encoding and isinstance(member, str):
        # from string to unicode
        member = unicode(member, encoding)
    if '\n' in member:
        raise QuoteError('Multiline values can\'t be quoted.\n"%s"' % str(member))
    #
    if nonquote and badchars.match(member) is not None:
        return member
    # this ordering of tests determines which quote character will be used in 
    # preference - here we have \" first...
    elif member.find('"') == -1:
        return '"%s"' % member
    # but we will use either... which may not suit some people
    elif member.find("'") == -1:
        return "'%s'" % member
    else:
        raise QuoteError('Value can\'t be quoted : "%s"' % member)

def putpref(f,var,comment=""):
    if type(eval(var))==type("a"):
        f.write(var+" = ")
        f.write(elem_quote(eval(var),nonquote=0))
        if comment: f.write('  #  '+comment)
        f.write("\n")
    else:
        f.write(var+" = "+str(eval(var)))
        if comment: f.write('#  '+comment)
        f.write("\n")

def saveprefs():
    f=open(PREFS,"w")
    f.write("# Preferences file for Dudek's iPhone note saver.\n")
    putpref(f,"VERSION","  #version of the program that made this file")
    putpref(f,"ORIGIN")
    putpref(f,"USE_SSH","integer value, 0 or 1")
    putpref(f,"USER")
    putpref(f,"PHONE_IP")
    putpref(f,"IPHUC_PATH")
    putpref(f,"NOTES")
    putpref(f,"print_on_save","print notes from iphone, if sqlite3 installed")
    putpref(f,"next_version_check","when to check for an updated version")
    f.close()

def runphone(phonefile,ignore_return=0):
    global USER,PHONE_IP
    cmd="ssh "+USER+"@"+PHONE_IP+" "+phonefile
    print cmd
    ret = os.system(cmd + " > /dev/tty < /dev/tty")
    if ret != 0 and not ignore_return:
        print "Use of ssh failed",ret
        if ret>256:
            print "ssh could not be executed.  Is it installed?"
        else:
            print "iPhone",USER,"password incorrect."
            print "or iPhone IP address wrong, or iPhone not on the network."
            if USER== "root": print "The default iphone root password is alpine."
            print "Re-enter address now (or hit control-C)."
            print "iPhone IP address:",
            try: 
                PHONE_IP = sys.stdin.readline().strip()
                saveprefs()
            except KeyboardInterrupt: pass
        sys.exit(1)

def scp(phonefile,filepath,tophone=0):
    """ Get/put file from/to phone using scp """
    global USER,PHONE_IP
    if tophone:
        cmd="scp %s "%filepath +USER+"@"+PHONE_IP+":"+phonefile
    else:
        cmd="scp "+USER+"@"+PHONE_IP+":"+phonefile+" %s"%filepath
    print cmd
    ret = os.system(cmd + " > /dev/tty < /dev/tty")
    if ret != 0:
        print "Use of scp failed",ret
        if ret >256:
            print "scp could not be executed.  Is it installed?"
        else:
            print "iPhone",USER,"password incorrect."
            print "or iPhone IP address wrong, or iPhone not on the network."
            if USER== "root": print "The default iphone root password is dottie."
            print "Re-enter address now (or hit control-C)."
            print "iPhone IP address:",
            try: 
                PHONE_IP = sys.stdin.readline().strip()
                saveprefs()
            except KeyboardInterrupt: pass
        sys.exit(1)
    print "Accessed copy of %s from iPhone using SSH."%filepath

def usb(phonefile,filepath,tophone=0):
    global IPHUC_PATH,USE_SSH
    import time
    try:
            import pexpect
    except:
            print "USB mode requires the (free) pexpect module for python also be installed."
            print "Visit http://pexpect.sourceforge.net/ for this tool."
            sys.exit(1)
    try:
        child = pexpect.spawn(IPHUC_PATH,timeout=10,)
    except:
        child=0
    if not child:
        print "Could not launch the program iphuc.  Is it installed?"
        print "You can get it from http://code.google.com/p/iphuc/"
        print "If you have ssh on the phone, that might be a preferred way to interact."
        print "Do you wish to USE ssh/scp INSTEAD OF USB (1 for scp, 0 for iphuc)?",
        USE_SSH = sys.stdin.readline().strip()
        if USE_SSH in ["yes","y","scp","ssh"]: USE_SSH=1
        if USE_SSH in ["no","n","iphuc"]: USE_SSH=0
        USE_SSH=int(USE_SSH)
        print "If iphuc is installed, please tell me the path (or hit return): ",
        IPHUC_PATH = sys.stdin.readline().strip()
        saveprefs()
        print "Now you need to run this program over again."
        sys.exit(1)

    if debug: child.logfile = sys.stdout
    try: child.expect('iPHUC.*\:')
    except pexpect.TIMEOUT:
        print "Cannot contact iPhone using USB connection."
        sys.exit(1)
    child.sendline("setafc com.apple.afc2")
    child.expect('iPHUC.*\.*:')
    child.sendline("setafc com.apple.afc2")
    child.expect('iPHUC.*\.*:')
    dirs = phonefile.split('/')
    for i in dirs[:-1]:
        child.sendline("cd "+i)
        child.expect('iPHUC.*\.*:')
    if tophone:
        child.sendline("putfile %s "%filepath+dirs[-1])
        print "Restored copy of %s to iPhone using USB."%dirs[-1]
    else:
        child.sendline("getfile %s %s"%(dirs[-1],filepath))
        print "Saved copy of %s from iPhone using USB."%dirs[-1]
    child.expect('iPHUC.*\.*:')

def transfer(phonefile,filepath,tophone=0):
     if USE_SSH: scp(phonefile,filepath,tophone)
     else: usb(phonefile,filepath,tophone)

####################### main program start #######################
try:
    # enable readline if available
    import rlcompleter, readline
    readline.parse_and_bind('tab: complete')
except: pass
try:
   # Check latest version for update (non-fatal if it doesn't work)
   if next_version_check < time.time():
       import urllib
       latest=urllib.URLopener().open("http://www.dudek.org/getNotesaver.py/getProperty?id=version").read()
       if len(latest)>1 and latest != VERSION:
          print "Note, a different version ("+latest+") of this program seems to be available."
          print "This is currently version",VERSION
          try: 
              print urllib.URLopener().open("http://www.dudek.org/getNotesaver.py/getProperty?id=whatsnew").read()
          except: pass
       next_version_check = time.time() + 60*60*24*7  # check at most every 7 days
except: pass

# Load preferences file, if possible.
v=VERSION
try:
    prefs=open(PREFS).read()
except: prefs=""
exec(prefs)
VERSION=v

restoremode=0
while len(sys.argv)>1:
    if sys.argv[1] == "-h" or sys.argv[1]=="--help":
        print doc
        print "Save or restore notes from iPhone."
        print "Options: -r    restore previously saved notes."
        print "Options: -usb  communicate via USB (may require killing iTunes Helper) [sticky]."
        print "Options: -ssh  communicate via SSH (TCP/IP) [sticky]."
        print "Options: -a x.x.x.x Internet address/name to be used for SSH (TCP/IP) [sticky]."
        print "Options: -cpatch  Change font size of carrier-name at top-left."
        print "Options: -version  Print program version and, if possible, lastest available version."
        print "Sticky options become defaults after being used once."
        del sys.argv[1]
    elif sys.argv[1] == "-r":
        print "Restoring backup copy of notes database from this computer to the iPhone."
        if not os.access(NOTES,0):
            print "Can't find notes file",NOTES,"to restore from."
            sys.exit(1)
        restoremode=1
        del sys.argv[1]
    elif sys.argv[1] == "-ssh":
        USE_SSH=1
        del sys.argv[1]
        saveprefs()
    elif sys.argv[1] == "-a":
        USE_SSH=1
        del sys.argv[1]
        PHONE_IP = sys.argv[1]
        del sys.argv[1]
        saveprefs()
    elif sys.argv[1] == "-cpatch":
        cpatch=1
        del sys.argv[1]
    elif sys.argv[1] == "-usb":
        USE_SSH=0
        del sys.argv[1]
        saveprefs()
    elif sys.argv[1] == "-version":
        del sys.argv[1]
        print "This is version",VERSION,"of this program."
        print "The latest version is",
        sys.stdout.flush()
        try:
            import urllib
            print urllib.URLopener().open("http://www.dudek.org/getNotesaver.py/getProperty?id=version").read()
        except: print "at http://www.dudek.org/blog/75"
    else:
        print "Unknown option",sys.argv[1],"try -h for a list of options."
        sys.exit(1)

if USE_SSH<0:
    print "The first time you run this, you ought to select -ssh or -usb mode."
    print "(or use the -h option for more information)."

    print "Do you wish to USE  ssh/scp for communication (preferred) as opposed to USB (1 for scp, 0 for USB with iphuc)?",
    USE_SSH = sys.stdin.readline().strip()
    if USE_SSH in ["yes","y","scp","ssh"]: USE_SSH=1
    if USE_SSH in ["no","n","iphuc"]: USE_SSH=0
    USE_SSH=int(USE_SSH)
    if not USE_SSH:
        if os.system("iphuc</dev/null>/dev/null") == 0:  # it runs ok.
            IPHUC_PATH = "iphuc"
        else:
            print "Running",os.system("iphuc")
            sys.exit(1)
            print "If iphuc is installed on your computer, please tell me the path (or hit return): ",
            print "You can get it from http://code.google.com/p/iphuc/"
            IPHUC_PATH = sys.stdin.readline().strip()
        try:
            import pexpect
        except:
            print "USB mode requires the (free) pexpect module for python also be installed."
            print "Visit http://pexpect.sourceforge.net/ for this tool."
            sys.exit(1)
    saveprefs()


if cpatch:
        print "This allows patching the carrier-name font size.  You will be having to"
        print "copy a file back and forth, and the will need to reboot the phone.\n"
        if USE_SSH: scp("/System/Library/CoreServices/SpringBoard.app/SpringBoard","/tmp/SpringBoard",0)
        else: usb("/System/Library/CoreServices/SpringBoard.app/SpringBoard","/tmp/SpringBoard",0)
	f=open("/tmp/SpringBoard").read()
	print "Old font size:",f[0x7c690:0x7c698]

	print "Pick point size for cell carrier name (type 10,12,14,16,18):"
	newpoint=sys.stdin.readline().strip()[-1]
	print "New point size will be 1"+newpoint
	q=f[:0x7c697]+newpoint+f[0x7c698:]
	print q[0x7c690:0x7c698]
	open("/tmp/SpringBoard","w").write(q)
        if USE_SSH: scp("/System/Library/CoreServices/SpringBoard.app/SpringBoard","/tmp/SpringBoard",1)
        else: usb("/System/Library/CoreServices/SpringBoard.app/SpringBoard","/tmp/SpringBoard",1)
        print "Patched. Enter your password when prompted if you want to reboot the phone, else control-C."
        runphone("/sbin/reboot",ignore_return=1)
        sys.exit(0)

# Get file from iPhone
if USE_SSH:
    scp("Library/Notes/notes.db",NOTES,restoremode)
else:
    usb("/var/root/Library/Notes/notes.db",NOTES,restoremode)


if not restoremode:
    # generate sqlite3 command file
    # will fail on systems that don't have sqlite3, but so what?
    #
    f=open("/tmp/sqcmds.txt","w");
    f.write( ".separator \\n\n")
    f.write( ".output notes.ascii\n")
    f.write( "select '\n',title,summary from Note;\n")
    f.write( "select '\n',* from note_bodies;\n")
    f.write( ".output notes.html\n")
    f.write( ".mode html\n")
    f.write( "select '\n',* from note_bodies;\n")
    f.close()
    if os.system("sqlite3 %s < /tmp/sqcmds.txt"%NOTES)>0:
        print "Could not run sqlite3 to produce readable ASCII version of database."
        print "You can get it at http://www.sqlite.org/"
        sys.exit(0)
    #if print_on_save: print open("notes.ascii","r").read()
    if print_on_save:
       f=open("/tmp/sqcmds.txt","w");
       f.write( ".mode line\n")
       f.write( "select * from Note;\n")
       f.close()
       os.system("sqlite3 %s < /tmp/sqcmds.txt"%NOTES)
       print "osascript -e 'tell application \"Safari\"' -e 'open \"%s/notes.html\"' -e 'end tell'"%(os.getcwd().replace(':','/')) 
       os.system("osascript -e 'tell application \"Safari\"' -e 'open \"%s/notes.html\"' -e 'end tell'"%(os.getcwd().replace(':','/')) )
    print "Saved data in readable form in notes.ascii (without dates, but they are saved in the database)."
    print "Saved data as wen page in notes.html (without dates, but they are saved in the database)."

