slicerNetwork.py 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658
  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. fhome=os.environ["HOME"]
  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.configDir=os.path.join(fhome,".labkey")
  44. self.mode="http"
  45. #try initializing network from default config file, if found
  46. self.initFromConfig()
  47. def CanHandleURI(self,uri):
  48. print("labkeyURIHandler::CanHandleURI({0})").format(uri)
  49. if uri.find('labkey://')==0:
  50. return 1
  51. return 0
  52. def GetClassName(self):
  53. return self.className
  54. def GetHostName(self):
  55. return self.hostname
  56. def SetHostName(self,host):
  57. self.hostname=host
  58. def SetLocalCahceDirectory(self,dir):
  59. self.localCacheDirectory=dir
  60. def GetLocalCacheDirectory(self):
  61. return self.localCacheDirectory
  62. def GetLabkeyUrl(self):
  63. return self.hostname+"/labkey"
  64. def GetLabkeyWebdavUrl(self):
  65. return self.GetLabkeyUrl()+"/_webdav"
  66. #configuration part
  67. def configureSSL(self,cert,key,pwd,cacert):
  68. #do this first
  69. try:
  70. self.ctx=ssl.SSLContext(ssl.PROTOCOL_SSLv23)
  71. self.ctx.load_cert_chain(cert,keyfile=key,password=pwd)
  72. self.ctx.verify_mode=ssl.CERT_REQUIRED
  73. self.ctx.load_verify_locations(cacert)
  74. except ssl.SSLError as err:
  75. print(" Failed to configure SSL: {0}").format(str(err))
  76. self.mode="https"
  77. def initRemote(self):
  78. if self.mode=="https":
  79. http_handler=urllib2.HTTPSHandler(context=self.ctx)
  80. if self.mode=="http":
  81. http_handler=urllib2.HTTPHandler()
  82. #cookie part
  83. cj=cookielib.CookieJar()
  84. cookie_handler=urllib2.HTTPCookieProcessor(cj)
  85. self.opener=urllib2.build_opener(http_handler,cookie_handler)
  86. def initFromConfig(self):
  87. path=os.path.join(self.configDir,"Remote.json")
  88. try:
  89. self.parseConfig(path)
  90. except OSError:
  91. return
  92. self.initRemote()
  93. def parseConfig(self,fname,parent=None):
  94. try:
  95. f=open(fname)
  96. except OSError as e:
  97. print("Confgiuration error: OS error({0}): {1}").format(e.errno, e.strerror)
  98. raise
  99. dt=json.load(f)
  100. self.mode="http"
  101. if dt.has_key('SSL'):
  102. self.configureSSL(
  103. dt['SSL']['user'],
  104. dt['SSL']['key'],
  105. dt['SSL']['keyPwd'],
  106. dt['SSL']['ca']
  107. )
  108. if not parent==None:
  109. parent.userCertButton.setText(dt['SSL']['user'])
  110. parent.caCertButton.setText(dt['SSL']['ca'])
  111. parent.privateKeyButton.setText(dt['SSL']['key'])
  112. parent.pwd=dt['SSL']['keyPwd']
  113. self.hostname=dt['host']
  114. self.auth_name=dt['labkey']['user']
  115. self.auth_pass=dt['labkey']['password']
  116. if not parent==None:
  117. parent.serverURL.setText(dt['host'])
  118. parent.authName.setText(dt['labkey']['user'])
  119. parent.authPass.setText(dt['labkey']['password'])
  120. #path convention
  121. #localPath is a path on local filesystem. It means it has to adhere to OS
  122. #policy, especially in separators
  123. #relativePath is just the portion of the path that is equal both locally
  124. #and remotely. In relativePath, separator is according to http convention,
  125. #which equals separator in osx and *nix
  126. #labkeyPath or remotePath is the full URL to the resource,
  127. #including http-like header and all intermediate portions
  128. #the following functions convert between different notations
  129. #was GetLocalPath
  130. def GetLocalPathFromRelativePath(self,relativePath):
  131. debug=False
  132. relativePath=re.sub('labkey://','',relativePath)
  133. sp=os.sep.encode('string-escape')
  134. if debug:
  135. print("Substituting / with {0} in {1}").format(sp,relativePath)
  136. relativePath=re.sub('/',sp,relativePath)
  137. return os.path.join(self.GetLocalCacheDirectory(),relativePath)
  138. #was GetRemotePath
  139. def GetLabkeyPathFromRelativePath(self,source):
  140. return self.GetLabkeyWebdavUrl()+"/"+source
  141. #was GetLabkeyPathFromLocalPath
  142. def GetRelativePathFromLocalPath(self,f):
  143. debug=False
  144. #report it with URL separator, forward-slash
  145. if f.find(self.localCacheDirectory)<-1:
  146. print("Localpath misformation. Exiting")
  147. return "NULL"
  148. f0=re.sub('\\\\','\\\\\\\\',self.localCacheDirectory)
  149. relativePath=re.sub(re.compile(f0),'',f)
  150. if debug:
  151. print("[SEP] Relative path {}").format(relativePath)
  152. #leaves /ContextPath/%40files/subdirectory_list/files
  153. #remove first separator
  154. sp=os.path.sep
  155. f1=re.sub('\\\\','\\\\\\\\',sp)
  156. if relativePath[0]==sp:
  157. relativePath=relativePath[1:len(relativePath)]
  158. if debug:
  159. print("[SLASH] Relative path {}").format(relativePath)
  160. return re.sub(f1,'/',relativePath)
  161. #was GetLabkeyPathFromRemotePath
  162. def GetRelativePathFromLabkeyPath(self,f):
  163. #used to query labkey stuff, so URL separator is used
  164. #in old conventions, labkey://was used to tag a labkeyPath
  165. f=re.sub('labkey://','',f)
  166. f=re.sub(self.GetLabkeyWebdavUrl(),'',f)
  167. if f[0]=='/':
  168. f=f[1:len(f)]
  169. return f;
  170. #standard HTTP
  171. def get(self,url):
  172. debug=False
  173. if debug:
  174. print("GET: {0}").format(url)
  175. print("as {0}").format(self.auth_name)
  176. r=urllib2.Request(url)
  177. base64string = base64.b64encode('%s:%s' % (self.auth_name, self.auth_pass))
  178. r.add_header("Authorization", "Basic %s" % base64string)
  179. try:
  180. return self.opener.open(r)
  181. #f contains json as a return value
  182. except urllib2.HTTPError as e:
  183. print e.code
  184. print e.read()
  185. return e
  186. except urllib2.URLError as e:
  187. print("Handling URLError")
  188. print e
  189. return None
  190. #a HEAD request
  191. def head(self,url):
  192. debug=False
  193. if debug:
  194. print("HEAD: {0}").format(url)
  195. print("as {0}").format(self.auth_name)
  196. r=MethodRequest(url.encode('utf-8'),method="HEAD")
  197. base64string = base64.b64encode('%s:%s' % (self.auth_name, self.auth_pass))
  198. r.add_header("Authorization", "Basic %s" % base64string)
  199. try:
  200. return self.opener.open(r)
  201. #f contains json as a return value
  202. except urllib2.HTTPError as e:
  203. print e.code
  204. print e.read()
  205. return e
  206. def post(self,url,data):
  207. debug=False
  208. r=urllib2.Request(url)
  209. #makes it a post
  210. r.add_data(data)
  211. r.add_header("Content-Type","application/json")
  212. #add csrf
  213. csrf=self.getCSRF()
  214. r.add_header("X-LABKEY-CSRF",csrf)
  215. base64string = base64.b64encode('%s:%s' % (self.auth_name, self.auth_pass))
  216. r.add_header("Authorization", "Basic %s" % base64string)
  217. if debug:
  218. print("{}: {}").format(r.get_method(),r.get_full_url())
  219. print("data: {}").format(r.get_data())
  220. print("Content-Type: {}").format(r.get_header('Content-Type'))
  221. try:
  222. return self.opener.open(r)
  223. except urllib2.HTTPError as e:
  224. print e.code
  225. print e.read()
  226. return e
  227. #f contains json as a return value
  228. def put(self,url,data):
  229. debug=False
  230. if debug:
  231. print("PUT: {}").format(url)
  232. r=MethodRequest(url.encode('utf-8'),method="PUT")
  233. #makes it a post
  234. r.add_data(data)
  235. print("PUT: data size: {}").format(len(data))
  236. r.add_header("content-type","application/octet-stream")
  237. base64string = base64.b64encode('%s:%s' % (self.auth_name, self.auth_pass))
  238. r.add_header("Authorization", "Basic %s" % base64string)
  239. return self.opener.open(r)
  240. #f contains json as a return value
  241. def getCSRF(self):
  242. url=self.GetLabkeyUrl()+'/login/whoAmI.view'
  243. try:
  244. jsonData=json.load(self.get(url))
  245. except AttributeError:
  246. print("Failed")
  247. return None
  248. return jsonData["CSRF"]
  249. #file manipulation routiens
  250. #was GetFile
  251. def DownloadFileToCache(self,relativePath):
  252. debug=False
  253. # check for file in cache. If available, use, if not, download
  254. localPath=self.GetLocalPathFromRelativePath(relativePath)
  255. if os.path.isfile(localPath):
  256. if debug:
  257. print("Returning localFile {}".format(localPath))
  258. return localPath
  259. if debug:
  260. print("labkeyURIHandler::DownloadFileToCache({0}->{1})").format(relativePath,localPath)
  261. #make all necessary directories LOCALLY
  262. path=os.path.dirname(localPath)
  263. if debug:
  264. print("Target directory: '{}''".format(path))
  265. if not os.path.isdir(path):
  266. os.makedirs(path)
  267. localBuffer=open(localPath,'wb')
  268. #make sure we are at the begining of the file
  269. #labkeyPath=self.GetLabkeyPathFromRelativePath(relativePath)
  270. remoteBuffer=self.readFileToBuffer(relativePath)
  271. #check file size
  272. if debug:
  273. remoteBuffer.seek(0,2)
  274. sz=remoteBuffer.tell()
  275. print("Remote size: {0}").format(sz)
  276. remoteBuffer.seek(0)
  277. shutil.copyfileobj(remoteBuffer,localBuffer)
  278. if debug:
  279. print("Local size: {0}").format(localBuffer.tell())
  280. localBuffer.close()
  281. return localPath
  282. def fileTypesAvailable(self):
  283. return ('VolumeFile','SegmentationFile','TransformFile')
  284. #mimic slicer.util.loadNodeFromFile
  285. def loadNode(self, relativeName, filetype, properties={},returnNode=False, keepCached=True):
  286. #this is the only relevant part - file must be downloaded to cache
  287. #labkeyName is just the relative part (from labkey onwards)
  288. localPath=self.DownloadFileToCache(relativeName)
  289. if not returnNode:
  290. slicer.util.loadNodeFromFile(localPath,filetype,properties,returnNode=False)
  291. if not keepCached:
  292. os.remove(localPath)
  293. return
  294. ok,node=slicer.util.loadNodeFromFile(localPath,filetype,properties,returnNode=True)
  295. if ok:
  296. if not keepCached:
  297. os.remove(localPath)
  298. return node
  299. os.remove(localPath)
  300. return None
  301. # #remove retrieved file
  302. def storeNode(self,node,project,dir=None):
  303. removeFile=True
  304. relativePath=project+'/%40files'
  305. if not dir==None:
  306. relativePath+='/'+dir
  307. labkeyPath=self.GetLabkeyPathFromRelativePath(relativePath)
  308. print ("Remote: {}").format(labkeyPath)
  309. #checks if exists
  310. self.mkdir(labkeyPath)
  311. localPath=self.GetLocalPathFromRelativePath(relativePath)
  312. localPath.replace('/',os.path.sep)
  313. nodeName=node.GetName()
  314. suffix=".nrrd"
  315. if node.__class__.__name__=="vtkMRMLDoubleArrayNode":
  316. suffix=".mcsv"
  317. if node.__class__.__name__=="vtkMRMLTransformNode":
  318. suffix=".h5"
  319. fileName=nodeName+suffix
  320. file=os.path.join(localPath,fileName)
  321. slicer.util.saveNode(node,file)
  322. print("Stored to: {}").format(file)
  323. f=open(file,"rb")
  324. f.seek(0,2)
  325. sz=f.tell()
  326. print("File size in memory: {0}").format(sz)
  327. f.seek(0)
  328. localBuffer=f.read()
  329. print("Local buffer size: {}").format(len(localBuffer))
  330. remoteFile=labkeyPath+'/'+fileName
  331. resp=self.put(remoteFile,localBuffer)
  332. print(resp.read())
  333. f.close()
  334. if removeFile:
  335. os.remove(file)
  336. def remoteDirExists(self,url):
  337. status,dirs=self.listRemoteDir(url);
  338. return status
  339. def mkdir(self,remoteDir):
  340. if self.remoteDirExists(remoteDir):
  341. return False
  342. r=MethodRequest(remoteDir,method="MKCOL")
  343. base64string = base64.b64encode('%s:%s' % (self.auth_name, self.auth_pass))
  344. r.add_header("Authorization", "Basic %s" % base64string)
  345. try:
  346. f=self.opener.open(r)
  347. except:
  348. print("Error: Failed MKCOL {}").format(remoteDir)
  349. return False
  350. return True
  351. def mkdirs(self,remoteDir):
  352. relativePath=self.GetRelativePathFromLabkeyPath(remoteDir)
  353. s=0
  354. while True:
  355. s1=relativePath.find('/',s)
  356. if s1<0:
  357. break
  358. path=relativePath[0:s1]
  359. remotePath=self.GetLabkeyPathFromRelativePath(relativePath)
  360. dirExists=self.remoteDirExists(remotePath)
  361. if not dirExists:
  362. if not self.mkdir(remotePath):
  363. return False
  364. s=s1+1
  365. return self.mkdir(remoteDir)
  366. def rmdir(self,remoteDir):
  367. if not self.remoteDirExists(remoteDir):
  368. return True
  369. r=MethodRequest(remoteDir,method="DELETE")
  370. base64string = base64.b64encode('%s:%s' % (self.auth_name, self.auth_pass))
  371. r.add_header("Authorization", "Basic %s" % base64string)
  372. try:
  373. f=self.opener.open(r)
  374. except:
  375. print("Error: Failed DELETE {}").format(remoteDir)
  376. return False
  377. return True
  378. #was listDir
  379. def listRelativeDir(self,relativeDir):
  380. print("Listing for {0}").format(relativeDir)
  381. dirUrl=self.GetLabkeyPathFromRelativePath(relativeDir)
  382. status,dirs=self.listRemoteDir(dirUrl)
  383. dirs=[self.GetRelativePathFromLabkeyPath(d) for d in dirs];
  384. return dirs
  385. #was isDir
  386. def isRemoteDir(self, remotePath):
  387. #print "isDir: {}".format(remotePath)
  388. r=MethodRequest(remotePath,method="PROPFIND")
  389. PROPFIND=u"""<?xml version="1.0" encoding="utf-8"?>\n
  390. <a:propfind xmlns:a="DAV:">\n
  391. <a:prop>\n
  392. <a:resourcetype/>\n
  393. </a:prop>\n
  394. </a:propfind>"""
  395. r.add_header('content-type','text/xml; charset="utf-8"')
  396. r.add_header('content-length',str(len(PROPFIND)))
  397. r.add_header('Depth','0')
  398. r.add_data(PROPFIND)
  399. base64string = base64.b64encode('%s:%s' % (self.auth_name, self.auth_pass))
  400. r.add_header("Authorization", "Basic %s" % base64string)
  401. print("PROPFIND: {0}").format(remotePath)
  402. try:
  403. f=self.opener.open(r)
  404. except:
  405. return False
  406. tree=ET.XML(f.read())
  407. try:
  408. rps=tree.find('{DAV:}response').find('{DAV:}propstat')
  409. rps=rps.find('{DAV:}prop')
  410. rps=rps.find('{DAV:}resourcetype').find('{DAV:}collection')
  411. if rps != None:
  412. return True
  413. else:
  414. return False
  415. except:
  416. return False
  417. def listRemoteDir(self,dirUrl):
  418. #input is remoteDir, result are remoteDirs
  419. r=MethodRequest(dirUrl,method="PROPFIND")
  420. PROPFIND=u"""<?xml version="1.0" encoding="utf-8"?>\n
  421. <propfind xmlns="DAV:">\n
  422. <prop>\n
  423. <getetag/>\n
  424. </prop>\n
  425. </propfind>"""
  426. r.add_header('content-type','text/xml; charset="utf-8"')
  427. r.add_header('content-length',str(len(PROPFIND)))
  428. r.add_header('Depth','1')
  429. r.add_data(PROPFIND)
  430. base64string = base64.b64encode('%s:%s' % (self.auth_name, self.auth_pass))
  431. r.add_header("Authorization", "Basic %s" % base64string)
  432. print("PROPFIND: {0}").format(dirUrl)
  433. dirs=[]
  434. try:
  435. f=self.opener.open(r)
  436. except:
  437. return False,dirs
  438. tree=ET.XML(f.read())
  439. rps=tree.findall('{DAV:}response')
  440. for r in rps:
  441. hr=r.find('{DAV:}href')
  442. dirent=hr.text
  443. #dirent=re.sub('/labkey/_webdav/','',dirent)
  444. dirent=self.GetHostName()+dirent
  445. dirs.append(dirent)
  446. del dirs[0]
  447. return True,dirs
  448. def toRelativePath(self,dirs):
  449. flist1=[]
  450. for d in dirs:
  451. if d[-1]=='/':
  452. d=d[:-1]
  453. if d.rfind('/')>-1:
  454. d=d[d.rfind('/')+1:]
  455. flist1.append(d)
  456. return flist1
  457. def readFileToBuffer(self, relativePath):
  458. dirUrl=self.GetLabkeyPathFromRelativePath(relativePath)
  459. f=self.get(dirUrl)
  460. return StringIO.StringIO(f.read())
  461. def uploadFile(self,localPath):
  462. #get upstream directories sorted out
  463. relativePath=self.GetRelativePathFromLocalPath(localPath)
  464. if relativePath=="NULL":
  465. errorCode="Failed to upload {}. Potential incorrect location"
  466. errorCode+=". Should be in labkeyCache!"
  467. print(errorCode.format(relativePath))
  468. return False
  469. relativeDir=relativePath[0:labkeyPath.rfind('/')]
  470. remoteDir=self.GetLabkeyPathFromRemotePath(relativeDir)
  471. if not self.remoteDirExists(remoteDir):
  472. if not self.mkdirs(remoteDir):
  473. errorCode="UploadFile: Could not create directory {}"
  474. print(errorCode.format(remoteDir))
  475. return False
  476. #make an URL request
  477. with open(localPath, 'rb') as f:
  478. data=f.read()
  479. remotePath=self.GetLabkeyPathFromRelativePath(relativePath)
  480. self.put(remotePath,data)
  481. #was copyFileToRemote
  482. def copyLocalFileToRemote(self,localPath, remotePath):
  483. #get upstream directories sorted out
  484. remoteDir=os.path.dirname(remotePath)
  485. if not self.remoteDirExists(remoteDir):
  486. if not self.mkdirs(remoteDir):
  487. errorCode="UploadFile: Could not create directory {}"
  488. print(errorCode.format(remoteDir))
  489. return False
  490. #make an URL request
  491. if (self.isRemoteDir(remotePath)):
  492. remotePath=remotePath+'/'+os.path.basename(localPath)
  493. with open(localPath, 'rb') as f:
  494. data=f.read()
  495. self.put(remotePath,data)
  496. #was loadDir
  497. def DownloadDirToCache(self, relativePath):
  498. files=self.listRelativeDir(relativePath)
  499. fdir="NONE"
  500. for f in files:
  501. #f is local path
  502. try:
  503. localDir=os.path.dirname(self.DownloadFileToCache(f))
  504. except:
  505. #fails if there is a subdirectory; go recursively
  506. print("self.readDir(f) not implemented")
  507. return localDir
  508. #database routines
  509. def loadDataset(self,project,dataset):
  510. url=self.GetLabkeyUrl()+'/'+project
  511. url+='/query-selectRows.api?schemaName=study&query.queryName='+dataset
  512. return json.load(self.get(url))
  513. def filterDataset(self,project,dataset,filter):
  514. debug=False
  515. url=self.GetLabkeyUrl()+'/'+project
  516. url+='/query-selectRows.api?schemaName=study&query.queryName='+dataset
  517. for f in filter:
  518. url+="&query."+f['variable']+"~"+f['oper']+"="+f['value']
  519. if debug:
  520. print("Sending {}").format(url)
  521. return json.load(self.get(url))
  522. def modifyDataset(self,method,project,dataset,rows):
  523. #method can be insert or update
  524. data={}
  525. data['schemaName']='study'
  526. data['queryName']=dataset
  527. data['rows']=rows
  528. url=self.GetLabkeyUrl()+'/'+project
  529. url+='/query-'+method+'Rows.api?'
  530. return self.post(url,json.dumps(data))
  531. def filterList(self,project,dataset,filter):
  532. schemaName='lists'
  533. debug=False
  534. url=self.GetLabkeyUrl()+'/'+project
  535. url+='/query-selectRows.api?schemaName='+schemaName+'&query.queryName='+dataset
  536. for f in filter:
  537. url+="&query."+f['variable']+"~"+f['oper']+"="+f['value']
  538. if debug:
  539. print("Sending {}").format(url)
  540. return json.load(self.get(url))
  541. def modifyList(self,method,project,dataset,rows):
  542. #method can be insert or update
  543. data={}
  544. schemaName='lists'
  545. data['schemaName']=schemaName
  546. data['queryName']=dataset
  547. data['rows']=rows
  548. url=self.GetLabkeyUrl()+'/'+project
  549. url+='/query-'+method+'Rows.api?'
  550. return self.post(url,json.dumps(data))