diff --git a/doc/projects.inc b/doc/projects.inc
index e7b0df51e6..7007511539 100644
--- a/doc/projects.inc
+++ b/doc/projects.inc
@@ -379,7 +379,7 @@ $astro_phys_chem = array(
// ),
array(
"LHC@home",
- "http://lhcathomeclassic.cern.ch/sixtrack/",
+ "http://lhcathome.cern.ch/",
tra("CERN (European Organization for Nuclear Research)"),
tra("Physics"),
tra("The Large Hadron Collider (LHC) is a particle accelerator at CERN, the European Organization for Nuclear Research, the world's largest particle physics laboratory. It is the most powerful instrument ever built to investigate on particles proprieties. LHC@home runs simulations to improve the design of LHC and its detectors."),
diff --git a/gl.py b/gl.py
new file mode 100644
index 0000000000..d9eb531a39
--- /dev/null
+++ b/gl.py
@@ -0,0 +1,16 @@
+# This file is part of BOINC.
+# http://boinc.berkeley.edu
+# Copyright (C) 2016 University of California
+#
+# BOINC is free software; you can redistribute it and/or modify it
+# under the terms of the GNU Lesser General Public License
+# as published by the Free Software Foundation,
+# either version 3 of the License, or (at your option) any later version.
+#
+# BOINC is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+# See the GNU Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with BOINC. If not, see .
diff --git a/lib/submit_api.py b/lib/submit_api.py
new file mode 100644
index 0000000000..884e6df084
--- /dev/null
+++ b/lib/submit_api.py
@@ -0,0 +1,183 @@
+# Python bindings of remote job submission and file management APIs
+
+import urllib
+import copy
+import xml.etree.ElementTree as ET
+
+# represents an input file
+#
+class FILE_DESC:
+ def __init__(self):
+ return
+ def to_xml(self):
+ xml = ('\n'
+ '%s\n'
+ ) %(self.mode)
+ if self.mode == 'remote':
+ xml += ('%s\n'
+ '%f\n'
+ '%s\n'
+ ) %(self.url, self.nbytes, self.md5)
+ else:
+ xml += '\n' %(self.source)
+ xml += '\n'
+ return xml
+
+# represents a job
+#
+class JOB_DESC:
+ def __init__(self):
+ return
+ def to_xml(self):
+ xml = ('\n'
+ '%f\n'
+ '%s\n'
+ ) %(self.rsc_fpops_est, self.command_line)
+ for file in self.files:
+ xml += file.to_xml()
+ xml += '\n';
+ return xml
+
+# represents a batch description for submit() or estimate()
+#
+class BATCH_DESC:
+ def __init__(self):
+ return
+
+ # convert to XML
+ #
+ def to_xml(self, op):
+ xml = ('<%s>\n'
+ '%s\n'
+ '\n'
+ '%s\n'
+ '%s\n'
+ ) %(op, self.authenticator, self.app_name, self.batch_name)
+ for job in self.jobs:
+ xml += job.to_xml()
+ xml += '\n%s>\n' %(op)
+ return xml
+
+# a generic request
+#
+class REQUEST:
+ def __init__(self):
+ return
+
+def do_http_post(req, project_url):
+ url = project_url + 'submit_rpc_handler.php'
+ params = urllib.urlencode({'request': req})
+ f = urllib.urlopen(url, params)
+ reply = f.read()
+ print reply
+ r = ET.fromstring(reply)
+ return r[0]
+
+def estimate_batch(req):
+ return do_http_post(req.to_xml('estimate_batch'), req.project)
+
+def submit_batch(req):
+ return do_http_post(req.to_xml('submit_batch'), req.project)
+
+def query_batches(req):
+ req_xml = ('\n'
+ '%s\n'
+ '%d\n'
+ '\n'
+ ) %(req.authenticator, 1 if req.get_cpu_time else 0)
+ return do_http_post(req_xml, req.project)
+
+def query_batch(req):
+ req_xml = ('\n'
+ '%s\n'
+ '%s\n'
+ '%d\n'
+ '\n'
+ ) %(req.authenticator, req.batch_id, 1 if req.get_cpu_time else 0)
+ return do_http_post(req_xml, req.project)
+
+def query_job(req):
+ req_xml = ('\n'
+ '%s\n'
+ '%s\n'
+ '\n'
+ ) %(req.authenticator, req.job_id)
+ return do_http_post(req_xml, req.project)
+
+def abort_batch(req):
+ req_xml = ('\n'
+ '%s\n'
+ '%s\n'
+ '\n'
+ ) %(req.authenticator, req.batch_id)
+ return do_http_post(req_xml, req.project)
+
+def get_output_file(req):
+ auth_str = md5.new(req.authenticator+req.instance_name).digest()
+ name = req.instance_name
+ file_num = req.file_num
+ return project_url+"/get_output.php?cmd=result_file&result_name=%s&file_num=%s&auth_str=%s"%(name, file_num, auth_str);
+
+def get_output_files(req):
+ auth_str = md5.new(req.authenticator+req.batch_id).digest()
+ return project_url+"/get_output.php?cmd=batch_files&batch_id=%s&auth_str=%s"%(req.batch_id, auth_str)
+
+
+def retire_batch(req):
+ req_xml = ('\n'
+ '%s\n'
+ '%s\n'
+ '\n'
+ ) %(req.authenticator, req.batch_id)
+ return do_http_post(req_xml, project_url)
+
+def query_files(req):
+ return do_http_post(req_xml, project_url)
+
+def upload_files(req):
+ return do_http_post(req_xml, project_url)
+
+def test_estimate():
+ file = FILE_DESC()
+ file.mode = 'remote'
+ file.url = 'http://isaac.ssl.berkeley.edu/validate_logic.txt'
+ file.md5 = "eec5a142cea5202c9ab2e4575a8aaaa7"
+ file.nbytes = 4250;
+
+ job = JOB_DESC()
+ job.files = [file]
+
+ batch = BATCH_DESC()
+ batch.project = 'http://isaac.ssl.berkeley.edu/test/'
+ batch.authenticator = "157f96a018b0b2f2b466e2ce3c7f54db"
+ batch.app_name = "uppercase"
+ batch.batch_name = "blah"
+ batch.jobs = []
+
+ for i in range(3):
+ job.rsc_fpops_est = i*1e9
+ job.command_line = '-i %s' %(i)
+ batch.jobs.append(copy.copy(job))
+
+ #print batch.to_xml("submit")
+ r = estimate_batch(batch)
+ print ET.tostring(r)
+
+def test_query_batches():
+ req = REQUEST()
+ req.project = 'http://isaac.ssl.berkeley.edu/test/'
+ req.authenticator = "157f96a018b0b2f2b466e2ce3c7f54db"
+ req.get_cpu_time = True
+ r = query_batches(req)
+ print ET.tostring(r)
+
+def test_query_batch():
+ req = REQUEST()
+ req.project = 'http://isaac.ssl.berkeley.edu/test/'
+ req.authenticator = "157f96a018b0b2f2b466e2ce3c7f54db"
+ req.batch_id = 101
+ req.get_cpu_time = True
+ r = query_batch(req)
+ print ET.tostring(r)
+
+test_query_batch()
diff --git a/tools/submit_api_test.py b/tools/submit_api_test.py
new file mode 100644
index 0000000000..755e0f1407
--- /dev/null
+++ b/tools/submit_api_test.py
@@ -0,0 +1,79 @@
+# This file is part of BOINC.
+# http://boinc.berkeley.edu
+# Copyright (C) 2016 University of California
+#
+# BOINC is free software; you can redistribute it and/or modify it
+# under the terms of the GNU Lesser General Public License
+# as published by the Free Software Foundation,
+# either version 3 of the License, or (at your option) any later version.
+#
+# BOINC is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+# See the GNU Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with BOINC. If not, see .
+
+# test code for submit_api.py
+
+from submit_api import *
+
+# read auth from a file so we don't have to including it here
+#
+def get_auth():
+ with open("test_auth", "r") as f:
+ return (f.readline()).strip()
+
+def make_batch():
+ file = FILE_DESC()
+ file.mode = 'remote'
+ file.url = 'http://isaac.ssl.berkeley.edu/validate_logic.txt'
+ file.md5 = "eec5a142cea5202c9ab2e4575a8aaaa7"
+ file.nbytes = 4250
+
+ job = JOB_DESC()
+ job.files = [file]
+
+ batch = BATCH_DESC()
+ batch.project = 'http://isaac.ssl.berkeley.edu/test/'
+ batch.authenticator = get_auth()
+ batch.app_name = "uppercase"
+ batch.batch_name = "blah"
+ batch.jobs = []
+
+ for i in range(3):
+ job.rsc_fpops_est = i*1e9
+ job.command_line = '-i %s' %(i)
+ batch.jobs.append(copy.copy(job))
+
+ return batch
+
+def test_estimate():
+ batch = make_batch()
+ #print batch.to_xml("submit")
+ r = estimate_batch(batch)
+ #print ET.tostring(r)
+ if r.tag == 'error':
+ print 'error: ', r.find('error_msg').text
+ else:
+ print 'estimated time: ', r.text, ' seconds'
+
+def test_query_batches():
+ req = REQUEST()
+ req.project = 'http://isaac.ssl.berkeley.edu/test/'
+ req.authenticator = get_auth()
+ req.get_cpu_time = True
+ r = query_batches(req)
+ print ET.tostring(r)
+
+def test_query_batch():
+ req = REQUEST()
+ req.project = 'http://isaac.ssl.berkeley.edu/test/'
+ req.authenticator = get_auth()
+ req.batch_id = 101
+ req.get_cpu_time = True
+ r = query_batch(req)
+ print ET.tostring(r)
+
+test_estimate()