slicerNetwork.py 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477
  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. #add csrf
  201. csrf=self.getCSRF()
  202. r.add_header("X-LABKEY-CSRF",csrf)
  203. base64string = base64.b64encode('%s:%s' % (self.auth_name, self.auth_pass))
  204. r.add_header("Authorization", "Basic %s" % base64string)
  205. print "{}: {}".format(r.get_method(),r.get_full_url())
  206. print "data: {}".format(r.get_data())
  207. print "Content-Type: {}".format(r.get_header('Content-Type'))
  208. return self.opener.open(r)
  209. #f contains json as a return value
  210. def put(self,url,data):
  211. print "PUT: {0}".format(url)
  212. r=MethodRequest(url.encode('utf-8'),method="PUT")
  213. #makes it a post
  214. r.add_data(data)
  215. r.add_header("content-type","application/octet-stream")
  216. base64string = base64.b64encode('%s:%s' % (self.auth_name, self.auth_pass))
  217. r.add_header("Authorization", "Basic %s" % base64string)
  218. return self.opener.open(r)
  219. #f contains json as a return value
  220. def getCSRF(self):
  221. url=self.GetLabkeyUrl()+'/login/whoAmI.view'
  222. jsonData=json.load(self.get(url))
  223. return jsonData["CSRF"]
  224. def remoteDirExists(self,url):
  225. status,dirs=self.listRemoteDir(url);
  226. return status
  227. def mkdir(self,remoteDir):
  228. if self.remoteDirExists(remoteDir):
  229. return False
  230. r=MethodRequest(remoteDir,method="MKCOL")
  231. base64string = base64.b64encode('%s:%s' % (self.auth_name, self.auth_pass))
  232. r.add_header("Authorization", "Basic %s" % base64string)
  233. try:
  234. f=self.opener.open(r)
  235. except:
  236. print "Error: Failed MKCOL {}".format(remoteDir)
  237. return False
  238. return True
  239. def mkdirs(self,remoteDir):
  240. labkeyPath=self.GetLabkeyPathFromRemotePath(remoteDir)
  241. s=0
  242. while True:
  243. s1=labkeyPath.find('/',s)
  244. if s1<0:
  245. break
  246. path=labkeyPath[0:s1]
  247. remotePath=self.GetLabkeyWebdavUrl()+'/'+path
  248. dirExists=self.remoteDirExists(remotePath)
  249. if not dirExists:
  250. if not self.mkdir(remotePath):
  251. return False
  252. s=s1+1
  253. return self.mkdir(remoteDir)
  254. def rmdir(self,remoteDir):
  255. if not self.remoteDirExists(remoteDir):
  256. return True
  257. r=MethodRequest(remoteDir,method="DELETE")
  258. base64string = base64.b64encode('%s:%s' % (self.auth_name, self.auth_pass))
  259. r.add_header("Authorization", "Basic %s" % base64string)
  260. try:
  261. f=self.opener.open(r)
  262. except:
  263. print "Error: Failed DELETE {}".format(remoteDir)
  264. return False
  265. return True
  266. def listDir(self,dir):
  267. print "Listing for {0}".format(dir)
  268. dirUrl=self.GetLabkeyWebdavUrl()+"/"+dir
  269. status,dirs=self.listRemoteDir(dirUrl)
  270. return dirs
  271. def isDir(self, remotePath):
  272. #print "isDir: {}".format(remotePath)
  273. r=MethodRequest(remotePath,method="PROPFIND")
  274. PROPFIND=u"""<?xml version="1.0" encoding="utf-8"?>\n
  275. <a:propfind xmlns:a="DAV:">\n
  276. <a:prop>\n
  277. <a:resourcetype/>\n
  278. </a:prop>\n
  279. </a:propfind>"""
  280. r.add_header('content-type','text/xml; charset="utf-8"')
  281. r.add_header('content-length',str(len(PROPFIND)))
  282. r.add_header('Depth','0')
  283. r.add_data(PROPFIND)
  284. base64string = base64.b64encode('%s:%s' % (self.auth_name, self.auth_pass))
  285. r.add_header("Authorization", "Basic %s" % base64string)
  286. print "PROPFIND: {0}".format(remotePath)
  287. try:
  288. f=self.opener.open(r)
  289. except:
  290. return False
  291. tree=ET.XML(f.read())
  292. try:
  293. rps=tree.find('{DAV:}response').find('{DAV:}propstat')
  294. rps=rps.find('{DAV:}prop')
  295. rps=rps.find('{DAV:}resourcetype').find('{DAV:}collection')
  296. if rps != None:
  297. return True
  298. else:
  299. return False
  300. except:
  301. return False
  302. def listRemoteDir(self,dirUrl):
  303. r=MethodRequest(dirUrl,method="PROPFIND")
  304. PROPFIND=u"""<?xml version="1.0" encoding="utf-8"?>\n
  305. <propfind xmlns="DAV:">\n
  306. <prop>\n
  307. <getetag/>\n
  308. </prop>\n
  309. </propfind>"""
  310. r.add_header('content-type','text/xml; charset="utf-8"')
  311. r.add_header('content-length',str(len(PROPFIND)))
  312. r.add_header('Depth','1')
  313. r.add_data(PROPFIND)
  314. base64string = base64.b64encode('%s:%s' % (self.auth_name, self.auth_pass))
  315. r.add_header("Authorization", "Basic %s" % base64string)
  316. print "PROPFIND: {0}".format(dirUrl)
  317. dirs=[]
  318. try:
  319. f=self.opener.open(r)
  320. except:
  321. return False,dirs
  322. tree=ET.XML(f.read())
  323. rps=tree.findall('{DAV:}response')
  324. for r in rps:
  325. hr=r.find('{DAV:}href')
  326. dirent=hr.text
  327. dirent=re.sub('/labkey/_webdav/','',dirent)
  328. dirs.append(dirent)
  329. del dirs[0]
  330. return True,dirs
  331. def toRelativePath(self,dirs):
  332. flist1=[]
  333. for d in dirs:
  334. if d[-1]=='/':
  335. d=d[:-1]
  336. if d.rfind('/')>-1:
  337. d=d[d.rfind('/')+1:]
  338. flist1.append(d)
  339. return flist1
  340. def readFile(self, serverUrl, path):
  341. dirUrl=serverUrl+"/labkey/_webdav"+"/"+path
  342. f=self.get(dirUrl)
  343. return StringIO.StringIO(f.read())
  344. def uploadFile(self,localPath):
  345. #get upstream directories sorted out
  346. labkeyPath=self.GetLabkeyPathFromLocalPath(localPath)
  347. if labkeyPath=="NULL":
  348. errorCode="Failed to upload {}. Potential incorrect location"
  349. errorCode+=". Should be in labkeyCache!"
  350. print errorCode.format(localPath)
  351. return False
  352. labkeyDir=labkeyPath[0:labkeyPath.rfind('/')]
  353. remoteDir=self.GetLabkeyWebdavUrl()+'/'+labkeyDir
  354. if not self.remoteDirExists(remoteDir):
  355. if not self.mkdirs(remoteDir):
  356. errorCode="UploadFile: Could not create directory {}"
  357. print errorCode.format(remoteDir)
  358. return False
  359. #make an URL request
  360. with open(localPath, 'r') as f:
  361. data=f.read()
  362. remotePath=self.GetLabkeyWebdavUrl()+'/'+labkeyPath
  363. self.put(remotePath,data)
  364. def copyFileToRemote(self,localPath, remotePath):
  365. #get upstream directories sorted out
  366. labkeyDir=os.path.dirname(remotePath)
  367. if not self.remoteDirExists(labkeyDir):
  368. if not self.mkdirs(labkeyDir):
  369. errorCode="UploadFile: Could not create directory {}"
  370. print errorCode.format(labkeyDir)
  371. return False
  372. #make an URL request
  373. if (self.isDir(remotePath)):
  374. remotePath=remotePath+'/'+os.path.basename(localPath)
  375. with open(localPath, 'r') as f:
  376. data=f.read()
  377. self.put(remotePath,data)
  378. def loadDir(self, path):
  379. #dirURL=serverUrl+"/labkey/_webdav/"+path
  380. files=self.listDir(path)
  381. fdir="NONE"
  382. for f in files:
  383. #returns local path
  384. try:
  385. fdir=os.path.dirname(self.GetFile(f))
  386. except:
  387. #fails if there is a subdirectory; go recursively
  388. print "self.readDir(f) not implemented"
  389. return fdir
  390. def loadDataset(self,project,dataset):
  391. url=self.GetLabkeyUrl()+'/'+project
  392. url+='/query-selectRows.api?schemaName=study&query.queryName='+dataset
  393. return json.load(self.get(url))
  394. def filterDataset(self,project,dataset,variable,value,oper="eq"):
  395. debug=True
  396. url=self.GetLabkeyUrl()+'/'+project
  397. url+='/query-selectRows.api?schemaName=study&query.queryName='+dataset
  398. url+="&query."+variable+"~"+oper+"="+value
  399. if debug:
  400. print "Sending {}".format(url)
  401. return json.load(self.get(url))
  402. def modifyDataset(self,method,project,dataset,rows):
  403. #method can be insert or update
  404. data={}
  405. data['schemaName']='study'
  406. data['queryName']=dataset
  407. data['rows']=rows
  408. url=self.GetLabkeyUrl()+'/'+project
  409. url+='/query-'+method+'Rows.api?'
  410. return self.post(url,json.dumps(data))