slicerNetwork.py 16 KB

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