slicerNetwork.py 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416
  1. import qt
  2. import urllib2
  3. import ssl
  4. import cookielib
  5. import xml.etree.ElementTree as ET
  6. import re
  7. import StringIO
  8. import slicer
  9. import shutil
  10. import distutils
  11. import os
  12. import base64
  13. import json
  14. #see https://gist.github.com/logic/2715756, allow requests to do Put
  15. class MethodRequest(urllib2.Request):
  16. def __init__(self, *args, **kwargs):
  17. if 'method' in kwargs:
  18. self._method = kwargs['method']
  19. del kwargs['method']
  20. else:
  21. self._method = None
  22. return urllib2.Request.__init__(self, *args, **kwargs)
  23. def get_method(self, *args, **kwargs):
  24. if self._method is not None:
  25. return self._method
  26. return urllib2.Request.get_method(self, *args, **kwargs)
  27. class slicerNetwork:
  28. def __init__(self, *args, **kwargs):
  29. pass
  30. class labkeyURIHandler(slicer.vtkURIHandler):
  31. def __init__(self):
  32. slicer.vtkURIHandler.__init__(self)
  33. self.className="labkeyURIHandler"
  34. slicer.mrmlScene.AddURIHandler(self)
  35. try:
  36. self.localCacheDirectory=os.path.join(os.environ["HOME"],"labkeyCache")
  37. except:
  38. #in windows, the variable is called HOMEPATH
  39. fhome=os.environ['HOMEDRIVE']+os.environ['HOMEPATH']
  40. self.localCacheDirectory=os.path.join(fhome,"labkeyCache")
  41. self.mode="http"
  42. def CanHandleURI(self,uri):
  43. print "labkeyURIHandler::CanHandleURI({0})".format(uri)
  44. if uri.find('labkey://')==0:
  45. return 1
  46. return 0
  47. def GetClassName(self):
  48. return self.className
  49. def GetHostName(self):
  50. return self.hostname
  51. def SetHostName(self,host):
  52. self.hostname=host
  53. def SetLocalCahceDirectory(self,dir):
  54. self.localCacheDirectory=dir
  55. def GetLocalCacheDirectory(self):
  56. return self.localCacheDirectory
  57. def GetLabkeyUrl(self):
  58. return self.hostname+"/labkey"
  59. def GetLabkeyWebdavUrl(self):
  60. return self.GetLabkeyUrl()+"/_webdav"
  61. def GetLocalPath(self,source):
  62. debug=False
  63. relativePath=re.sub('labkey://','',source)
  64. sp=os.sep.encode('string-escape')
  65. if debug:
  66. print "Substituting / with {0} in {1}".format(sp,relativePath)
  67. relativePath=re.sub('/',sp,relativePath)
  68. return os.path.join(self.localCacheDirectory,relativePath)
  69. def GetRemotePath(self,source):
  70. return self.GetLabkeyWebdavUrl()+"/"+GetLabkeyPathFromLocalPath(source)
  71. def GetLabkeyPathFromLocalPath(self,f):
  72. #report it with URL separator, forward-slash
  73. if f.find(self.localCacheDirectory)<-1:
  74. print "Localpath misformation. Exiting"
  75. return "NULL"
  76. dest=re.sub(self.localCacheDirectory,'',f)
  77. #leaves /ContextPath/%40files/subdirectory_list/files
  78. #remove first separator
  79. sp=os.sep.encode('string-escape')
  80. if dest[0]==sp:
  81. dest=dest[1:len(dest)]
  82. return re.sub(sp,'/',dest)
  83. def GetLabkeyPathFromRemotePath(self,f):
  84. #used to query labkey stuff, so URL separator is used
  85. f=re.sub('labkey://','',f)
  86. f=re.sub(self.GetLabkeyWebdavUrl(),'',f)
  87. if f[0]=='/':
  88. f=f[1:len(f)]
  89. return f;
  90. def GetFile(self,source):
  91. # check for file in cache. If available, use, if not, download
  92. localPath=self.GetLocalPath(source)
  93. if os.path.isfile(localPath):
  94. return localPath
  95. self.StageFileRead(source,localPath)
  96. return localPath
  97. def StageFileRead(self,source,dest):
  98. print "labkeyURIHandler::StageFileRead({0},{1})".format(source,dest)
  99. labkeyPath=re.sub('labkey://','',source)
  100. remote=self.readFile(self.hostname,labkeyPath)
  101. #make all necessary directories
  102. path=os.path.dirname(dest)
  103. if not os.path.isdir(path):
  104. os.makedirs(path)
  105. local=open(dest,'wb')
  106. #make sure we are at the begining of the file
  107. #check file size
  108. remote.seek(0,2)
  109. sz=remote.tell()
  110. print "Remote size: {0}".format(sz)
  111. remote.seek(0)
  112. shutil.copyfileobj(remote,local)
  113. print "Local size: {0}".format(local.tell())
  114. local.close()
  115. def StageFileWrite(self,source,dest):
  116. print "labkeyURIHandler::StageFileWrite({0},{1}) not implemented yet".format(
  117. source,dest)
  118. def fileTypesAvailable(self):
  119. return ('VolumeFile','SegmentationFile','TransformFile')
  120. #mimic slicer.util.loadNodeFromFile
  121. def loadNodeFromFile(self, filename, filetype, properties={}, returnNode=False):
  122. #this is the only relevant part - file must be downloaded to cache
  123. localPath=self.GetFile(filename)
  124. slicer.util.loadNodeFromFile(localPath,filetype,properties,returnNode)
  125. #remove retrieved file
  126. try:
  127. if not(properties['keepCachedFile']) :
  128. os.remove(localPath)
  129. except:
  130. pass
  131. def loadVolume(self,filename, properties={}, returnNode=False):
  132. filetype = 'VolumeFile'
  133. #redirect to self.loadNodeFromFile first to get the cached file
  134. return self.loadNodeFromFile(filename,filetype, properties,returnNode)
  135. def loadSegmentation(self,filename,properties={},returnNode=False):
  136. filetype='SegmentationFile'
  137. #redirect to self.loadNodeFromFile first to get the cached file
  138. return self.loadNodeFromFile(filename,filetype, properties,returnNode)
  139. #add others if needed
  140. ## setup & initialization routines
  141. def configureSSL(self,cert,key,pwd,cacert):
  142. #do this first
  143. try:
  144. self.ctx=ssl.SSLContext(ssl.PROTOCOL_SSLv23)
  145. self.ctx.load_cert_chain(cert,key,pwd)
  146. self.ctx.verify_mode=ssl.CERT_REQUIRED
  147. self.ctx.load_verify_locations(cacert)
  148. except ssl.SSLError as err:
  149. print " Failed to configure SSL: {0}".format(str(err))
  150. self.mode="https"
  151. def connectRemote(self,serverUrl,uname,pwd):
  152. https_handler=urllib2.HTTPSHandler(context=self.ctx)
  153. self.SetHostName(serverUrl)
  154. #cookie part
  155. cj=cookielib.CookieJar()
  156. cookie_handler=urllib2.HTTPCookieProcessor(cj)
  157. self.opener=urllib2.build_opener(https_handler,cookie_handler)
  158. loginUrl=serverUrl+"/labkey/login/login.post"
  159. r=urllib2.Request(str(loginUrl))
  160. print "Connecting to {0}".format(loginUrl)
  161. r.add_header('ContentType','application/x-www-form-urlencoded')
  162. data="email="+uname+"&password="+pwd
  163. #print "Data: '{0}".format(data)
  164. r.add_data(data)
  165. self.opener.open(r)
  166. def initRemote(self):
  167. if self.mode=="https":
  168. http_handler=urllib2.HTTPSHandler(context=self.ctx)
  169. if self.mode=="http":
  170. http_handler=urllib2.HTTPHandler()
  171. #cookie part
  172. cj=cookielib.CookieJar()
  173. cookie_handler=urllib2.HTTPCookieProcessor(cj)
  174. self.opener=urllib2.build_opener(http_handler,cookie_handler)
  175. def parseConfig(self,fname):
  176. try:
  177. f=open(fname)
  178. except OSError as e:
  179. print "Confgiuration error: OS error({0}): {1}".format(e.errno, e.strerror)
  180. return
  181. dt=json.load(f)
  182. if dt.has_key('SSL'):
  183. self.configureSSL(
  184. dt['SSL']['user'],
  185. dt['SSL']['key'],
  186. dt['SSL']['keyPwd'],
  187. dt['SSL']['ca']
  188. )
  189. self.hostname=dt['host']
  190. self.auth_name=dt['labkey']['user']
  191. self.auth_pass=dt['labkey']['password']
  192. #cj in opener contains the cookies
  193. def get(self,url):
  194. print "GET: {0}".format(url)
  195. print "as {0}".format(self.auth_name)
  196. r=urllib2.Request(url)
  197. base64string = base64.b64encode('%s:%s' % (self.auth_name, self.auth_pass))
  198. r.add_header("Authorization", "Basic %s" % base64string)
  199. return self.opener.open(r)
  200. #f contains json as a return value
  201. def post(self,url,data):
  202. r=urllib2.Request(url)
  203. #makes it a post
  204. r.add_data(data)
  205. r.add_header("Content-Type","application/json")
  206. base64string = base64.b64encode('%s:%s' % (self.auth_name, self.auth_pass))
  207. r.add_header("Authorization", "Basic %s" % base64string)
  208. print "{}: {}".format(r.get_method(),r.get_full_url())
  209. print "data: {}".format(r.get_data())
  210. print "Content-Type: {}".format(r.get_header('Content-Type'))
  211. return self.opener.open(r)
  212. #f contains json as a return value
  213. def put(self,url,data):
  214. print "PUT: {0}".format(url)
  215. r=MethodRequest(url,method="PUT")
  216. #makes it a post
  217. r.add_data(data)
  218. r.add_header("content-type","application/octet-stream")
  219. base64string = base64.b64encode('%s:%s' % (self.auth_name, self.auth_pass))
  220. r.add_header("Authorization", "Basic %s" % base64string)
  221. return self.opener.open(r)
  222. #f contains json as a return value
  223. def remoteDirExists(self,url):
  224. status,dirs=self.listRemoteDir(url);
  225. return status
  226. def mkdir(self,remoteDir):
  227. if self.remoteDirExists(remoteDir):
  228. return False
  229. r=MethodRequest(remoteDir,method="MKCOL")
  230. base64string = base64.b64encode('%s:%s' % (self.auth_name, self.auth_pass))
  231. r.add_header("Authorization", "Basic %s" % base64string)
  232. try:
  233. f=self.opener.open(r)
  234. except:
  235. print "Error: Failed MKCOL {}".format(remoteDir)
  236. return False
  237. return True
  238. def mkdirs(self,remoteDir):
  239. labkeyPath=self.GetLabkeyPathFromRemotePath(remoteDir)
  240. s=0
  241. while True:
  242. s1=labkeyPath.find('/',s)
  243. if s1<0:
  244. break
  245. path=labkeyPath[0:s1]
  246. remotePath=self.GetLabkeyWebdavUrl()+'/'+path
  247. dirExists=self.remoteDirExists(remotePath)
  248. if not dirExists:
  249. if not self.mkdir(remotePath):
  250. return False
  251. s=s1+1
  252. return self.mkdir(remoteDir)
  253. def rmdir(self,remoteDir):
  254. if not self.remoteDirExists(remoteDir):
  255. return True
  256. r=MethodRequest(remoteDir,method="DELETE")
  257. base64string = base64.b64encode('%s:%s' % (self.auth_name, self.auth_pass))
  258. r.add_header("Authorization", "Basic %s" % base64string)
  259. try:
  260. f=self.opener.open(r)
  261. except:
  262. print "Error: Failed DELETE {}".format(remoteDir)
  263. return False
  264. return True
  265. def listDir(self,dir):
  266. print "Listing for {0}".format(dir)
  267. dirUrl=self.GetLabkeyWebdavUrl()+"/"+dir
  268. status,dirs=self.listRemoteDir(dirUrl)
  269. return dirs
  270. def listRemoteDir(self,dirUrl):
  271. r=MethodRequest(dirUrl,method="PROPFIND")
  272. PROPFIND=u"""<?xml version="1.0" encoding="utf-8"?>\n
  273. <propfind xmlns="DAV:">\n
  274. <prop>\n
  275. <getetag/>\n
  276. </prop>\n
  277. </propfind>"""
  278. r.add_header('content-type','text/xml; charset="utf-8"')
  279. r.add_header('content-length',str(len(PROPFIND)))
  280. r.add_header('Depth','1')
  281. r.add_data(PROPFIND)
  282. base64string = base64.b64encode('%s:%s' % (self.auth_name, self.auth_pass))
  283. r.add_header("Authorization", "Basic %s" % base64string)
  284. print "PROPFIND: {0}".format(dirUrl)
  285. dirs=[]
  286. try:
  287. f=self.opener.open(r)
  288. except:
  289. return False,dirs
  290. tree=ET.XML(f.read())
  291. rps=tree.findall('{DAV:}response')
  292. for r in rps:
  293. hr=r.find('{DAV:}href')
  294. dirent=hr.text
  295. dirent=re.sub('/labkey/_webdav/','',dirent)
  296. dirs.append(dirent)
  297. del dirs[0]
  298. return True,dirs
  299. def toRelativePath(self,dirs):
  300. flist1=[]
  301. for d in dirs:
  302. if d[-1]=='/':
  303. d=d[:-1]
  304. if d.rfind('/')>-1:
  305. d=d[d.rfind('/')+1:]
  306. flist1.append(d)
  307. return flist1
  308. def readFile(self, serverUrl, path):
  309. dirUrl=serverUrl+"/labkey/_webdav"+"/"+path
  310. f=self.get(dirUrl)
  311. return StringIO.StringIO(f.read())
  312. def uploadFile(self,localPath):
  313. #get upstream directories sorted out
  314. labkeyPath=self.GetLabkeyPathFromLocalPath(localPath)
  315. if labkeyPath=="NULL":
  316. errorCode="Failed to upload {}. Potential incorrect location"
  317. errorCode+=". Should be in labkeyCache!"
  318. print errorCode.format(localPath)
  319. return False
  320. labkeyDir=labkeyPath[0:labkeyPath.rfind('/')]
  321. remoteDir=self.GetLabkeyWebdavUrl()+'/'+labkeyDir
  322. if not self.remoteDirExists(remoteDir):
  323. if not self.mkdirs(remoteDir):
  324. errorCode="UploadFile: Could not create directory {}"
  325. print errorCode.format(remoteDir)
  326. return False
  327. #make an URL request
  328. with open(localPath, 'r') as f:
  329. data=f.read()
  330. remotePath=self.GetLabkeyWebdavUrl()+'/'+labkeyPath
  331. self.put(remotePath,data)
  332. def loadDir(self, path):
  333. #dirURL=serverUrl+"/labkey/_webdav/"+path
  334. files=self.listDir(path)
  335. fdir="NONE"
  336. for f in files:
  337. #returns local path
  338. try:
  339. fdir=os.path.dirname(self.GetFile(f))
  340. except:
  341. #fails if there is a subdirectory; go recursively
  342. self.readDir(f)
  343. return fdir
  344. def loadDataset(self,project,dataset):
  345. url=self.GetLabkeyUrl()+'/'+project
  346. url+='/query-selectRows.api?schemaName=study&query.queryName='+dataset
  347. return json.load(self.get(url))
  348. def modifyDataset(self,method,project,dataset,rows):
  349. #method can be insert or update
  350. data={}
  351. data['schemaName']='study'
  352. data['queryName']=dataset
  353. data['rows']=rows
  354. url=self.GetLabkeyUrl()+'/'+project
  355. url+='/query-'+method+'Rows.api?'
  356. return self.post(url,json.dumps(data))