slicerNetwork.py 21 KB

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