├── README ├── TODO └── spojbackup.py /README: -------------------------------------------------------------------------------- 1 | ''' 2 | _____ _____ _____ __ _ _ 3 | | __| _ | |__| | | |_ ___ ___| |_ _ _ ___ 4 | |__ | __| | | | | | . | .'| _| '_| | | . | 5 | |_____|__| |_____|_____| |___|__,|___|_,_|___| _| 6 | |_| 7 | 8 | Copyright (c) 2009, Abhishek Mishra 9 | All rights reserved. 10 | 11 | Redistribution and use in source and binary forms, with or without modification, 12 | are permitted provided that the following conditions are met: 13 | 14 | * Redistributions of source code must retain the above copyright notice, 15 | this list of conditions and the following disclaimer. 16 | * Redistributions in binary form must reproduce the above copyright notice, 17 | this list of conditions and the following disclaimer in the documentation 18 | and/or other materials provided with the distribution. 19 | * Neither the name of the Secret Labs AB nor the names of its contributors 20 | may be used to endorse or promote products derived from this software 21 | without specific prior written permission. 22 | 23 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 24 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 25 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 26 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR 27 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 28 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 29 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON 30 | ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 31 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 32 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 33 | --------------------------------------------------------------------------------/ 34 | ''' 35 | 36 | 37 | SPOJBACKUP is a Python script that helps you take incremental backup of all your 38 | accepted and challenge code submissions on Sphere Online Judge (http://spoj.pl). 39 | 40 | Usage is simple - 41 | 42 | $ python ./spojbackup.py -o /disk/code/spoj/mybackup/ 43 | Enter username : foobar 44 | Enter password : *** 45 | 46 | 47 | Notes - 48 | 49 | * Packaging is yet to be done 50 | * Support on Windows yet to be done 51 | * Maybe we can add a tiny UI too, if at all needed 52 | 53 | 54 | Author - Abhishek Mishra 55 | Proxy support - Shashwat Anand 56 | -------------------------------------------------------------------------------- /TODO: -------------------------------------------------------------------------------- 1 | 1. [+] optparse for proxy configs / optional for normal users 2 | 2. [+] no mercy on zero byte files 3 | 3. [+] https http issues 4 | 4. [+] modify file modification dates to match spoj submission timestamp 5 | 5. [ ] packaging, such that 6 | after installing, 7 | one is able to say 8 | $ mkdir foobarcodes 9 | $ cd foobarcodes 10 | $ spojbackup 11 | Enter username: 12 | password 13 | . 14 | . 15 | . 16 | -------------------------------------------------------------------------------- /spojbackup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | ''' 5 | 6 | _____ _____ _____ __ _ _ 7 | | __| _ | |__| | | |_ ___ ___| |_ _ _ ___ 8 | |__ | __| | | | | | . | .'| _| '_| | | . | 9 | |_____|__| |_____|_____| |___|__,|___|_,_|___| _| 10 | |_| 11 | 12 | Keywords: python, tools, algorithms, spoj 13 | 14 | Redistribution and use in source and binary forms, with or without modification, 15 | are permitted provided that the following conditions are met: 16 | 17 | * Redistributions of source code must retain the above copyright notice, 18 | this list of conditions and the following disclaimer. 19 | * Redistributions in binary form must reproduce the above copyright notice, 20 | this list of conditions and the following disclaimer in the documentation 21 | and/or other materials provided with the distribution. 22 | * Neither the name of the Secret Labs AB nor the names of its contributors 23 | may be used to endorse or promote products derived from this software 24 | without specific prior written permission. 25 | 26 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 27 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 28 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 29 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR 30 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 31 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 32 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON 33 | ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 34 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 35 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 36 | 37 | ''' 38 | # Dependencies: 39 | # mechanize => http://pypi.python.org/pypi/mechanize/0.1.7b 40 | 41 | import os 42 | import sys 43 | import glob 44 | import getpass 45 | import optparse 46 | import time 47 | import datetime 48 | 49 | try: 50 | from mechanize import Browser 51 | except ImportError: 52 | print "mechanize required but missing" 53 | sys.exit(1) 54 | 55 | 56 | def getSolutions (path_prefix, path_proxy): 57 | global br, username, password 58 | 59 | # create a browser object 60 | br = Browser() 61 | 62 | # add proxy support to browser 63 | if len(path_proxy) != 0: 64 | protocol,proxy = options.proxy.split("://") 65 | br.set_proxies({protocol:proxy}) 66 | 67 | # let browser fool robots.txt 68 | br.addheaders = [('User-agent', 'Mozilla/5.0 (X11; U; Linux i686; en-US; \ 69 | rv:1.9.0.1) Gecko/2008071615 Fedora/3.0.1-1.fc9 Firefox/3.0.1')] 70 | br.set_handle_robots(False) 71 | 72 | print "Enter yout SPOJ username :", 73 | username = raw_input() 74 | password = getpass.getpass() 75 | 76 | # authenticate the user 77 | print "Authenticating " + username 78 | br.open ("https://spoj.com/login") 79 | #br.select_form (name="login") 80 | #the form no longer is named "login" therefore to access it by id: 81 | formcount=0 82 | for frm in br.forms(): 83 | if str(frm.attrs["id"])=="login-form": 84 | break 85 | formcount=formcount+1 86 | br.select_form(nr=formcount) 87 | 88 | br["login_user"] = username 89 | br["password"] = password 90 | 91 | # sign in for a day to avoid timeouts 92 | #br.find_control(name="autologin").items[0].selected = True 93 | #this attribute is missing in the new spoj format 94 | br.form.action = "https://www.spoj.com/login" 95 | response = br.submit() 96 | 97 | verify = response.read() 98 | if (verify.find("Authentication failed!") != -1): 99 | print "Error authenticating - " + username 100 | exit(0) 101 | 102 | # grab the signed submissions list 103 | print "Grabbing siglist for " + username 104 | siglist = br.open("https://www.spoj.com/status/" + username + "/signedlist") 105 | 106 | # dump first nine useless lines in signed list for formatting 107 | for i in xrange(9): 108 | siglist.readline() 109 | 110 | # make a list of all AC's and challenges 111 | print "Filtering siglist for AC/Challenge solutions..." 112 | mysublist = list() 113 | 114 | while True: 115 | temp = siglist.readline() 116 | 117 | if temp=='\------------------------------------------------------------------------------/\n': 118 | # reached end of siglist 119 | break 120 | 121 | if not len(temp) : 122 | print "Reached EOF, siglist format has probably changed," + \ 123 | " contact author." 124 | exit(1) 125 | 126 | entry = [x.strip() for x in temp.split('|')] 127 | 128 | if entry[4] == 'AC' or entry[4].isdigit(): 129 | mysublist.append (entry) 130 | 131 | print "Done !!!" 132 | return mysublist 133 | 134 | def downloadSolutions(mysublist): 135 | totalsubmissions = len(mysublist) 136 | 137 | print "Fetching sources into " + path_prefix 138 | progress = 0 139 | 140 | for entry in mysublist: 141 | existing_files = glob.glob(os.path.join(path_prefix, "%s-%s*" % \ 142 | (entry[3],entry[1]))) 143 | 144 | progress += 1 145 | if len(existing_files) == 1: 146 | print "%d/%d - %s skipped." % (progress, totalsubmissions, entry[3]) 147 | else: 148 | source_code = br.open("https://www.spoj.com/files/src/save/" + \ 149 | entry[1]) 150 | header = dict(source_code.info()) 151 | filename = "" 152 | try: 153 | filename = header['content-disposition'].split('=')[1] 154 | filename = entry[3] + "-" + filename 155 | except: 156 | filename = entry[3] + "-" + entry[1] 157 | 158 | fp = open( os.path.join(path_prefix, filename), "w") 159 | fp.write (source_code.read()) 160 | fp.close 161 | timestamp = time.mktime(datetime.datetime.strptime(entry[2], "%Y-%m-%d %H:%M:%S").timetuple()) 162 | os.utime(os.path.join(path_prefix, filename),(timestamp, timestamp)) 163 | print "%d/%d - %s done." % (progress, totalsubmissions, filename) 164 | 165 | print "Created a backup of %d submission for %s" % \ 166 | (totalsubmissions, username) 167 | 168 | 169 | if __name__=="__main__": 170 | 171 | parser = optparse.OptionParser() 172 | parser.add_option("-o", "--outputpath", default="./", type=str, help="Destination directory to store solutions") 173 | parser.add_option("-p", "--proxy", default = "", type = str, help = "Proxy , ex- http://username:password@proxy:port") 174 | 175 | (options, args) = parser.parse_args() 176 | path_prefix = options.outputpath 177 | path_proxy = options.proxy 178 | 179 | downloadSolutions(getSolutions(path_prefix, path_proxy)) 180 | --------------------------------------------------------------------------------