class DocsetManager (object):
	def __init__(self, iconPath, typeIconPath, serverManager):
		self.typeManager = TypeManager.TypeManager(typeIconPath)
		# self.localServer = 'http://localhost/feeds/'
		self.localServer = None
		self.docsets = []
		self.downloading = []
		self.updateAvailable = []
		self.docsetFolder = 'Docsets/Standard'
		self.plistPath = 'Contents/Info.plist'
		self.indexPath = 'Contents/Resources/docSet.dsidx'
		self.iconPath = iconPath
		self.typeIconPath = typeIconPath
		self.headers = {
    'User-Agent': 'PyDoc-Pythonista'
    }
		self.__createDocsetFolder()
		self.docsetFeeds = self.__getDocsetFeeds()
		self.serverManager = serverManager
		self.downloadThreads = []
		self.uiUpdateThreads = []
		self.workThreads = []
		self.lastDocsetGroup = None
		self.createInitialSearchIndexAllDocsets()
		
	def __createDocsetFolder(self):
		if not os.path.exists(self.docsetFolder):
			os.mkdir(self.docsetFolder)
		
	def __getDocsetFeeds(self):
		with open('feeds.json') as json_data:
			data = json.load(json_data)
			feeds = []
			for feed in data:
				f = {'name':self.__getDocsetName(feed['feed']),'detailString':'',
				'feed':feed['feed'],
				'iconName':feed['icon'],
				'isCustom':False,
				'feedUrl':'http://kapeli.com/feeds/'+feed['feed'],
				'aliases':feed['aliases'],
				'hasVersions':feed['hasVersions']}
				if feed['feed'] == 'SproutCore.xml':
					f['isCustom'] = True
					f['feedUrl'] = 'http://docs.sproutcore.com/feeds/' + feed['feed']
				f['image'] = self.__getIconWithName(feed['icon'])
				feeds.append(f)
			return feeds
	
	def getOnlineVersions(self, d = None):
		docset = d
		if d == None:
			docset = self.lastDocsetGroup
		else:
			self.lastDocsetGroup = d
		link = docset['feed']
		if link == 'SproutCore.xml':
			data=requests.get('http://docs.sproutcore.com/feeds/' + link).text
			e = xml.etree.ElementTree.fromstring(data)
			version = e.findall('version')[0].text
			for atype in e.findall('url'):
				return {'url': atype.text, 'version':version}
		server = self.serverManager.getDownloadServer(self.localServer)
		data = requests.get(server.url+link).text
		e = xml.etree.ElementTree.fromstring(data)
		urlToUse = ''	
		url = ''
		for atype in e.findall('url'):	
			if not self.localServer == None:
				url = self.localServer
				urlToUse = atype.text
			if atype.text.find(server.url) >= 0:
				urlToUse = atype.text
				url = server.url
		versions = e.findall('other-versions')[0].findall('version')
		ret = []
		downloaded = self.getDownloadedDocsets()
		toCheck = []
		for down in downloaded:
			if down['name'] == docset['name']:
				toCheck.append(down)
		for v in versions:
			baseUrl = url
			onlineVersion = v.find('name').text
			disassembled = urlparse(urlToUse)
			filename, file_ext = splitext(basename(disassembled.path))
			if not baseUrl[-1] == '/':
				baseUrl = baseUrl + '/'
			baseUrl = baseUrl + 'zzz/versions/' + filename + '/' + onlineVersion + '/' + filename + file_ext
			f = copy.copy(docset)
			f['path'] = None
			f['downloadUrl'] = baseUrl
			f['version'] = onlineVersion
			f['status'] = 'online'
			for d in downloaded:
				if d['name'] == f['name'] and d['version'] == f['version']:
					f['status'] = 'installed'
					f['path'] = d['path']
					f['id'] = d['id']
					toCheck.remove(d)
			ret.append(f)
		for d in toCheck:
			d['status'] = 'installed'
			ret.append(d)
		return sorted(ret, key=lambda x: x['version'], reverse=True)
		
	def getAvailableDocsets(self):
		docsets = self.__getOnlineDocsets()
		for d in self.__getDownloadedDocsets():
			for c in docsets:
				if c['name'] == d['name']:
					c['status'] = 'installed'
					c['path'] = d['path']
					c['id'] = d['id']
					c['version'] = d['version']
		for d in self.updateAvailable:
			for c in docsets:
				if c['name'] == d['name']:
					c['status'] = 'Update Available'
		for d in self.__getDownloadingDocsets():
			for c in docsets:
				if c['name'] == d['name']:
					c['version'] = d['version']
					c['status'] = d['status']
					try:
						c['stats'] = d['stats']
					except KeyError:
						c['stats'] = 'downloading'
		return docsets
	
	def getDownloadedDocsets(self):
		return self.__getDownloadedDocsets()
	
	def __docsetFeedToDocset(self, feed):
		return feed
			
	def __getDownloadedDocsets(self):
		dbManager = DBManager.DBManager()
		t = dbManager.InstalledDocsetsByType('standard')
		ds = []
		feeds = self.__getDocsetFeeds()
		for d in t:
			aa = {}
			aa['name'] = d[1]
			aa['id'] = d[0]
			aa['path'] = os.path.join(os.path.abspath('.'),d[2])
			aa['image'] = self.__getIconWithName(d[4])
			aa['version'] = d[5]
			aa['hasVersions'] = False
			for f in feeds:
				if f['name'] == aa['name']:
					aa['hasVersions'] = f['hasVersions']
			ds.append(aa)
		return ds

	def __getDownloadingDocsets(self):
		return self.downloading
	
	def __getOnlineDocsets(self):
		feeds = self.__getDocsetFeeds()
		onlineDocsets = []
		for f in feeds:
			obj = self.__docsetFeedToDocset(f)
			obj['status'] = 'online'
			onlineDocsets.append(obj)
		return onlineDocsets
	
	def __getDocsetName(self, feed):
		name = feed.replace('.xml','')
		name = name.replace('_',' ')
		if name == 'NET Framework':
			name = '.NET Framework'
		return name
	
	def checkDocsetsForUpdates(self, docsets):
		console.show_activity('Checking for updates...')
		for d in docsets:
			if not d['hasVersions'] and d['status'] == 'installed':
				console.show_activity('Checking ' + d['name'] + ' for update...')
				f = self.__getDownloadLink(d['feed'])
				if LooseVersion(str(d['version']).replace('/','')) < LooseVersion(f['version'].replace('/','')):
					d['status'] = 'Update Available'
					d['version'] = f['version']
					self.updateAvailable.append(d)
					
	def __getIconWithName(self, name):
		imgPath = os.path.join(os.path.abspath('.'), self.iconPath, name+'.png')
		if not os.path.exists(imgPath):
			imgPath = os.path.join(os.path.abspath('.'), self.iconPath, 'Other.png')
		return ui.Image.named(imgPath)
	
	def __checkDocsetCanDownload(self, docset):
		cont = True
		feed = docset['feed']
		title = ''
		message = ''
		if feed == 'DOM.xml':
			cont = False
			title = 'DOM Documentation'
			message = 'There is no DOM docset. DOM documentation can be found in the JavaScript docset. Please install the JavaScript docset instead.'
		elif feed == 'RubyMotion.xml':
			cont = False
			title = 'RubyMotion Documentation'
			message = 'RubyMotion had to remove its API documentation due to legal reasons. Please contact the RubyMotion team for more details.\n\nIn the meantime, you can use the Apple API Reference docset instead.'
		elif feed == 'Apple_API_Reference.xml':
			cont = False
			title = 'Apple API Reference'
			message = 'To install the Apple API Reference docset you need to:\n\n1. Use Dash for macOS to install the Apple API Reference docset from Preferences > Downloads\n2. Go to Preferences > Docsets, right click the Apple API Reference docset and select \"Generate iOS Compatible Docset\"\n3. Transfer the resulting docset using the transfer function'
		elif feed == 'Apple_Guides_and_Sample_Code.xml':
			cont = False
			title = 'Apple Guides and Sample Code'
			message = 'To install the Apple Guides and Sample Code docset you need to:\n\n1. Download the docset in Xcode 8\'s Preferences > Components > Documentation\n2.Transfer the resulting docset using the transfer function'
		elif feed == 'OS_X.xml' or feed == 'macOS.xml' or feed == 'watchOS.xml' or feed == 'iOS.xml' or feed == 'tvOS.xml':
			cont = False
			title = 'Apple API Reference'
			name = docset['name']
			message = 'There is no '+name+' docset. The documentation for '+name+' can be found inside the Apple API Reference docset. \n\nTo install the Apple API Reference docset you need to:\n\n1. Use Dash for macOS to install the docset from Preferences > Downloads\n2. Go to Preferences > Docsets, right click the Apple API Reference docset and select \"Generate iOS-compatible Docset\"\n3. Transfer the resulting docset using the transfer function'
				
		if cont == False:
			console.alert(title, message,'Ok', hide_cancel_button=True)
		return cont
	
	def downloadDocset(self, docset, action, refresh_main_view):
		cont = self.__checkDocsetCanDownload(docset)
		if cont and not docset in self.downloading:
			self.downloading.append(docset)
			removeSoon = []
			for d in self.updateAvailable:
				if d['name'] == docset['name']:
					removeSoon.append(d)
			for d in removeSoon:
				self.updateAvailable.remove(d)
			docset['status'] = 'downloading'
			action()
			workThread = LogThread.LogThread(target=self.__determineUrlAndDownload, args=(docset,action,refresh_main_view,))
			self.workThreads.append(workThread)
			workThread.start()
			
	def __determineUrlAndDownload(self, docset, action, refresh_main_view):
		docset['stats'] = 'getting download link'
		action()
		if not 'downloadUrl' in docset.keys():
			data = self.__getDownloadLink(docset['feed'])
			docset['version'] = data['version']
			downloadLink = data['url']
		else:
			downloadLink = docset['downloadUrl']
		downloadThread = LogThread.LogThread(target=self.downloadFile, args=(downloadLink,docset,refresh_main_view,))
		self.downloadThreads.append(downloadThread)
		downloadThread.start()
		updateThread = LogThread.LogThread(target=self.updateUi, args=(action,downloadThread,))
		self.uiUpdateThreads.append(updateThread)
		updateThread.start()

	def updateUi(self, action, t):
		while t.is_alive():
			action()
			time.sleep(0.5)
		action()
		
	def __getDownloadLink(self, link):
		if link == 'SproutCore.xml':
			data=requests.get('http://docs.sproutcore.com/feeds/' + link).text
			e = xml.etree.ElementTree.fromstring(data)
			version = e.findall('version')[0].text
			for atype in e.findall('url'):
				return {'url': atype.text, 'version':version}
		server = self.serverManager.getDownloadServer(self.localServer)
		data = requests.get(server.url+link).text
		e = xml.etree.ElementTree.fromstring(data)
		version = e.findall('version')[0].text
		for atype in e.findall('url'):
			if not self.localServer == None:
				disassembled = urlparse(atype.text)
				filename, file_ext = splitext(basename(disassembled.path))
				url = self.localServer
				if not url[-1] == '/':
					url = url + '/'
				url = url + filename + file_ext
				return {'url': url, 'version':version}
			if atype.text.find(server.url) >= 0:
				return {'url': atype.text, 'version':version}
	
	def downloadFile(self, url, docset, refresh_main_view):
		local_filename = self.__downloadFile(url, docset)
		#self.__downloadFile(url+'.tarix', docset)
		docset['status'] = 'waiting for install'
		self.installDocset(local_filename, docset, refresh_main_view)
	
	def __downloadFile(self, url, docset):
		local_filename = self.docsetFolder+'/'+str(docset['version'].replace('/','_'))+url.split('/')[-1]
		r = requests.get(url, headers = self.headers, stream=True)
		ret = None
		if r.status_code == 200:
			ret = local_filename
			total_length = r.headers.get('content-length')
			dl = 0
			last = 0
			if os.path.exists(local_filename):
				os.remove(local_filename)
			with open(local_filename, 'wb') as f:
				for chunk in r.iter_content(chunk_size=1024): 
					if chunk: # filter out keep-alive new chunks
						dl += len(chunk)
						f.write(chunk)
						if not total_length == None:
							done = 100 * dl / int(total_length)
							docset['stats'] = str(round(done,2)) + '% ' + str(self.convertSize(dl)) + ' / '+ str(self.convertSize(float(total_length)))
						else:
							docset['stats'] = str(self.convertSize(dl))
		
		r.close()	
		return ret		
		
	def installDocset(self, filename, docset, refresh_main_view):
		extract_location = os.path.join(self.docsetFolder, '_'+docset['name'].replace('/','_'), '_'+docset['version'].replace('/','_'))
		docset['status'] = 'Preparing to install: This might take a while.'
		tar = tarfile.open(filename, 'r:gz')
		n = [name for name in tar.getnames() if '/' not in name][0]
		m = os.path.join(extract_location, n)
		tar.extractall(path=extract_location, members = self.track_progress(tar, docset, len(tar.getmembers())))
		tar.close()
		os.remove(filename)
		dbManager = DBManager.DBManager()
		dbManager.DocsetInstalled(docset['name'], m, 'standard', docset['iconName'], docset['version'])
		self.indexDocset(docset, refresh_main_view, m)
	
	def track_progress(self, members, docset, totalFiles):
		i = 0
		for member in members:
			i = i + 1
			done = 100 * i / totalFiles
			docset['status'] = 'installing: ' + str(round(done,2)) + '% ' + str(i) + ' / '+ str(totalFiles) 
			yield member
		
	def indexDocset(self, docset, refresh_main_view, path):
		docset['status'] = 'indexing'
		indexPath = os.path.join(path, self.indexPath)
		conn = sqlite3.connect(indexPath)
		sql = 'SELECT count(*) FROM sqlite_master WHERE type = \'table\' AND name = \'searchIndex\''
		c = conn.execute(sql)
		data = c.fetchone()
		if int(data[0]) == 0:
			sql = 'CREATE TABLE searchIndex(rowid INTEGER PRIMARY KEY, name TEXT, type TEXT, path TEXT)'
			c = conn.execute(sql)
			conn.commit()
			sql = 'SELECT f.ZPATH, m.ZANCHOR, t.ZTOKENNAME, ty.ZTYPENAME, t.rowid FROM ZTOKEN t, ZTOKENTYPE ty, ZFILEPATH f, ZTOKENMETAINFORMATION m WHERE ty.Z_PK = t.ZTOKENTYPE AND f.Z_PK = m.ZFILE AND m.ZTOKEN = t.Z_PK ORDER BY t.ZTOKENNAME'
			c = conn.execute(sql)
			data = c.fetchall()
			for t in data:
				conn.execute("insert into searchIndex values (?, ?, ?, ?)", (t[4], t[2], self.typeManager.getTypeForName(t[3]).name, t[0] ))
				conn.commit()
		else:
			sql = 'SELECT rowid, type FROM searchIndex'
			c = conn.execute(sql)
			data = c.fetchall()
			for t in data:
				newType = self.typeManager.getTypeForName(t[1])
				if not newType == None and not newType.name == t[1]:
					conn.execute("UPDATE searchIndex SET type=(?) WHERE rowid = (?)", (newType.name, t[0] ))
				conn.commit()
		indexSql = 'CREATE INDEX ix_searchIndex_name ON searchIndex(name)'
		conn.execute(indexSql)
		conn.close()
		self.postProcess(docset, refresh_main_view)

	def createInitialSearchIndexAllDocsets(self):
		docsets = self.getDownloadedDocsets()
		for d in docsets:
			indexPath = os.path.join(d['path'], self.indexPath)
			conn = sqlite3.connect(indexPath)
			indexSql = 'CREATE INDEX IF NOT EXISTS ix_searchIndex_name ON searchIndex(name)'
			conn.execute(indexSql)
			conn.close()
		
		
	def postProcess(self, docset, refresh_main_view):
		docset['status'] = 'installed'
		refresh_main_view()

	def convertSize(self, size):
		if (size == 0):
			return '0B'
		size_name = ("B", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB")
		i = int(math.floor(math.log(size,1024)))
		p = math.pow(1024,i)
		s = round(size/p,2)
		return '%s %s' % (s,size_name[i])
	
	def getTypesForDocset(self, docset):
		types = []
		path = docset['path']
		indexPath = os.path.join(path, self.indexPath)
		conn = sqlite3.connect(indexPath)
		sql = 'SELECT type FROM searchIndex GROUP BY type ORDER BY type COLLATE NOCASE'
		c = conn.execute(sql)
		data = c.fetchall()
		conn.close()
		for t in data:
			types.append(self.typeManager.getTypeForName(t[0]))
		return types
	
	def getIndexesbyTypeForDocset(self, docset, type):
		indexes = []
		path = docset['path']
		indexPath = os.path.join(path, self.indexPath)
		conn = sqlite3.connect(indexPath)
		sql = 'SELECT type, name, path FROM searchIndex WHERE type = (?) ORDER BY name COLLATE NOCASE'
		c = conn.execute(sql, (type.name,))
		data = c.fetchall()
		conn.close()
		dTypes = {}
		type = None
		for t in data:
			if t[0] in dTypes.keys():
				type= dTypes[t[0]]
			else:
				type = self.typeManager.getTypeForName(t[0])
				dTypes[t[0]] = type
			indexes.append({'type':type, 'name':t[1],'path':t[2]})
		return indexes
		
	def getIndexesbyTypeAndNameForDocset(self, docset, typeName, name):
		indexes = []
		path = docset['path']
		indexPath = os.path.join(path, self.indexPath)
		conn = sqlite3.connect(indexPath)
		sql = 'SELECT type, name, path FROM searchIndex WHERE type = (?) AND name LIKE (?) ORDER BY name COLLATE NOCASE'
		c = conn.execute(sql, (typeName, name))
		data = c.fetchall()
		conn.close()
		dTypes = {}
		type = None
		for t in data:
			if t[0] in dTypes.keys():
				type= dTypes[t[0]]
			else:
				type = self.typeManager.getTypeForName(t[0])
				dTypes[t[0]] = type
			indexes.append({'type':type, 'name':t[1],'path':t[2]})
		return indexes
	
	def getIndexesbyNameForDocset(self, docset, name):
		indexes = []
		path = docset['path']
		indexPath = os.path.join(path, self.indexPath)
		conn = sqlite3.connect(indexPath)
		sql = 'SELECT type, name, path FROM searchIndex WHERE name LIKE (?) ORDER BY name COLLATE NOCASE'
		c = conn.execute(sql, (name,))
		data = c.fetchall()
		conn.close()
		dTypes = {}
		type = None
		for t in data:
			if t[0] in dTypes.keys():
				type= dTypes[t[0]]
			else:
				type = self.typeManager.getTypeForName(t[0])
				dTypes[t[0]] = type
			indexes.append({'type':type, 'name':t[1],'path':t[2]})
		return indexes
	
	def getIndexesForDocset(self, docset):
		indexes = []
		path = docset['path']
		indexPath = os.path.join(path, self.indexPath)
		conn = sqlite3.connect(indexPath)
		sql = 'SELECT type, name, path FROM searchIndex ORDER BY name COLLATE NOCASE'
		c = conn.execute(sql)
		data = c.fetchall()
		conn.close()
		dTypes = {}
		type = None
		for i in data:
			if t[0] in dTypes.keys():
				type= dTypes[t[0]]
			else:
				type = self.typeManager.getTypeForName(t[0])
				dTypes[t[0]] = type
			indexes.append({'type':type, 'name':t[1],'path':t[2]})
		return types
	
	def getIndexesbyNameForAllDocset(self, name):
		if name == None or name == '':
			return {}
		else:
			docsets = self.getDownloadedDocsets()
			indexes = {}
			for d in docsets:
				ind = self.getIndexesbyNameForDocsetSearch(d, name)
				for k in ind:
					if not k in indexes.keys():
						indexes[k] = []
					indexes[k].extend(ind[k])
			return indexes
			
	
	def getIndexesbyNameForDocsetSearch(self, docset, name):
		if name == None or name == '':
			return []
		else:
			ind = {}
			path = docset['path']
			indexPath = os.path.join(path, self.indexPath)
			conn = sqlite3.connect(indexPath)
			sql = 'SELECT type, name, path FROM searchIndex WHERE name LIKE (?) ORDER BY name COLLATE NOCASE'
			c = conn.execute(sql, (name, ))
			data = {'first' : c.fetchall()}

			sql = 'SELECT type, name, path FROM searchIndex WHERE name LIKE (?) AND name NOT LIKE (?) ORDER BY name COLLATE NOCASE'
			c = conn.execute(sql, (name.replace(' ','%'), name, ))
			data['second'] = c.fetchall()
						
			sql = 'SELECT type, name, path FROM searchIndex WHERE name LIKE (?) AND name NOT LIKE (?) AND name NOT LIKE (?) ORDER BY name COLLATE NOCASE'
			c = conn.execute(sql, (name.replace(' ','%')+'%', name.replace(' ','%'), name, ))
			data['third'] = c.fetchall()
			
			sql = 'SELECT type, name, path FROM searchIndex WHERE name LIKE (?) AND name NOT LIKE (?) AND name NOT LIKE (?) AND name NOT LIKE (?) ORDER BY name COLLATE NOCASE'
			c = conn.execute(sql, ('%'+name.replace(' ','%')+'%',name.replace(' ','%')+'%',name.replace(' ','%'), name, ))
			data['fourth'] = c.fetchall()
						
									
			conn.close()
			dTypes = {}
			for k in data:
				ind[k] = []
				for t in data[k]:
					url = 'file://' + os.path.join(path, 'Contents/Resources/Documents', t[2])
					url = url.replace(' ', '%20')
					type = None
					if t[0] in dTypes.keys():
						type= dTypes[t[0]]
					else:
						type = self.typeManager.getTypeForName(t[0])
						dTypes[t[0]] = type
					ind[k].append({'name':t[1], 'path':url, 'icon':docset['image'],'docsetname':docset['name'],'type':type, 'callbackOverride':'', 'docset': docset,'hasVersions':docset['hasVersions'],'version':docset['version']})
			return ind
			
	
	
	def deleteDocset(self, docset, post_action, confirm=True):
		but = 1
		if confirm:
			but = console.alert('Are you sure?', 'Would you like to delete the docset, ' + docset['name'] + '\n This may take a while.', 'Ok')
		if but == 1:
			p = os.path.join(self.docsetFolder, '_'+docset['name'].replace('/','_'), '_'+docset['version'].replace('/','_'))
			dbmanager = DBManager.DBManager()
			dbmanager.DocsetRemoved(docset['id'])
			shutil.rmtree(p)
			docset['status'] = 'online'
			docset['path'] = None
			if not post_action == None:
				post_action() class ServerManager (object):
	def __init__(self):
		self.headers = {
    'User-Agent': 'PyDoc-Pythonista'
    }
		self.__servers = []
		self.__dynamicServers = []
		self.__setupDefaultServers()
	
	def __setupDefaultServers(self):
		serverList = ['http://newyork.kapeli.com/feeds/', 'http://sanfrancisco.kapeli.com/feeds/', 'http://london.kapeli.com/feeds/']
		for server in serverList:
			s = Server()
			s.url = server
			self.__servers.append(s)
	
	def __addDynamicServers(self, serversUrls):
		for dynamicServerUrl in serversUrls:
			add = True
			for server in self.__dynamicServers:
				if dynamicServerUrl == server.url:
					add = False
			if add:
				s = Server()
				s.url = dynamicServerUrl
				self.__dynamicServers.append(s)
	
	def getDownloadServer(self, server = None):
		if not server == None:
			s = Server()
			s.url = server
			return s
		downloadServer = None
		self.updateServerLatency()
		for server in self.__servers:
			if downloadServer == None or server.latency < downloadServer.latency:
				downloadServer = server
		for server in self.__dynamicServers:
			if downloadServer == None or server.latency < downloadServer.latency:
				downloadServer = server
		return downloadServer
		
	def updateServerLatency(self):
		for server in self.__servers:
			server.latency = self.getServerLatency(server.url)
			#print(server.url)
			#print(server.latency)
		for server in self.__dynamicServers:
			server.latency = self.getServerLatency(server.url)
			#print(server.url)
			#print(server.latency)
	
	def getServerLatency(self, url):
		timebefore = time()
		urlToTest = url
		if not urlToTest.endswith('/'):
			urlToTest=urlToTest+'/'
		urlToTest = urlToTest+'latencyTest_v2.txt'
		req = requests.get(urlToTest, headers=self.headers)
		timeafter = time()
		if req.status_code == 200:
			loc = req.text.find('Extra mirrors: ') 
			if loc >= 0:
				extra = req.text[loc+15:].split(', ')
				self.__addDynamicServers(extra)
			return timeafter-timebefore
		else:
			return 1000 class CheatsheetManager (object):
	def __init__(self, serverManager, iconPath, typeIconPath):
		self.typeManager = TypeManager.TypeManager(typeIconPath)
		self.serverManager = serverManager
		self.iconPath = iconPath
		self.typeIconPath = typeIconPath
		self.localServer = None
		self.jsonServerLocation = 'zzz/cheatsheets/cheat.json'
		self.downloadServerLocation = 'zzz/cheatsheets/%@.tgz'
		self.plistPath = 'Contents/Info.plist'
		self.indexPath = 'Contents/Resources/docSet.dsidx'
		self.cheatsheetFolder = 'Docsets/Cheatsheets'
		self.headers = {'User-Agent': 'PyDoc-Pythonista'}
		self.cheatsheets = None
		self.downloading = []
		self.updateAvailable = []
		self.workThreads = []
		self.downloadThreads = []
		self.uiUpdateThreads = []
		self.__createCheatsheetFolder()
		self.createInitialSearchIndexAllDocsets()
	
	def getAvailableCheatsheets(self):
		cheatsheets = self.__getOnlineCheatsheets()
		for d in self.__getDownloadedCheatsheets():
			for c in cheatsheets:
				if c.name == d.name:
					c.status = 'installed'
					c.path = d.path
					c.id = d.id
					c.version = d.version
		for d in self.updateAvailable:
			for c in cheatsheets:
				if c.name == d.name:
					c.status = "Update Available"
		for d in self.__getDownloadingCheatsheets():
			for c in cheatsheets:
				if c.name == d.name:
					c.status = d.status
					c.version = d.version
					try:
						c.stats = d.stats
					except KeyError:
						c.stats = 'downloading'
		return cheatsheets
	
	def __getOnlineCheatsheets(self):
		if self.cheatsheets == None:
			self.cheatsheets = self.__getCheatsheets()
		return self.cheatsheets
	
	def __getDownloadedCheatsheets(self):
		ds = []
		dbManager = DBManager.DBManager()
		t = dbManager.InstalledDocsetsByType('cheatsheet')
		ds = []
		for d in t:
			aa = Cheatsheet()
			aa.name = d[1]
			aa.id = d[0]
			aa.path = os.path.join(os.path.abspath('.'),d[2])
			aa.image = self.__getIconWithName(d[4])
			aa.version = d[5]
			ds.append(aa)
		return ds
	
	def __getDownloadingCheatsheets(self):
		return self.downloading
		
	def getDownloadedCheatsheets(self):
		return self.__getDownloadedCheatsheets()
		
	def __getCheatsheets(self):
		server = self.serverManager.getDownloadServer(self.localServer)
		url = server.url
		if not url[-1] == '/':
			url = url + '/'
		url = url + self.jsonServerLocation
		data = requests.get(url).text
		data = ast.literal_eval(data)
		
		cheatsheets = []
		icon = self.__getIconWithName('cheatsheet')
		for k,d in data['cheatsheets'].items():
			c = Cheatsheet()
			c.name = d['name']
			c.aliases = d['aliases']
			c.globalversion = data['global_version']
			c.version = d['version']
			c.image = icon
			c.onlineid = k
			c.status = 'online'
			cheatsheets.append(c)
		return sorted(cheatsheets, key=lambda x: x.name.lower())
	
	def checkDocsetsForUpdates(self, docsets):
		console.show_activity('Checking for updates...')
		self.cheatsheets = None
		online = self.__getOnlineCheatsheets()
		for d in docsets:
			if d.status == 'installed':
				console.show_activity('Checking ' + d.name + ' for update...')
				for f in online:
					if f.name == d.name:
						if LooseVersion(str(d.version).replace('/','')) < LooseVersion(str(f.version).replace('/','')):
							d.status = 'Update Available'
							d.version = f.version
							self.updateAvailable.append(d)
					
	def __getIconWithName(self, name):
		imgPath = os.path.join(os.path.abspath('.'), self.iconPath, name+'.png')
		if not os.path.exists(imgPath):
			imgPath = os.path.join(os.path.abspath('.'), self.iconPath, 'Other.png')
		return ui.Image.named(imgPath)
	
	def __createCheatsheetFolder(self):
		if not os.path.exists(self.cheatsheetFolder):
			os.mkdir(self.cheatsheetFolder)
		
	def downloadCheatsheet(self, cheatsheet, action, refresh_main_view):
		if not cheatsheet in self.downloading:
			cheatsheet.status = 'downloading'
			self.downloading.append(cheatsheet)
			removeSoon = []
			for d in self.updateAvailable:
				if d.name == cheatsheet.name:
					removeSoon.append(d)
			for d in removeSoon:
				self.updateAvailable.remove(d)
			cheatsheet.status = 'downloading'
			action()
			workThread = LogThread.LogThread(target=self.__determineUrlAndDownload, args=(cheatsheet,action,refresh_main_view,))
			self.workThreads.append(workThread)
			workThread.start()
	
	def __determineUrlAndDownload(self, cheatsheet, action, refresh_main_view):
		cheatsheet.stats = 'getting download link'
		action()
		downloadLink = self.__getDownloadLink(cheatsheet.onlineid)
		downloadThread = LogThread.LogThread(target=self.downloadFile, args=(downloadLink,cheatsheet,refresh_main_view,))
		self.downloadThreads.append(downloadThread)
		downloadThread.start()
		updateThread = LogThread.LogThread(target=self.updateUi, args=(action,downloadThread,))
		self.uiUpdateThreads.append(updateThread)
		updateThread.start()

	def updateUi(self, action, t):
		while t.is_alive():
			action()
			time.sleep(0.5)
		action()
	
	def __getDownloadLink(self, id):
		server = self.serverManager.getDownloadServer(self.localServer)
		url = server.url
		if not url[-1] == '/':
			url = url + '/'
		url = url + self.downloadServerLocation
		url = url.replace('%@', id)
		return url
	
	def downloadFile(self, url, cheatsheet, refresh_main_view):
		local_filename = self.__downloadFile(url, cheatsheet)
		#self.__downloadFile(url+'.tarix', cheatsheet)
		cheatsheet.status = 'waiting for install'
		self.installCheatsheet(local_filename, cheatsheet, refresh_main_view)
	
	def __downloadFile(self, url, cheatsheet):
		local_filename = self.cheatsheetFolder+'/'+url.split('/')[-1]
		r = requests.get(url, headers = self.headers, stream=True)
		ret = None
		if r.status_code == 200:
			ret = local_filename
			total_length = r.headers.get('content-length')
			dl = 0
			last = 0
			if os.path.exists(local_filename):
				os.remove(local_filename)
			with open(local_filename, 'wb') as f:
				for chunk in r.iter_content(chunk_size=1024): 
					if chunk: # filter out keep-alive new chunks
						dl += len(chunk)
						f.write(chunk)
						if not total_length == None:
							done = 100 * dl / int(total_length)
							cheatsheet.stats = str(round(done,2)) + '% ' + str(self.convertSize(dl)) + ' / '+ str(self.convertSize(float(total_length)))
						else:
							 cheatsheet.stats = str(self.convertSize(dl))
		
		r.close()	
		return ret		
		
	def installCheatsheet(self, filename, cheatsheet, refresh_main_view):
		extract_location = self.cheatsheetFolder
		cheatsheet.status = 'Preparing to install: This might take a while.'
		tar = tarfile.open(filename, 'r:gz')
		n = [name for name in tar.getnames() if '/' not in name][0]
		m = os.path.join(self.cheatsheetFolder, n)
		tar.extractall(path=extract_location, members = self.track_progress(tar, cheatsheet, len(tar.getmembers())))
		tar.close()
		os.remove(filename)
		dbManager = DBManager.DBManager()
		dbManager.DocsetInstalled(cheatsheet.name, m, 'cheatsheet', 'cheatsheet', cheatsheet.version)
		if cheatsheet in self.downloading:
			self.downloading.remove(cheatsheet)
		self.indexCheatsheet(cheatsheet, refresh_main_view, m)
	
	def track_progress(self, members, cheatsheet, totalFiles):
		i = 0
		for member in members:
			i = i + 1
			done = 100 * i / totalFiles
			cheatsheet.status = 'installing: ' + str(round(done,2)) + '% ' + str(i) + ' / '+ str(totalFiles) 
			yield member
	
	def indexCheatsheet(self, cheatsheet, refresh_main_view, path):
		cheatsheet.status = 'indexing'
		indexPath = os.path.join(path, self.indexPath)
		conn = sqlite3.connect(indexPath)
		sql = 'SELECT count(*) FROM sqlite_master WHERE type = \'table\' AND name = \'searchIndex\''
		c = conn.execute(sql)
		data = c.fetchone()
		if int(data[0]) == 0:
			sql = 'CREATE TABLE searchIndex(rowid INTEGER PRIMARY KEY, name TEXT, type TEXT, path TEXT)'
			c = conn.execute(sql)
			conn.commit()
			sql = 'SELECT f.ZPATH, m.ZANCHOR, t.ZTOKENNAME, ty.ZTYPENAME, t.rowid FROM ZTOKEN t, ZTOKENTYPE ty, ZFILEPATH f, ZTOKENMETAINFORMATION m WHERE ty.Z_PK = t.ZTOKENTYPE AND f.Z_PK = m.ZFILE AND m.ZTOKEN = t.Z_PK ORDER BY t.ZTOKENNAME'
			c = conn.execute(sql)
			data = c.fetchall()
			for t in data:
				conn.execute("insert into searchIndex values (?, ?, ?, ?)", (t[4], t[2], self.typeManager.getTypeForName(t[3]).name, t[0] ))
				conn.commit()
		else:
			sql = 'SELECT rowid, type FROM searchIndex'
			c = conn.execute(sql)
			data = c.fetchall()
			for t in data:
				newType = self.typeManager.getTypeForName(t[1])
				if not newType == None and not newType.name == t[1]:
					conn.execute("UPDATE searchIndex SET type=(?) WHERE rowid = (?)", (newType.name, t[0] ))
				conn.commit()
		indexSql = 'CREATE INDEX ix_searchIndex_name ON searchIndex(name)'
		conn.execute(indexSql)
		conn.close()
		self.postProcess(cheatsheet, refresh_main_view)
	
	def createInitialSearchIndexAllDocsets(self):
		docsets = self.getDownloadedCheatsheets()
		for d in docsets:
			indexPath = os.path.join(d.path, self.indexPath)
			conn = sqlite3.connect(indexPath)
			conn = sqlite3.connect(indexPath)
			indexSql = 'CREATE INDEX IF NOT EXISTS ix_searchIndex_name ON searchIndex(name)'
			conn.execute(indexSql)
			conn.close()
		
	def postProcess(self, cheatsheet, refresh_main_view):
		cheatsheet.status = 'installed'
		refresh_main_view()

	def convertSize(self, size):
		if (size == 0):
			return '0B'
		size_name = ("B", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB")
		i = int(math.floor(math.log(size,1024)))
		p = math.pow(1024,i)
		s = round(size/p,2)
		return '%s %s' % (s,size_name[i])
	
	def deleteCheatsheet(self, cheatsheet, post_action, confirm = True):
		but = 1
		if confirm:
			but = console.alert('Are you sure?', 'Would you like to delete the cheatsheet, ' +  cheatsheet.name, 'Ok')
		if but == 1:
			dbmanager = DBManager.DBManager()
			dbmanager.DocsetRemoved(cheatsheet.id)
			shutil.rmtree(cheatsheet.path)
			cheatsheet.status = 'online'
			if not post_action == None:
				post_action()
			cheatsheet.path = None
	
	def getTypesForCheatsheet(self, cheatsheet):
		types = []
		path = cheatsheet.path
		indexPath = os.path.join(path, self.indexPath)
		conn = sqlite3.connect(indexPath)
		sql = 'SELECT type FROM searchIndex GROUP BY type ORDER BY type COLLATE NOCASE'
		c = conn.execute(sql)
		data = c.fetchall()
		conn.close()
		for t in data:
			types.append(self.typeManager.getTypeForName(t[0]))
		return types
	
	def getIndexesbyTypeForCheatsheet(self, cheatsheet, type):
		indexes = []
		path = cheatsheet.path
		indexPath = os.path.join(path, self.indexPath)
		conn = sqlite3.connect(indexPath)
		sql = 'SELECT type, name, path FROM searchIndex WHERE type = (?) ORDER BY name COLLATE NOCASE'
		c = conn.execute(sql, (type.name,))
		data = c.fetchall()
		conn.close()
		dTypes ={}
		type = None		
		for t in data:
			if t[0] in dTypes.keys():
				type= dTypes[t[0]]
			else:
				type = self.typeManager.getTypeForName(t[0])
				dTypes[t[0]] = type
			indexes.append({'type':type, 'name':t[1],'path':t[2]})
		return indexes
	
	def getIndexesbyTypeAndNameForDocset(self, cheatsheet, typeName, name):
		indexes = []
		path = cheatsheet.path
		indexPath = os.path.join(path, self.indexPath)
		conn = sqlite3.connect(indexPath)
		sql = 'SELECT type, name, path FROM searchIndex WHERE type = (?) AND name LIKE (?) ORDER BY name COLLATE NOCASE'
		c = conn.execute(sql, (typeName, name,))
		data = c.fetchall()
		conn.close()
		dTypes ={}
		type = None		
		for t in data:
			if t[0] in dTypes.keys():
				type= dTypes[t[0]]
			else:
				type = self.typeManager.getTypeForName(t[0])
				dTypes[t[0]] = type
			indexes.append({'type':type, 'name':t[1],'path':t[2]})
		return indexes
		
	def getIndexesByNameForDocset(self, cheatsheet, name):
		indexes = []
		path = cheatsheet.path
		indexPath = os.path.join(path, self.indexPath)
		conn = sqlite3.connect(indexPath)
		sql = 'SELECT type, name, path FROM searchIndex WHERE name LIKE (?) ORDER BY name COLLATE NOCASE'
		c = conn.execute(sql, (name,))
		data = c.fetchall()
		conn.close()
		dTypes ={}
		type = None
		for t in data:
			if t[0] in dTypes.keys():
				type= dTypes[t[0]]
			else:
				type = self.typeManager.getTypeForName(t[0])
				dTypes[t[0]] = type
			indexes.append({'type':type, 'name':t[1],'path':t[2]})
		return indexes
	
	def getIndexesForCheatsheet(self, cheatsheet):
		indexes = []
		path = cheatsheet.path
		indexPath = os.path.join(path, self.indexPath)
		conn = sqlite3.connect(indexPath)
		sql = 'SELECT type, name, path FROM searchIndex ORDER BY name COLLATE NOCASE'
		c = conn.execute(sql)
		data = c.fetchall()
		conn.close()
		dTypes ={}
		type = None
		for i in data:
			if t[0] in dTypes.keys():
				type= dTypes[t[0]]
			else:
				type = self.typeManager.getTypeForName(t[0])
				dTypes[t[0]] = type
			indexes.append({'type':type, 'name':t[1],'path':t[2]})
		return types
		
	def getIndexesbyNameForAllCheatsheet(self, name):
		if name == None or name == '':
			return {}
		else:
			docsets = self.getDownloadedCheatsheets()
			indexes = {}
			for d in docsets:
				ind = self.getIndexesbyNameForDocsetSearch(d, name)
				for k in ind:
					if not k in indexes.keys():
						indexes[k] = []
					indexes[k].extend(ind[k])
			return indexes
			
	def getIndexesbyNameForDocsetSearch(self, docset, name):
		if name == None or name == '':
			return []
		else:
			ind = {}
			path = docset.path
			indexPath = os.path.join(path, self.indexPath)
			conn = sqlite3.connect(indexPath)
			sql = 'SELECT type, name, path FROM searchIndex WHERE name LIKE (?) ORDER BY name COLLATE NOCASE'
			c = conn.execute(sql, (name, ))
			data = {'first' : c.fetchall()}

			sql = 'SELECT type, name, path FROM searchIndex WHERE name LIKE (?) AND name NOT LIKE (?) ORDER BY name COLLATE NOCASE'
			c = conn.execute(sql, (name.replace(' ','%'), name, ))
			data['second'] = c.fetchall()
						
			sql = 'SELECT type, name, path FROM searchIndex WHERE name LIKE (?) AND name NOT LIKE (?) AND name NOT LIKE (?) ORDER BY name COLLATE NOCASE'
			c = conn.execute(sql, (name.replace(' ','%')+'%', name.replace(' ','%'), name, ))
			data['third'] = c.fetchall()
			
			sql = 'SELECT type, name, path FROM searchIndex WHERE name LIKE (?) AND name NOT LIKE (?) AND name NOT LIKE (?) AND name NOT LIKE (?) ORDER BY name COLLATE NOCASE'
			c = conn.execute(sql, ('%'+name.replace(' ','%')+'%',name.replace(' ','%')+'%',name.replace(' ','%'), name, ))
			data['fourth'] = c.fetchall()
						
									
			conn.close()
			dTypes = {}
			for k in data:
				ind[k] = []
				for t in data[k]:
					url = 'file://' + os.path.join(path, 'Contents/Resources/Documents', t[2])
					url = url.replace(' ', '%20')
					type = None
					if t[0] in dTypes.keys():
						type= dTypes[t[0]]
					else:
						type = self.typeManager.getTypeForName(t[0])
						dTypes[t[0]] = type
					ind[k].append({'name':t[1], 'path':url, 'icon':docset.image,'docsetname':docset.name,'type':type, 'callbackOverride':'', 'docset': docset})
			return ind class UserContributedManager (object):
	def __init__(self, serverManager, iconPath, typeIconPath):
		self.typeManager = TypeManager.TypeManager(typeIconPath)
		self.serverManager = serverManager
		self.iconPath = iconPath
		self.typeIconPath = typeIconPath
		self.localServer = None
		self.jsonServerLocation = 'zzz/user_contributed/build/index.json'
		self.downloadServerLocation = 'zzz/user_contributed/build/%@/%$'
		self.plistPath = 'Contents/Info.plist'
		self.indexPath = 'Contents/Resources/docSet.dsidx'
		self.userContributedFolder = 'Docsets/UserContributions'
		self.headers = {'User-Agent': 'PyDoc-Pythonista'}
		self.usercontributed = None
		self.downloading = []
		self.updateAvailable = []
		self.workThreads = []
		self.downloadThreads = []
		self.uiUpdateThreads = []
		self.lastDocsetGroup = None
		self.__createUserContributedFolder()
		self.createInitialSearchIndexAllDocsets()
	
	def getAvailableUserContributed(self):
		usercontributed = self.__getOnlineUserContributed()
		for d in self.__getDownloadedUserContributed():
			for c in usercontributed:
				if c.name == d.name and c.version == d.version:
					c.status = 'installed'
					c.path = d.path
					c.id = d.id
		for d in self.updateAvailable:
			for c in usercontributed:
				if c.name == d.name:
					c.status = "Update Available"
		for d in self.__getDownloadingUserContributed():
			for c in usercontributed:
				if c.name == d.name and c.version == d.version:
					c.status = d.status
					c.version = d.version
					c.hasVersions = d.hasVersions
					try:
						c.stats = d.stats
					except KeyError:
						c.stats = 'downloading'
		return usercontributed
	
	def __getOnlineUserContributed(self):
		if self.usercontributed == None:
			self.usercontributed = self.__getUserContributed()
		return self.usercontributed
		
	def __getDownloadedUserContributed(self):
		dbManager = DBManager.DBManager()
		t = dbManager.InstalledDocsetsByType('usercontributed')
		ds = []
		for d in t:
			aa = UserContributed()
			aa.name = d[1]
			aa.id = d[0]
			aa.path = os.path.join(os.path.abspath('.'),d[2])
			imgData = str(d[4])
			if not imgData == '':
				imgdata = base64.standard_b64decode(imgData)
				aa.image = ui.Image.from_data(imgdata)
			else:
				aa.image = self.__getIconWithName('Other')
			od = yaml.load(d[6])
			if type(od) is type({}): 
				aa.authorName = od['author']
				aa.hasVersions = od['hasVersions']
			else:
				aa.authorName = od
			aa.version = d[5]
			ds.append(aa)
		return ds
	
	def __getDownloadingUserContributed(self):
		return self.downloading
		
	def getDownloadedUserContributed(self):
		return self.__getDownloadedUserContributed()
		
	def __getUserContributed(self):
		server = self.serverManager.getDownloadServer(self.localServer)
		url = server.url
		if not url[-1] == '/':
			url = url + '/'
		url = url + self.jsonServerLocation
		data = requests.get(url).text
		data = ast.literal_eval(data)

		usercontributed = []
		defaultIcon = self.__getIconWithName('Other')
		
		for k,d in data['docsets'].items():
			u = UserContributed()
			u.name = d['name']
			if 'aliases' in d.keys():
				u.aliases = d['aliases']
			u.version = d['version']
			u.archive = d['archive']
			u.authorName = d['author']['name']
			u.hasVersions = 'specific_versions' in d.keys()
			if u.hasVersions:
				u.specificVersions = d['specific_versions']
			if 'icon' in d.keys():
				imgdata = base64.standard_b64decode(d['icon'])
				u.image = ui.Image.from_data(imgdata)
				u.imageData = d['icon']
			else:
				u.image = defaultIcon
			u.onlineid = k
			u.status = 'online'
			usercontributed.append(u)
		return sorted(usercontributed, key=lambda x: x.name.lower())
	
	def checkDocsetsForUpdates(self, docsets):
		console.show_activity('Checking for updates...')
		self.usercontributed = None
		online = self.__getOnlineUserContributed()
		for d in docsets:
			if not d.hasVersions and d.status == 'installed':
				console.show_activity('Checking ' + d.name + ' for update...')
				for f in online:
					if f.name == d.name:
						if LooseVersion(str(d.version).replace('/','')) < LooseVersion(str(f.version).replace('/','')):
							d.status = 'Update Available'
							d.version = f.version
							self.updateAvailable.append(d)
	
	def getOnlineVersions(self, doc= None):
		d = None
		if doc == None:
			d = self.lastDocsetGroup
		else:
			self.lastDocsetGroup = doc
			d = doc
		data = [d]
		downloaded = self.getDownloadedUserContributed()
		endCheck = []
		for dad in downloaded:
			if dad.name == d.name:
				endCheck.append(dad)
		toRemoveOrig = [i for i in endCheck if i.name==d.name and i.version == d.version]
		for rt in toRemoveOrig:
			endCheck.remove(rt)
		for version in d.specificVersions:
			if not '_comment' in version.keys():
				da = copy.copy(d)
				da.specificVersions = []
				da.status = 'online'
				da.version = version['version']
				da.archive = version['archive'].replace('\\','')
				da.path = None
				for down in downloaded:
					if da.name == down.name and da.version == down.version:
						da.status = 'installed'
						da.path = down.path
						da.id = down.id
						toRemoveFromEndCheck = [i for i in endCheck if i.name==da.name and i.version == da.version]
						for rt in toRemoveFromEndCheck:
							endCheck.remove(rt)
				add = True
				for toCheck in data:
					if toCheck.name == da.name and toCheck.version == da.version:
						add = False
				if add:
					data.append(da)
		for e in endCheck:
			e.status = 'installed'
			data.append(e)
		return sorted(data, key=lambda x: x.version, reverse=True)
							
	def __getLocalIcon(self, path):
		imgPath = os.path.join(os.path.abspath('.'),path,'icon.png')
		if not os.path.exists(imgPath):
			imgPath = os.path.join(os.path.abspath('.'), self.iconPath, 'Other.png')
		return ui.Image.named(imgPath)
	
	def __getIconWithName(self, name):
		imgPath = os.path.join(os.path.abspath('.'), self.iconPath, name+'.png')
		if not os.path.exists(imgPath):
			imgPath = os.path.join(os.path.abspath('.'), self.iconPath, 'Other.png')
		return ui.Image.named(imgPath)
	
	def __createUserContributedFolder(self):
		if not os.path.exists(self.userContributedFolder):
			os.mkdir(self.userContributedFolder)
		
	def downloadUserContributed(self, usercontributed, action, refresh_main_view):	
		if not usercontributed in self.downloading:
			removeSoon = []
			for d in self.updateAvailable:
				if d.name == usercontributed.name:
					removeSoon.append(d)
			for d in removeSoon:
				self.updateAvailable.remove(d)
			usercontributed.status = 'downloading'
			self.downloading.append(usercontributed)
			action()
			workThread = LogThread.LogThread(target=self.__determineUrlAndDownload, args=(usercontributed,action,refresh_main_view,))
			self.workThreads.append(workThread)
			workThread.start()
	
	def __determineUrlAndDownload(self, usercontributed, action, refresh_main_view):
		usercontributed.stats = 'getting download link'
		action()
		downloadLink = self.__getDownloadLink(usercontributed.onlineid, usercontributed.archive)
		downloadThread = LogThread.LogThread(target=self.downloadFile, args=(downloadLink,usercontributed,refresh_main_view,))
		self.downloadThreads.append(downloadThread)
		downloadThread.start()
		updateThread = LogThread.LogThread(target=self.updateUi, args=(action,downloadThread,))
		self.uiUpdateThreads.append(updateThread)
		updateThread.start()

	def updateUi(self, action, t):
		while t.is_alive():
			action()
			time.sleep(0.5)
		action()
	
	def __getDownloadLink(self, id, archive):
		server = self.serverManager.getDownloadServer(self.localServer)
		url = server.url
		if not url[-1] == '/':
			url = url + '/'
		url = url + self.downloadServerLocation
		url = url.replace('%@', id)
		url = url.replace('%$', archive)
		return url
	
	def downloadFile(self, url, usercontributed, refresh_main_view):
		local_filename = self.__downloadFile(url, usercontributed)
		#self.__downloadFile(url+'.tarix', cheatsheet)
		usercontributed.status = 'waiting for install'
		self.installUserContributed(local_filename, usercontributed, refresh_main_view)
	
	def __downloadFile(self, url, usercontributed):
		local_filename = self.userContributedFolder+'/'+str(usercontributed.version).replace('/','_')+url.split('/')[-1]
		r = requests.get(url, headers = self.headers, stream=True)
		ret = None
		if r.status_code == 200:
			ret = local_filename
			total_length = r.headers.get('content-length')
			dl = 0
			last = 0
			if os.path.exists(local_filename):
				os.remove(local_filename)
			with open(local_filename, 'wb') as f:
				for chunk in r.iter_content(chunk_size=1024): 
					if chunk: # filter out keep-alive new chunks
						dl += len(chunk)
						f.write(chunk)
						if not total_length == None:
							done = 100 * dl / int(total_length)
							usercontributed.stats = str(round(done,2)) + '% ' + str(self.convertSize(dl)) + ' / '+ str(self.convertSize(float(total_length)))
						else:
							usercontributed.stats = str(self.convertSize(dl))
		r.close()	
		return ret		
		
	def installUserContributed(self, filename, usercontributed, refresh_main_view):
		extract_location = os.path.join(self.userContributedFolder, '_'+usercontributed.name.replace('/','_'), '_'+usercontributed.version.replace('/','_'))
		usercontributed.status = 'Preparing to install: This might take a while.'
		tar = tarfile.open(filename, 'r:gz')
		n = [name for name in tar.getnames() if '/' not in name][0]
		m = os.path.join(extract_location, n)
		tar.extractall(path=extract_location, members = self.track_progress(tar, usercontributed, len(tar.getmembers())))
		tar.close()
		encodedImg = usercontributed.imageData
		dbManager = DBManager.DBManager()
		otherAtt = {}
		otherAtt['author'] = usercontributed.authorName
		otherAtt['hasVersions'] = usercontributed.hasVersions
		dbManager.DocsetInstalled(usercontributed.name, m, 'usercontributed', str(encodedImg), usercontributed.version, str(otherAtt))
		os.remove(filename)
		if usercontributed in self.downloading:
			self.downloading.remove(usercontributed)		
		self.indexUserContributed(usercontributed, refresh_main_view, m)
	
	def track_progress(self, members, usercontributed, totalFiles):
		i = 0
		for member in members:
			i = i + 1
			done = 100 * i / totalFiles
			usercontributed.status = 'installing: ' + str(round(done,2)) + '% ' + str(i) + ' / '+ str(totalFiles) 
			yield member
	
	def indexUserContributed(self, usercontributed, refresh_main_view, path):
		usercontributed.status = 'indexing'
		indexPath = os.path.join(path, self.indexPath)
		conn = sqlite3.connect(indexPath)
		sql = 'SELECT count(*) FROM sqlite_master WHERE type = \'table\' AND name = \'searchIndex\''
		c = conn.execute(sql)
		data = c.fetchone()
		if int(data[0]) == 0:
			sql = 'CREATE TABLE searchIndex(rowid INTEGER PRIMARY KEY, name TEXT, type TEXT, path TEXT)'
			c = conn.execute(sql)
			conn.commit()
			sql = 'SELECT f.ZPATH, m.ZANCHOR, t.ZTOKENNAME, ty.ZTYPENAME, t.rowid FROM ZTOKEN t, ZTOKENTYPE ty, ZFILEPATH f, ZTOKENMETAINFORMATION m WHERE ty.Z_PK = t.ZTOKENTYPE AND f.Z_PK = m.ZFILE AND m.ZTOKEN = t.Z_PK ORDER BY t.ZTOKENNAME'
			c = conn.execute(sql)
			data = c.fetchall()
			for t in data:
				conn.execute("insert into searchIndex values (?, ?, ?, ?)", (t[4], t[2], self.typeManager.getTypeForName(t[3]).name, t[0] ))
				conn.commit()
		else:
			sql = 'SELECT rowid, type FROM searchIndex'
			c = conn.execute(sql)
			data = c.fetchall()
			for t in data:
				newType = self.typeManager.getTypeForName(t[1])
				if not newType == None and not newType.name == t[1]:
					conn.execute("UPDATE searchIndex SET type=(?) WHERE rowid = (?)", (newType.name, t[0] ))
				conn.commit()
		indexSql = 'CREATE INDEX ix_searchIndex_name ON searchIndex(name)'
		conn.execute(indexSql)
		conn.close()
		self.postProcess(usercontributed, refresh_main_view)

	def createInitialSearchIndexAllDocsets(self):
		docsets = self.getDownloadedUserContributed()
		for d in docsets:
			indexPath = os.path.join(d.path, self.indexPath)
			conn = sqlite3.connect(indexPath)
			conn = sqlite3.connect(indexPath)
			indexSql = 'CREATE INDEX IF NOT EXISTS ix_searchIndex_name ON searchIndex(name)'
			conn.execute(indexSql)
			conn.close()

	def postProcess(self, usercontributed, refresh_main_view):
		usercontributed.status = 'installed'
		refresh_main_view()

	def convertSize(self, size):
		if (size == 0):
			return '0B'
		size_name = ("B", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB")
		i = int(math.floor(math.log(size,1024)))
		p = math.pow(1024,i)
		s = round(size/p,2)
		return '%s %s' % (s,size_name[i])
	
	def deleteUserContributed(self, usercontributed, post_action, confirm = True):
		but = 1
		if confirm:
			but = console.alert('Are you sure?', 'Would you like to delete the docset, ' +  usercontributed.name, 'Ok')
		if but == 1:
			dbmanager = DBManager.DBManager()
			dbmanager.DocsetRemoved(usercontributed.id)
			shutil.rmtree(usercontributed.path)
			usercontributed.status = 'online'
			usercontributed.path = None
			if not post_action == None:
				post_action()

	
	def getTypesForUserContributed(self, usercontributed):
		types = []
		path = usercontributed.path
		indexPath = os.path.join(path, self.indexPath)
		conn = sqlite3.connect(indexPath)
		sql = 'SELECT type FROM searchIndex GROUP BY type ORDER BY type COLLATE NOCASE'
		c = conn.execute(sql)
		data = c.fetchall()
		conn.close()
		for t in data:
			types.append(self.typeManager.getTypeForName(t[0]))
		return types
	
	def getIndexesbyTypeAndNameForUserContributed(self, usercontributed, typeName, name):
		indexes = []
		path = usercontributed.path
		indexPath = os.path.join(path, self.indexPath)
		conn = sqlite3.connect(indexPath)
		sql = 'SELECT type, name, path FROM searchIndex WHERE type = (?) AND name LIKE (?) ORDER BY name COLLATE NOCASE'
		c = conn.execute(sql, (typeName, name,))
		data = c.fetchall()
		conn.close()
		dTypes ={}
		type = None		
		for t in data:
			if t[0] in dTypes.keys():
				type= dTypes[t[0]]
			else:
				type = self.typeManager.getTypeForName(t[0])
				dTypes[t[0]] = type
			indexes.append({'type':type, 'name':t[1],'path':t[2]})
		return indexes
		
	def getIndexesbyNameForUserContributed(self, usercontributed, name):
		indexes = []
		path = usercontributed.path
		indexPath = os.path.join(path, self.indexPath)
		conn = sqlite3.connect(indexPath)
		sql = 'SELECT type, name, path FROM searchIndex WHERE name LIKE (?) ORDER BY name COLLATE NOCASE'
		c = conn.execute(sql, (name,))
		data = c.fetchall()
		conn.close()
		dTypes ={}
		type = None		
		for t in data:
			if t[0] in dTypes.keys():
				type= dTypes[t[0]]
			else:
				type = self.typeManager.getTypeForName(t[0])
				dTypes[t[0]] = type
			indexes.append({'type':type, 'name':t[1],'path':t[2]})
		return indexes
	
	def getIndexesbyTypeForUserContributed(self, usercontributed, type):
		indexes = []
		path = usercontributed.path
		indexPath = os.path.join(path, self.indexPath)
		conn = sqlite3.connect(indexPath)
		sql = 'SELECT type, name, path FROM searchIndex WHERE type = (?) ORDER BY name COLLATE NOCASE'
		c = conn.execute(sql, (type.name,))
		data = c.fetchall()
		conn.close()
		dTypes ={}
		type = None		
		for t in data:
			if t[0] in dTypes.keys():
				type= dTypes[t[0]]
			else:
				type = self.typeManager.getTypeForName(t[0])
				dTypes[t[0]] = type
			indexes.append({'type':type, 'name':t[1],'path':t[2]})
		return indexes
	
	def getIndexesForUserContributed(self, usercontributed):
		indexes = []
		path = usercontributed.path
		indexPath = os.path.join(path, self.indexPath)
		conn = sqlite3.connect(indexPath)
		sql = 'SELECT type, name, path FROM searchIndex ORDER BY name COLLATE NOCASE'
		c = conn.execute(sql)
		data = c.fetchall()
		conn.close()
		dTypes ={}
		type = None		
		for t in data:
			if t[0] in dTypes.keys():
				type= dTypes[t[0]]
			else:
				type = self.typeManager.getTypeForName(t[0])
				dTypes[t[0]] = type
			indexes.append({'type':type, 'image':self.__getTypeIconWithName(t[0]), 'name':t[1],'path':t[2]})
		return types
	
	def getIndexesbyNameForAllUserContributed(self, name):
		if name == None or name == '':
			return {}
		else:
			docsets = self.getDownloadedUserContributed()
			indexes = {}
			for d in docsets:
				ind = self.getIndexesbyNameForDocsetSearch(d, name)
				for k in ind:
					if not k in indexes.keys():
						indexes[k] = []
					indexes[k].extend(ind[k])
			return indexes
			
	def getIndexesbyNameForDocsetSearch(self, docset, name):
		if name == None or name == '':
			return []
		else:
			ind = {}
			path = docset.path
			indexPath = os.path.join(path, self.indexPath)
			conn = sqlite3.connect(indexPath)
			sql = 'SELECT type, name, path FROM searchIndex WHERE name LIKE (?) ORDER BY name COLLATE NOCASE'
			c = conn.execute(sql, (name, ))
			data = {'first' : c.fetchall()}

			sql = 'SELECT type, name, path FROM searchIndex WHERE name LIKE (?) AND name NOT LIKE (?) ORDER BY name COLLATE NOCASE'
			c = conn.execute(sql, (name.replace(' ','%'), name, ))
			data['second'] = c.fetchall()
						
			sql = 'SELECT type, name, path FROM searchIndex WHERE name LIKE (?) AND name NOT LIKE (?) AND name NOT LIKE (?) ORDER BY name COLLATE NOCASE'
			c = conn.execute(sql, (name.replace(' ','%')+'%', name.replace(' ','%'), name, ))
			data['third'] = c.fetchall()
			
			sql = 'SELECT type, name, path FROM searchIndex WHERE name LIKE (?) AND name NOT LIKE (?) AND name NOT LIKE (?) AND name NOT LIKE (?) ORDER BY name COLLATE NOCASE'
			c = conn.execute(sql, ('%'+name.replace(' ','%')+'%',name.replace(' ','%')+'%',name.replace(' ','%'), name, ))
			data['fourth'] = c.fetchall()
						
									
			conn.close()
			dTypes = {}
			for k in data:
				ind[k] = []
				for t in data[k]:
					url = 'file://' + os.path.join(path, 'Contents/Resources/Documents', t[2])
					url = url.replace(' ', '%20')
					type = None
					if t[0] in dTypes.keys():
						type= dTypes[t[0]]
					else:
						type = self.typeManager.getTypeForName(t[0])
						dTypes[t[0]] = type
					ind[k].append({'name':t[1], 'path':url, 'icon':docset.image,'docsetname':docset.name,'type':type, 'callbackOverride':'', 'docset': docset, 'hasVersions':docset.hasVersions,'version':docset.version})
			return ind class DBManager (object):
	def __init__(self):
		self.docsetDBLocation = 'Docsets/docsets.db'
		self.migrationDBLocation = '.migrations.db'
		self.connection = None
		self.SetupDocsetDB()
		self.SetMigrationDB()
				
	def SetupDocsetDB(self):
		self.connection = sqlite3.connect(self.docsetDBLocation)
		c = self.connection.cursor()
		c.execute('CREATE TABLE IF NOT EXISTS docsets(ID INTEGER PRIMARY KEY AUTOINCREMENT, Name TEXT NOT NULL, Path TEXT NOT NULL, Type TEXT NOT NULL, Icon TEXT NOT NULL, Version REAL NULL, OtherData TEXT NOT NULL);')
		self.connection.commit()
		
	def SetMigrationDB(self):
		self.migrationconnection = sqlite3.connect(self.migrationDBLocation)
		mc = self.migrationconnection.cursor()
		mc.execute('CREATE TABLE IF NOT EXISTS migrations(ID INTEGER PRIMARY KEY AUTOINCREMENT, Name TEXT NOT NULL, Completed BOOLEAN NOT NULL)')
		self.migrationconnection.commit()
		
	def RunQueryOnDocsetDB(self, query):
		self.connection = sqlite3.connect(self.docsetDBLocation)
		c = self.connection.cursor()
		c.execute(query)
		self.connection.commit()
	
	def DocsetInstalled(self, name, path, type, icon, version, otherdata = ''):
		c = self.connection.cursor()
		c.execute('INSERT INTO docsets (Name, Path, Type, Icon, Version, OtherData) VALUES (?,?,?,?,?,?)',(name,path,type,icon,version,otherdata,))
		self.connection.commit()
	
	
	def DocsetRemoved(self, id):
		c = self.connection.cursor()
		c.execute('DELETE FROM docsets WHERE ID = (?)',(id,))
		self.connection.commit()
	
	def InstalledDocsets(self):
		return self.connection.execute('SELECT * FROM docsets').fetchall()
	
	def InstalledDocsetsByType(self, type):
		return self.connection.execute('SELECT * FROM docsets WHERE type = (?) ORDER BY name COLLATE NOCASE ASC, version DESC', (type,)).fetchall()
	
	def AddMigration(self, name, completed = False):
		records = self.migrationconnection.execute('SELECT * FROM migrations WHERE Name like (?)',(name,)).fetchall()
		if len(records) == 0:
			c = self.migrationconnection.cursor()
			c.execute('INSERT INTO migrations (Name, Completed) VALUES (?,?)',(name,completed,))
			self.migrationconnection.commit()
	
	def UpdateMigration(self, name, completed):
		records = self.migrationconnection.execute('SELECT * FROM migrations WHERE Name like (?)',(name,)).fetchall()
		if len(records) > 0:
			c = self.migrationconnection.cursor()
		
			c.execute('UPDATE migrations SET Completed = (?) WHERE name like (?)',(completed,name,))
			self.migrationconnection.commit()
	
	def GetMigration(self, name):
		record = self.migrationconnection.execute('SELECT * FROM migrations WHERE Name like (?)',(name,)).fetchone()
		return record  class ThemeManager (object):
	def __init__(self, themesfolder):
		self.themesFolder = themesfolder
		self.themes = self.getThemes(themesfolder)
		self.themeFileName = self.getThemeToUse(themesfolder)
		try:
			self.currentTheme = self.themes[self.themeFileName]
		except KeyError:
			self.setThemeToUse('Default.json')
			self.themeFileName = self.getThemeToUse(themesfolder)	
			self.currentTheme = self.themes[self.themeFileName]
	
	def setThemeToUse(self, themeFileName):
		self.themeFileName = themeFileName
		self.currentTheme = self.themes[themeFileName]
		self.saveCurrentThemeToUse()
		
	def getThemeToUse(self, themesfolder):
		themeConfigPath = '.themesConfig'
		if not os.path.exists(themeConfigPath):
			self.saveThemeToUse('Default.json')
		with open(themeConfigPath, 'r') as config:
			name = config.read()
		return name
	
	def saveCurrentThemeToUse(self):
		themeConfigPath = '.themesConfig'
		if os.path.exists(themeConfigPath):
			os.remove(themeConfigPath)
		with open(themeConfigPath, 'w') as config:
			config.write(self.themeFileName)
	
	def saveThemeToUse(self, themeFileName):
		themeConfigPath = '.themesConfig'
		if os.path.exists(themeConfigPath):
			os.remove(themeConfigPath)
		with open(themeConfigPath, 'w') as config:
			config.write(themeFileName)

	def getThemes(self, folder):
		themes = {}
		folders = glob(os.path.join(folder, '*.json')) 
		for fullFilePath in folders:
			if os.path.isfile(fullFilePath):
				with open(fullFilePath , 'r') as data_file:
					data = json.load(data_file)
					themes[os.path.basename(fullFilePath)] = Theme(data)
		return themes class StackOverflowManager (object):
	def __init__(self, serverManager, iconPath, typeIconPath):
		self.typeManager = TypeManager.TypeManager(typeIconPath)
		self.serverManager = serverManager
		self.iconPath = iconPath
		self.typeIconPath = typeIconPath
		self.localServer = None
		self.jsonServerLocation = 'zzz/stackoverflow/index.json'
		self.downloadServerLocation = 'zzz/stackoverflow/%@_%v.tgz'
		self.plistPath = 'Contents/Info.plist'
		self.indexPath = 'Contents/Resources/docSet.dsidx'
		self.stackoverflowFolder = 'Docsets/StackOverflow'
		self.headers = {'User-Agent': 'PyDoc-Pythonista'}
		self.stackoverflows = None
		self.downloading = []
		self.updateAvailable = []		
		self.workThreads = []
		self.downloadThreads = []
		self.uiUpdateThreads = []
		self.__createStackOverflowFolder()
		self.createInitialSearchIndexAllDocsets()
	
	def getAvailableStackOverflows(self):
		stackoverflows = self.__getOnlineStackOverflows()
		for d in self.__getDownloadedStackOverflows():
			for s in stackoverflows:
				if s.name+s.type == d.name:
					s.status = 'installed'
					s.path = d.path
					s.id = d.id
					s.version = d.version
		for d in self.updateAvailable:
			for s in stackoverflows:
				if s.name+s.type == d.name+d.type:
					s.status = "Update Available"					
		for d in self.__getDownloadingStackOverflows():
			for s in stackoverflows:
				if s.name+s.type == d.name:
					s.status = d.status
					s.version = d.version					
					try:
						s.stats = d.stats
					except KeyError:
						s.stats = 'downloading'
		return stackoverflows
	
	def __getOnlineStackOverflows(self):
		if self.stackoverflows == None:
			self.stackoverflows = self.__getStackOverflows()
		return self.stackoverflows
	
	def __getDownloadedStackOverflows(self):
		ds = []
		dbManager = DBManager.DBManager()
		t = dbManager.InstalledDocsetsByType('stackoverflow')
		ds = []
		for d in t:
			aa = StackOverflow()
			aa.name = d[1]
			aa.id = d[0]
			aa.path = os.path.join(os.path.abspath('.'),d[2])
			aa.image = self.__getIconWithName(d[4])
			aa.type = d[6]
			aa.version = d[5]
			ds.append(aa)
		return ds
	
	def __getDownloadingStackOverflows(self):
		return self.downloading
		
	def getDownloadedStackOverflows(self):
		return self.__getDownloadedStackOverflows()
		
	def __getStackOverflows(self):
		server = self.serverManager.getDownloadServer(self.localServer)
		url = server.url
		if not url[-1] == '/':
			url = url + '/'
		url = url + self.jsonServerLocation
		data = requests.get(url).text
		data = ast.literal_eval(data)
		stackoverflows = []
		onlineIcon = self.__getIconWithName('soonline')
		offlineIcon = self.__getIconWithName('sooffline')
		for k,d in data['docsets'].items():
			if 'online' in d['variants'].keys():
				s = StackOverflow()
				s.name = d['name']
				s.aliases = d['aliases']
				s.version = d['version']
				s.tags = d['tags']
				s.keyword = d['keyword']
				s.image = onlineIcon
				s.onlineid = k
				s.status = 'online'
				s.type = 'Online'
				stackoverflows.append(s)
			if 'offline' in d['variants'].keys():
				so = StackOverflow()
				so.name = d['name']
				so.aliases = d['aliases']
				so.version = d['version']
				so.tags = d['tags']
				so.keyword = d['keyword']
				so.image = offlineIcon
				so.onlineid = k
				so.status = 'online'
				so.type = 'Offline'
				stackoverflows.append(so)
		return sorted(stackoverflows, key=lambda x: x.name.lower())

	def checkDocsetsForUpdates(self, docsets):
		console.show_activity('Checking for updates...')
		self.stackoverflows = None
		online = self.__getOnlineStackOverflows()
		for d in docsets:
			if d.status == 'installed':
				console.show_activity('Checking ' + d.name + ' for update...')
				for f in online:
					if f.name == d.name:
						if datetime.datetime.strptime(d.version.replace('Sept', 'Sep'), '%b %d, %Y') < datetime.datetime.strptime(f.version.replace('Sept', 'Sep'), '%b %d, %Y'):
							d.status = 'Update Available'
							d.version = f.version
							self.updateAvailable.append(d)
							
	def __getIconWithName(self, name):
		imgPath = os.path.join(os.path.abspath('.'), self.iconPath, name+'.png')
		if not os.path.exists(imgPath):
			imgPath = os.path.join(os.path.abspath('.'), self.iconPath, 'Other.png')
		return ui.Image.named(imgPath)
	
	def __createStackOverflowFolder(self):
		if not os.path.exists(self.stackoverflowFolder):
			os.mkdir(self.stackoverflowFolder)
		
	def downloadStackOverflow(self, stackoverflow, action, refresh_main_view):
		if not stackoverflow in self.downloading:
			removeSoon = []
			for d in self.updateAvailable:
				if d.name+d.type == stackoverflow.name+stackoverflow.type:
					removeSoon.append(d)
			for d in removeSoon:
				self.updateAvailable.remove(d)
			stackoverflow.status = 'downloading'
			self.downloading.append(stackoverflow)
			action()
			workThread = LogThread.LogThread(target=self.__determineUrlAndDownload, args=(stackoverflow,action,refresh_main_view,))
			self.workThreads.append(workThread)
			workThread.start()
	
	def __determineUrlAndDownload(self, stackoverflow, action, refresh_main_view):
		stackoverflow.stats = 'getting download link'
		action()
		downloadLink = self.__getDownloadLink(stackoverflow.onlineid, stackoverflow.type)
		downloadThread = LogThread.LogThread(target=self.downloadFile, args=(downloadLink,stackoverflow,refresh_main_view,))
		self.downloadThreads.append(downloadThread)
		downloadThread.start()
		updateThread = LogThread.LogThread(target=self.updateUi, args=(action,downloadThread,))
		self.uiUpdateThreads.append(updateThread)
		updateThread.start()

	def updateUi(self, action, t):
		while t.is_alive():
			action()
			time.sleep(0.5)
		action()
	
	def __getDownloadLink(self, id, type):
		server = self.serverManager.getDownloadServer(self.localServer)
		url = server.url
		if not url[-1] == '/':
			url = url + '/'
		url = url + self.downloadServerLocation
		url = url.replace('%@', id)
		url = url.replace('%v', type)
		return url
	
	def downloadFile(self, url, stackoverflow, refresh_main_view):
		local_filename = self.__downloadFile(url, stackoverflow)
		stackoverflow.status = 'waiting for install'
		self.installStackOverflow(local_filename, stackoverflow, refresh_main_view)
	
	def __downloadFile(self, url, stackoverflow):
		local_filename = self.stackoverflowFolder+'/'+url.split('/')[-1]
		r = requests.get(url, headers = self.headers, stream=True)
		ret = None
		if r.status_code == 200:
			ret = local_filename
			total_length = r.headers.get('content-length')
			dl = 0
			last = 0
			if os.path.exists(local_filename):
				os.remove(local_filename)
			with open(local_filename, 'wb') as f:
				for chunk in r.iter_content(chunk_size=1024): 
					if chunk: # filter out keep-alive new chunks
						dl += len(chunk)
						f.write(chunk)
						if not total_length == None:
							done = 100 * dl / int(total_length)
							stackoverflow.stats = str(round(done,2)) + '% ' + str(self.convertSize(dl)) + ' / '+ str(self.convertSize(float(total_length)))
						else:
							 stackoverflow.stats = str(self.convertSize(dl))
		r.close()	
		return ret		
		
	def installStackOverflow(self, filename, stackoverflow, refresh_main_view):
		extract_location = self.stackoverflowFolder
		stackoverflow.status = 'Preparing to install: This might take a while.'
		tar = tarfile.open(filename, 'r:gz')
		n = [name for name in tar.getnames() if '/' not in name][0]
		m = os.path.join(self.stackoverflowFolder, n)
		tar.extractall(path=extract_location, members = self.track_progress(tar, stackoverflow, len(tar.getmembers())))
		tar.close()
		os.remove(filename)
		dbManager = DBManager.DBManager()
		icon = 'soonline'
		if stackoverflow.type == 'Offline':
			icon = 'sooffline'
		dbManager.DocsetInstalled(stackoverflow.name+stackoverflow.type, m, 'stackoverflow', icon, stackoverflow.version, stackoverflow.type)
		if stackoverflow in self.downloading:
			self.downloading.remove(stackoverflow)
		self.indexStackOverflow(stackoverflow, refresh_main_view, m)
	
	def track_progress(self, members, stackoverflow, totalFiles):
		i = 0
		for member in members:
			i = i + 1
			done = 100 * i / totalFiles
			stackoverflow.status = 'installing: ' + str(round(done,2)) + '% ' + str(i) + ' / '+ str(totalFiles) 
			yield member
	
	def indexStackOverflow(self, stackoverflow, refresh_main_view, path):
		stackoverflow.status = 'indexing'
		indexPath = os.path.join(path, self.indexPath)
		conn = sqlite3.connect(indexPath)
		sql = 'SELECT count(*) FROM sqlite_master WHERE type = \'table\' AND name = \'searchIndex\''
		c = conn.execute(sql)
		data = c.fetchone()
		if int(data[0]) == 0:
			sql = 'CREATE TABLE searchIndex(rowid INTEGER PRIMARY KEY, name TEXT, type TEXT, path TEXT)'
			c = conn.execute(sql)
			conn.commit()
			sql = 'SELECT f.ZPATH, m.ZANCHOR, t.ZTOKENNAME, ty.ZTYPENAME, t.rowid FROM ZTOKEN t, ZTOKENTYPE ty, ZFILEPATH f, ZTOKENMETAINFORMATION m WHERE ty.Z_PK = t.ZTOKENTYPE AND f.Z_PK = m.ZFILE AND m.ZTOKEN = t.Z_PK ORDER BY t.ZTOKENNAME'
			c = conn.execute(sql)
			data = c.fetchall()
			for t in data:
				conn.execute("insert into searchIndex values (?, ?, ?, ?)", (t[4], t[2], self.typeManager.getTypeForName(t[3]).name, t[0] ))
				conn.commit()
		else:
			sql = 'SELECT rowid, type FROM searchIndex'
			c = conn.execute(sql)
			data = c.fetchall()
			for t in data:
				newType = self.typeManager.getTypeForName(t[1])
				if not newType == None and not newType.name == t[1]:
					conn.execute("UPDATE searchIndex SET type=(?) WHERE rowid = (?)", (newType.name, t[0] ))
				conn.commit()
		indexSql = 'CREATE INDEX ix_searchIndex_name ON searchIndex(name)'
		conn.execute(indexSql)
		conn.close()
		self.postProcess(stackoverflow, refresh_main_view)
	
	def createInitialSearchIndexAllDocsets(self):
		docsets = self.getDownloadedStackOverflows()
		for d in docsets:
			indexPath = os.path.join(d.path, self.indexPath)
			conn = sqlite3.connect(indexPath)
			conn = sqlite3.connect(indexPath)
			indexSql = 'CREATE INDEX IF NOT EXISTS ix_searchIndex_name ON searchIndex(name)'
			conn.execute(indexSql)
			conn.close()

	def postProcess(self, stackoverflow, refresh_main_view):
		stackoverflow.status = 'installed'
		refresh_main_view()

	def convertSize(self, size):
		if (size == 0):
			return '0B'
		size_name = ("B", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB")
		i = int(math.floor(math.log(size,1024)))
		p = math.pow(1024,i)
		s = round(size/p,2)
		return '%s %s' % (s,size_name[i])
	
	def deleteStackOverflow(self, stackoverflow, post_action, confirm = True):
		but = 1
		if confirm:
			but = console.alert('Are you sure?', 'Would you like to delete the docset, ' +  stackoverflow.name, 'Ok')
		if but == 1:
			dbmanager = DBManager.DBManager()
			dbmanager.DocsetRemoved(stackoverflow.id)
			shutil.rmtree(stackoverflow.path)
			stackoverflow.status = 'online'
			if not post_action == None:
				post_action()
			stackoverflow.path = None
	
	def getTypesForStackOverflow(self, stackoverflow):
		types = []
		path = stackoverflow.path
		indexPath = os.path.join(path, self.indexPath)
		conn = sqlite3.connect(indexPath)
		sql = 'SELECT type FROM searchIndex GROUP BY type ORDER BY type COLLATE NOCASE'
		c = conn.execute(sql)
		data = c.fetchall()
		conn.close()
		for t in data:
			types.append(self.typeManager.getTypeForName(t[0]))
		return types
	
	def getIndexesbyTypeForStackOverflow(self, stackoverflow, type):
		indexes = []
		path = stackoverflow.path
		indexPath = os.path.join(path, self.indexPath)
		conn = sqlite3.connect(indexPath)
		sql = 'SELECT type, name, path FROM searchIndex WHERE type = (?) ORDER BY name COLLATE NOCASE'
		c = conn.execute(sql, (type.name,))
		data = c.fetchall()
		conn.close()
		dTypes ={}
		type = None
		for t in data:
			if t[0] in dTypes.keys():
				type= dTypes[t[0]]
			else:
				type = self.typeManager.getTypeForName(t[0])
				dTypes[t[0]] = type
			indexes.append({'type':type, 'name':t[1],'path':t[2]})
		return indexes
	
	def getIndexesbyTypeAndNameForDocset(self, stackoverflow, typeName, name):
		indexes = []
		path = stackoverflow.path
		indexPath = os.path.join(path, self.indexPath)
		conn = sqlite3.connect(indexPath)
		sql = 'SELECT type, name, path FROM searchIndex WHERE type = (?) AND name LIKE (?) ORDER BY name COLLATE NOCASE'
		c = conn.execute(sql, (typeName, name,))
		data = c.fetchall()
		conn.close()
		dTypes = {}
		type = None
		for t in data:
			if t[0] in dTypes.keys():
				type= dTypes[t[0]]
			else:
				type = self.typeManager.getTypeForName(t[0])
				dTypes[t[0]] = type
			indexes.append({'type':type, 'name':t[1],'path':t[2]})
		return indexes
		
	def getIndexesByNameForDocset(self, stackoverflow, name):
		indexes = []
		path = stackoverflow.path
		indexPath = os.path.join(path, self.indexPath)
		conn = sqlite3.connect(indexPath)
		sql = 'SELECT type, name, path FROM searchIndex WHERE name LIKE (?) ORDER BY name COLLATE NOCASE'
		c = conn.execute(sql, (name,))
		data = c.fetchall()
		conn.close()
		dTypes = {}
		type = None
		for t in data:
			if t[0] in dTypes.keys():
				type= dTypes[t[0]]
			else:
				type = self.typeManager.getTypeForName(t[0])
				dTypes[t[0]] = type
			indexes.append({'type':type, 'name':t[1],'path':t[2]})
		return indexes
	
	def getIndexesForStackOverflow(self, stackoverflow):
		indexes = []
		path = stackoverflow.path
		indexPath = os.path.join(path, self.indexPath)
		conn = sqlite3.connect(indexPath)
		sql = 'SELECT type, name, path FROM searchIndex ORDER BY name COLLATE NOCASE'
		c = conn.execute(sql)
		data = c.fetchall()
		conn.close()
		dTypes = {}
		type = None
		for t in data:
			if t[0] in dTypes.keys():
				type= dTypes[t[0]]
			else:
				type = self.typeManager.getTypeForName(t[0])
				dTypes[t[0]] = type
			indexes.append({'type':type, 'name':t[1],'path':t[2]})
		return types
		
	def getIndexesbyNameForAllStackOverflow(self, name):
		if name == None or name == '':
			return {}
		else:
			docsets = self.getDownloadedStackOverflows()
			indexes = {}
			for d in docsets:
				ind = self.getIndexesbyNameForDocsetSearch(d, name)
				for k in ind:
					if not k in indexes.keys():
						indexes[k] = []
					indexes[k].extend(ind[k])
			return indexes
			
	def getIndexesbyNameForDocsetSearch(self, docset, name):
		if name == None or name == '':
			return []
		else:
			ind = {}
			path = docset.path
			indexPath = os.path.join(path, self.indexPath)
			conn = sqlite3.connect(indexPath)
			sql = 'SELECT type, name, path FROM searchIndex WHERE name LIKE (?) ORDER BY name COLLATE NOCASE'
			c = conn.execute(sql, (name, ))
			data = {'first' : c.fetchall()}

			sql = 'SELECT type, name, path FROM searchIndex WHERE name LIKE (?) AND name NOT LIKE (?) ORDER BY name COLLATE NOCASE'
			c = conn.execute(sql, (name.replace(' ','%'), name, ))
			data['second'] = c.fetchall()
						
			sql = 'SELECT type, name, path FROM searchIndex WHERE name LIKE (?) AND name NOT LIKE (?) AND name NOT LIKE (?) ORDER BY name COLLATE NOCASE'
			c = conn.execute(sql, (name.replace(' ','%')+'%', name.replace(' ','%'), name, ))
			data['third'] = c.fetchall()
			
			sql = 'SELECT type, name, path FROM searchIndex WHERE name LIKE (?) AND name NOT LIKE (?) AND name NOT LIKE (?) AND name NOT LIKE (?) ORDER BY name COLLATE NOCASE'
			c = conn.execute(sql, ('%'+name.replace(' ','%')+'%',name.replace(' ','%')+'%',name.replace(' ','%'), name, ))
			data['fourth'] = c.fetchall()
						
									
			conn.close()
			dTypes = {}
			for k in data:
				ind[k] = []
				for t in data[k]:
					callbackOverride = ''
					if docset.type == 'Online':
						url = t[2]
						url = url.replace(' ', '%20')
					else:
						url = t[2]
						callbackOverride = 'sooffline'
					type = None
					if t[0] in dTypes.keys():
						type= dTypes[t[0]]
					else:
						type = self.typeManager.getTypeForName(t[0])
						dTypes[t[0]] = type
					head, _sep, tail = docset.name.rpartition(docset.type)
					ind[k].append({'name':t[1], 'path':url, 'icon':docset.image,'docsetname':head + tail,'type':type, 'callbackOverride':callbackOverride, 'docset': docset})
			return ind
	
	def buildOfflineDocsetHtml(self, entry, docset):
		indexPath = os.path.join(docset.path, self.indexPath)
		id = entry['path'].split('#',1)[0].replace('dash-stack://','')
		conn = sqlite3.connect(indexPath)
		questionSql = 'SELECT body, score, owneruserid, creationdate, acceptedanswerid FROM Posts WHERE ID = (?)'
		c = conn.execute(questionSql, (id,))
		question = c.fetchall()
		if len(question) > 0:
			question = question[0]
		else:
			question = ['','','']
		

		questionUserSql = 'SELECT DisplayName, AccountId FROM Users WHERE ID = (?)'
		c = conn.execute(questionUserSql, (question[2],))
		questionUser = c.fetchall()
		if len(questionUser) > 0:
			questionUser = questionUser[0]
		else:
			questionUser = ['[Deleted User]','','']
 
		acceptedAnswerSql = 'SELECT body, id, score, owneruserid, creationdate FROM Posts WHERE Id = (?)'
		c = conn.execute(acceptedAnswerSql, (question[4],))
		acceptedAnswer = c.fetchall()
		
		
		answerSql = 'SELECT body, id, score, owneruserid, creationdate FROM Posts WHERE ParentId = (?) and id != (?)'
		c = conn.execute(answerSql, (id,question[4],))
		answers = c.fetchall()
		
		commentsSql = 'SELECT text, creationdate, userid FROM comments WHERE PostId = (?) ORDER BY creationdate'
		
		with open('Resources/header.html', 'rb') as f:
			header = f.read().decode('utf8')

		with open('Resources/body.html', 'rb') as f:
			bodyTemplate = f.read().decode('utf8')
		
		with open('Resources/answers.html', 'rb') as f:
			answerTemplate = f.read().decode('utf8')
		
		with open('Resources/AcceptedAnswer.html', 'rb') as f:
			acceptedAnswerTemplate = f.read().decode('utf8')
		
		with open('Resources/comments.html', 'rb') as f:
			commentsTemplate = f.read().decode('utf8')
		
		questionTime = time.strftime('%d-%b-%Y at %H:%M:%S', time.gmtime(question[3]))
			
		body = header
		body += bodyTemplate.replace('{{{PostBody}}}', question[0]).replace('{{{Title}}}', entry['name']).replace('{{{AnswerCount}}}', str(len(answers)+len(acceptedAnswer))).replace('{{{Id}}}', str(id)).replace('{{{PostScore}}}', str(question[1])).replace('{{{PostOwnerDisplayName}}}', str(questionUser[0])).replace('{{{PostOwnerId}}}', str(question[2])).replace('{{{PostDateTime}}}', str(questionTime))
		
		answerData = ''
		if len(acceptedAnswer) > 0:
			aad = acceptedAnswer[0]
			acceptedAnswerTime = time.strftime('%d-%b-%Y at %H:%M:%S', time.gmtime(aad[4]))
			c = conn.execute(questionUserSql, (aad[3],))
			acceptedAnswerUser = c.fetchall()
			if len(acceptedAnswerUser) > 0:
				acceptedAnswerUser = acceptedAnswerUser[0]
			else:
				acceptedAnswerUser = ['[Deleted User]','','']
 
			c = conn.execute(commentsSql, (aad[1],))
			comments = c.fetchall()
			commentData = ''
			for comment in comments:
				c = conn.execute(questionUserSql, (comment[2],))
				commentUser = c.fetchall()
				if len(commentUser) > 0:
					commentUser = commentUser[0]
				else:
					commentUser = ['[Deleted User]','','']
 
				
				commentTime = time.strftime('%d-%b-%Y at %H:%M:%S', time.gmtime(comment[1]))
				commentData += commentsTemplate.replace('{{{CommentBody}}}', comment[0]).replace('{{{CommentOwnerId}}}', str(comment[2])).replace('{{{CommentDisplayname}}}',commentUser[0]).replace('{{{CommentDateTime}}}',str(commentTime))
			aa = answerTemplate.replace('{{{AnswerScore}}}', str(aad[2])).replace('{{{AcceptedAnswer}}}', acceptedAnswerTemplate).replace('{{{AnswerDateTime}}}', str(acceptedAnswerTime)).replace('{{{AnswerBody}}}', aad[0]).replace('{{{AnswerDisplayName}}}',acceptedAnswerUser[0]).replace('{{{AnswerOwnerId}}}', str(aad[3])).replace('{{{Comments}}}', commentData)
			answerData = aa
		for answer in answers:
			answerTime = time.strftime('%d-%b-%Y at %H:%M:%S', time.gmtime(answer[4]))
			c = conn.execute(questionUserSql, (answer[3],))
			answerUser = c.fetchall()
			if len(answerUser) > 0:
				answerUser = answerUser[0]
			else:
				answerUser = ['[Deleted User]','','']
			c = conn.execute(commentsSql, (answer[1],))
			comments = c.fetchall()
			commentData = ''
			for comment in comments:
				c = conn.execute(questionUserSql, (comment[2],))
				commentUser = c.fetchall()
				if len(commentUser) > 0:
					commentUser = commentUser[0]
				else:
					commentUser = ['[Deleted User]','','']
				commentTime = time.strftime('%d-%b-%Y at %H:%M:%S', time.gmtime(comment[1]))
				commentData += commentsTemplate.replace('{{{CommentBody}}}', comment[0]).replace('{{{CommentOwnerId}}}', str(comment[2])).replace('{{{CommentDisplayname}}}',commentUser[0]).replace('{{{CommentDateTime}}}',str(commentTime))
			answerData += answerTemplate.replace('{{{AnswerScore}}}', str(answer[2])).replace('{{{AcceptedAnswer}}}', ' ').replace('{{{AnswerDateTime}}}', str(answerTime)).replace('{{{AnswerBody}}}', answer[0]).replace('{{{AnswerDisplayName}}}',answerUser[0]).replace('{{{AnswerOwnerId}}}', str(answer[3])).replace('{{{Comments}}}', commentData)
			
			
		body += answerData
		body += '</body></html>'
		conn.close()
		# return '<html><body>' + body + '</body</html>' 
		return body class WebSearchManager (object):
	def __init__(self, typeIconPath):
		self.docsetFolder = 'Docsets/WebSearch'
		self.docsetIndexFileName = os.path.join(self.docsetFolder, 'WebSearch.db')
		self.typeManager = TypeManager.TypeManager(typeIconPath)
		self.connection = None
		self.__createDocsetFolder()
		self.__createInitialIndex()
	
	def __createDocsetFolder(self):
		if not os.path.exists(self.docsetFolder):
			os.mkdir(self.docsetFolder)
	
	def __createInitialIndex(self):
		initCreate = not os.path.exists(self.docsetIndexFileName)
		self.connection = sqlite3.connect(self.docsetIndexFileName)
		c = self.connection.cursor()
		c.execute('CREATE TABLE IF NOT EXISTS websearch(ID INTEGER PRIMARY KEY AUTOINCREMENT, Name TEXT NOT NULL, Url TEXT NOT NULL, Enabled Integer NOT NULL);')
		self.connection.commit()
		if initCreate:
			self.AddWebSearch('Google','https://www.google.com/search?q={query}')
			self.AddWebSearch('DuckDuckGo','https://duckduckgo.com/?q={query}&ia=web')
			self.AddWebSearch('Stack Overflow','https://stackoverflow.com/search?q={query}')
	
	def AddWebSearch(self, name, url):
		searches = self.GetWebSearches()
		for search in searches:
			if search[1] == name:
				return False, 'Name ' +name+' already exists in database, please choose another name.'
		parsedurl = urllib.parse.urlparse(url)
		scheme = parsedurl.scheme
		if scheme == '':
			return False, 'Invalid URL ' + url + ', No scheme found, please include a scheme, eg http://'
		hostName = parsedurl.netloc
		if hostName == '':
			return False, 'Invalid URL ' + url + ' no host name could be determined'
		qurl = scheme + '://' + hostName + parsedurl.path +'?' + urllib.parse.quote(parsedurl.query,'/=&')
		qurl = qurl.replace('%7Bquery%7D','{query}')
		c = self.connection.cursor()
		c.execute('INSERT INTO websearch (Name, Url, Enabled) VALUES (?,?,?)',(name,qurl,1,))
		self.connection.commit()
		self.SaveIconForWebSearch(c.lastrowid, qurl)
		return True, ''
	
	def SaveIconForWebSearch(self, id, url):
		parsedurl = urllib.parse.urlparse(url)
		scheme = parsedurl.scheme
		if scheme == '':
			return False, 'Invalid URL ' + url + ', No scheme found, please include a scheme, eg http://'
		hostName = parsedurl.netloc
		if hostName == '':
			return False, 'Invalid URL ' + url + ' no host name could be determined'
		faviconurl = scheme + '://' + hostName + '/favicon.ico'
		path = os.path.join(self.docsetFolder, str(id)+'.ico')
		try:
			r = requests.get(faviconurl, stream=True)
			if r.status_code == 200:
				with open(path, 'wb') as f:
					for chunk in r:
						f.write(chunk)
				basewidth = 48
				img = Image.open(path)
				wpercent = (basewidth/float(img.size[0]))
				hsize = int((float(img.size[1])*float(wpercent)))
				img = img.resize((basewidth,hsize), Image.ANTIALIAS)
				pathx = path.replace('.ico','')
				pathx = pathx + '@3x.ico'
				basewidth = 32
				img.save(pathx)
				img = Image.open(path)
				wpercent = (basewidth/float(img.size[0]))
				hsize = int((float(img.size[1])*float(wpercent)))
				img = img.resize((basewidth,hsize), Image.ANTIALIAS)
				pathx = path.replace('.ico','')
				pathx = pathx + '@2x.ico'
				img.save(pathx) 
				basewidth = 16
				img = Image.open(path)
				wpercent = (basewidth/float(img.size[0]))
				hsize = int((float(img.size[1])*float(wpercent)))
				img = img.resize((basewidth,hsize), Image.ANTIALIAS)
				img.save(path)
		except requests.RequestException as e:
			pass
		
	
	def RemoveWebSearch(self, id):
		c = self.connection.cursor()
		c.execute('DELETE FROM websearch WHERE ID = (?)',(id,))
		self.connection.commit()
		path = os.path.join(self.docsetFolder, str(id))
		if os.path.exists(path+'.ico'):
			os.remove(path+'.ico')
		if os.path.exists(path+'@2x.ico'):
			os.remove(path+'@2x.ico')
		if os.path.exists(path+'@3x.ico'):
			os.remove(path+'@3x.ico')
	
	def GetWebSearches(self, OnlyEnabled=False):
		connection = sqlite3.connect(self.docsetIndexFileName)
		if not OnlyEnabled:
			return connection.execute('SELECT * FROM websearch').fetchall()
		else:
			return connection.execute('SELECT * FROM websearch WHERE Enabled = 1')
	
	def EnableWebSearch(self, id):
		c = self.connection.cursor()
		c.execute('UPDATE websearch SET Enabled = 1 WHERE id = (?)',(id,))
		self.connection.commit()
	
	def DisableWebSearch(self, id):
		c = self.connection.cursor()
		c.execute('UPDATE websearch SET Enabled = 0 WHERE id = (?)',(id,))
		self.connection.commit()
	
	def GetAllWebSearches(self, name):
		if name == None or name == '':
			return []
		else:
			searches = self.GetWebSearches(True)
			indexes = []
			for s in searches:
				ind = []
				n = urllib.parse.quote(name)
				url = s[2].replace('{query}', n)
				#url = url.replace(' ', '%20')
				type = None
				dTypes={}
				img = None
				imgPath = os.path.join(self.docsetFolder, str(s[0])+'.ico')
				if os.path.exists(imgPath):
					img = ui.Image.named(imgPath)
				if 'Entry' in dTypes.keys():
					type= dTypes['Entry']
				else:
					type = self.typeManager.getTypeForName('Entry')
					dTypes['Entry'] = type
				ind.append({'name':s[1], 'path':url, 'icon':img,'docsetname':s[1],'type':type, 'callbackOverride':'', 'docset': None})
				indexes.extend(ind)
			return indexes class TransferManager (object):
	def __init__(self, iconPath, typeIconPath):
		self.server = None
		self.running = False
		self.typeManager = TypeManager.TypeManager(typeIconPath)
		self.iconPath = iconPath
		self.docsetFolder = 'Docsets/Transfer'
		self.plistPath = 'Contents/Info.plist'
		self.indexPath = 'Contents/Resources/docSet.dsidx'
		self.installThreads = []
		self.uiUpdateThreads = []
		self.__installingDocsets = []
		
		self.__createDocsetFolder()
	def __createDocsetFolder(self):
		if not os.path.exists(self.docsetFolder):
			os.mkdir(self.docsetFolder)
	
	def startTransferService(self, template_directory, file_upload_directory, port, callback):
		self.server = ServerThread(app, template_directory, file_upload_directory, port, callback)
		self.server.start()
		self.running = True
		ip = 'Unknown'
		try:
			ip = socket.gethostbyname(socket.gethostname())
		except socket.gaierror:
			s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
			s.connect(("8.8.8.8", 80))
			ip = s.getsockname()[0]
			s.close()
		return {'hostname':socket.gethostname(), 'port':port, 'ip':ip}

	def stopTransferService(self, action):
		self.server.shutdown()
		self.server = None
		self.running = False
		action()

	def __installDocset(self, docset, refresh):
		extract_location = self.docsetFolder
		docset.stats = 'Preparing to install: This might take a while.'
		refresh()
		zip = ZipFile(docset.zipPath, mode='r')
		ll = [name for name in zip.namelist() if '.docset' in name]
		if len(ll) > 0:
			n = ll[0]
			m = os.path.join(self.docsetFolder, n)
			docset.stats = 'Preparing to extract'
			refresh()
			l = zip.infolist()
			zip.extractall(path=extract_location, members = self.track_progress(l, docset, len(l), refresh))
			zip.close()
			os.remove(docset.zipPath)
			plistpath = os.path.join(m, self.plistPath)
			name = docset.name
			image = ''
			with open(plistpath, 'rb') as f:
				pp = plistlib.load(f)
				if 'CFBundleName' in pp.keys():
					name = pp['CFBundleName']
				if 'CFBundleIdentifier' in pp.keys():
					image = pp['CFBundleIdentifier']
			dbManager = DBManager.DBManager()
			dbManager.DocsetInstalled(name, m, 'transfer', image, 0.0)
			if docset in self.__installingDocsets:
				self.__installingDocsets.remove(docset)
			docset.status = 'Cleaning up...'
			refresh() 
			cleanup_path = os.path.join(self.docsetFolder,'__MACOSX') 
			if os.path.exists(cleanup_path):
				shutil.rmtree(cleanup_path)
			docset.status = 'Installed'
			refresh()
		else:
			raise Exception('Unknown docset structure')
	
	def track_progress(self, members, docset, totalFiles, refresh):
		i = 0
		for member in members:
			i = i + 1
			done = 100 * i / totalFiles
			docset.stats = 'installing: ' + str(round(done,2)) + '% ' + str(i) + ' / '+ str(totalFiles) 
			if i % 100 == 0:
				refresh()
			yield member
		refresh()
	
	def installDocset(self, docset, action, refresh_main_view):
		self.__installingDocsets.append(docset)
		docset.status = 'Installing'
		action()
		installThread = LogThread.LogThread(target=self.__installDocset, args=(docset,refresh_main_view,))
		self.installThreads.append(installThread)
		installThread.start()
		# updateThread = LogThread.LogThread(target=self.updateUi, args=(action,installThread,))
		# self.uiUpdateThreads.append(updateThread)
		# updateThread.start()
	
	def deleteDocset(self, docset, post_action, confirm = True):
		but = 1
		if confirm:
			but = console.alert('Are you sure?', 'Would you like to delete the docset, ' +  docset.name, 'Ok')
		if but == 1:
			dbmanager = DBManager.DBManager()
			dbmanager.DocsetRemoved(docset.id)
			shutil.rmtree(docset.path)
			docset.status = 'Not Installed'
			if not post_action == None:
				post_action()
			docset.path = None
	
	def __getIconWithName(self, name):
		imgPath = os.path.join(os.path.abspath('.'), self.iconPath, name+'.png')
		if not os.path.exists(imgPath):
			imgPath = os.path.join(os.path.abspath('.'), self.iconPath, 'Other.png')
		return ui.Image.named(imgPath)
	
	def __getAvailableDocsets(self):
		d = []
		for file in os.listdir(self.docsetFolder):
			if file.endswith('.zip') or file.endswith('.ZIP'):
				doc = Transfer()
				doc.name = file
				doc.status = 'Not Installed'
				doc.zipPath = os.path.join(os.path.abspath('.'), self.docsetFolder, file)
				d.append(doc)
		return d
	
	def __getInstallingDocsets(self):
		return self.__installingDocsets
		
	def __getInstalledDocsets(self):
		ds = []
		dbManager = DBManager.DBManager()
		t = dbManager.InstalledDocsetsByType('transfer')
		ds = []
		for d in t:
			aa = Transfer()
			aa.name = d[1]
			aa.id = d[0]
			aa.path = os.path.join(os.path.abspath('.'),d[2])
			aa.image = self.__getIconWithName(d[4])
			aa.status = 'Installed'
			# aa.version = d[5]
			ds.append(aa)
		return ds
		
	def getInstalledDocsets(self):
		return self.__getInstalledDocsets()
	
	def getAvailableDocsets(self):
		dic = {}
		ava = self.__getAvailableDocsets()
		ins = self.__getInstallingDocsets()
		inst = self.getInstalledDocsets()
		if len(ins) > 0:
			for i in ins:
				for a in ava:
					if i.name == a.name:
						a.status = 'Installing'
						a.stats = i.stats
		if len(ava) > 0:
			dic['Available'] = ava
		if len(inst) > 0:
			dic['Installed'] = inst
		return dic
	
	def getIndexesbyTypeForDocset(self, docset, type):
		indexes = []
		path = docset.path
		indexPath = os.path.join(path, self.indexPath)
		conn = sqlite3.connect(indexPath)
		sql = 'SELECT type, name, path FROM searchIndex WHERE type = (?) ORDER BY name COLLATE NOCASE'
		c = conn.execute(sql, (type.name,))
		data = c.fetchall()
		conn.close()
		dTypes ={}
		type = None		
		for t in data:
			if t[0] in dTypes.keys():
				type= dTypes[t[0]]
			else:
				type = self.typeManager.getTypeForName(t[0])
				dTypes[t[0]] = type
			indexes.append({'type':type, 'name':t[1],'path':t[2]})
		return indexes
	
	def getTypesForDocset(self, docset):
		types = []
		path = docset.path
		indexPath = os.path.join(path, self.indexPath)
		conn = sqlite3.connect(indexPath)
		sql = 'SELECT type FROM searchIndex GROUP BY type ORDER BY type COLLATE NOCASE'
		c = conn.execute(sql)
		data = c.fetchall()
		conn.close()
		for t in data:
			types.append(self.typeManager.getTypeForName(t[0]))
		return types
	
	def updateUi(self, action, t):
		while t.is_alive():
			action()
			time.sleep(0.5)
		action()
	
	def getIndexesbyNameForAllDocsets(self, name):
		if name == None or name == '':
			return {}
		else:
			docsets = self.getInstalledDocsets()
			indexes = {}
			for d in docsets:
				ind = self.getIndexesbyNameForDocsetSearch(d, name)
				for k in ind:
					if not k in indexes.keys():
						indexes[k] = []
					indexes[k].extend(ind[k])
			return indexes
			
	def getIndexesbyNameForDocsetSearch(self, docset, name):
		if name == None or name == '':
			return []
		else:
			ind = {}
			path = docset.path
			indexPath = os.path.join(path, self.indexPath)
			conn = sqlite3.connect(indexPath)
			sql = 'SELECT type, name, path FROM searchIndex WHERE name LIKE (?) ORDER BY name COLLATE NOCASE'
			c = conn.execute(sql, (name, ))
			data = {'first' : c.fetchall()}

			sql = 'SELECT type, name, path FROM searchIndex WHERE name LIKE (?) AND name NOT LIKE (?) ORDER BY name COLLATE NOCASE'
			c = conn.execute(sql, (name.replace(' ','%'), name, ))
			data['second'] = c.fetchall()
						
			sql = 'SELECT type, name, path FROM searchIndex WHERE name LIKE (?) AND name NOT LIKE (?) AND name NOT LIKE (?) ORDER BY name COLLATE NOCASE'
			c = conn.execute(sql, (name.replace(' ','%')+'%', name.replace(' ','%'), name, ))
			data['third'] = c.fetchall()
			
			sql = 'SELECT type, name, path FROM searchIndex WHERE name LIKE (?) AND name NOT LIKE (?) AND name NOT LIKE (?) AND name NOT LIKE (?) ORDER BY name COLLATE NOCASE'
			c = conn.execute(sql, ('%'+name.replace(' ','%')+'%',name.replace(' ','%')+'%',name.replace(' ','%'), name, ))
			data['fourth'] = c.fetchall()
						
									
			conn.close()
			dTypes = {}
			for k in data:
				ind[k] = []
				for t in data[k]:
					url = 'file://' + os.path.join(path, 'Contents/Resources/Documents', t[2])
					
					url = url.replace(' ', '%20').replace('<', '%3E').replace('>', '%3C')
					type = None
					if t[0] in dTypes.keys():
						type= dTypes[t[0]]
					else:
						type = self.typeManager.getTypeForName(t[0])
						dTypes[t[0]] = type
					ind[k].append({'name':t[1], 'path':url, 'icon':None,'docsetname':docset.name,'type':type, 'callbackOverride':'', 'docset': docset})
			return ind class MigrationManager (object):
	def __init__(self, dbmanager, docsetfolder):
		self.dbmanager = dbmanager 
		self.migrations = {'change_version_column_to_text':self.change_version_column_to_text}
		self.docsetfolder = docsetfolder

		
	def perform_migrations(self):
		for migration in self.migrations.keys():
			if self.check_migration_required(migration):
				action = self.migrations[migration]
				try:
					action()
					self.dbmanager.UpdateMigration(migration, True)
				except:
					self.dbmanager.UpdateMigration(migration, False)
					
	def change_version_column_to_text(self):
		req = len(self.dbmanager.InstalledDocsets()) > 0
		if req:
			console.hide_activity()
			console.alert('Breaking Change', 'A breaking database change is required. All docsets need to be removed.', 'Ok', hide_cancel_button=True)
			console.show_activity('Performing migrations')
			df = os.path.join(os.path.abspath('.'),self.docsetfolder)
			shutil.rmtree(df)
		if not os.path.exists(os.path.join(os.path.abspath('.'),self.docsetfolder)):
			os.mkdir(os.path.join(os.path.abspath('.'),self.docsetfolder))
		self.dbmanager.RunQueryOnDocsetDB('DROP TABLE IF EXISTS docsets;')
		self.dbmanager.RunQueryOnDocsetDB('CREATE TABLE IF NOT EXISTS docsets(ID INTEGER PRIMARY KEY AUTOINCREMENT, Name TEXT NOT NULL, Path TEXT NOT NULL, Type TEXT NOT NULL, Icon TEXT NOT NULL, Version TEXT NULL, OtherData TEXT NOT NULL);')
	
	def check_migration_required(self, name):
		dbmigration = self.dbmanager.GetMigration(name)
		if dbmigration == None:
			self.dbmanager.AddMigration(name)
			return True
		else:
			return not dbmigration[2] class SettingsManager (object):
	def __init__(self):
		self.settingsStoreLoaction = '.settings'
		self.settings = {}
		self.load_settings()
		self.set_defaults()
		self.save_settings()
	
	def set_defaults(self):
		if not 'ui_style' in self.settings.keys():
			self.settings['ui_style'] = 'default'
			
	def load_settings(self):
		if os.path.exists(self.settingsStoreLoaction):
			with open(self.settingsStoreLoaction, 'r') as set:
				self.settings = yaml.load(set)
			
	def save_settings(self):
		with open(self.settingsStoreLoaction, 'w') as yaml_file:
			yaml.dump(self.settings, yaml_file, default_flow_style=False) class DocsetManagementView (object):
	def __init__(self, docsets, download_action, refresh_docsets_action, delete_action,refresh_main_view, theme_manager, show_versions_view):
		self.data = docsets
		self.download_action = download_action
		self.refresh_docsets_action = refresh_docsets_action
		self.delete_action = delete_action
		self.refresh_main_view = refresh_main_view	
		self.theme_manager = theme_manager
		self.show_versions_view = show_versions_view
		
	def tableview_did_select(self, tableview, section, row):
		if self.data[row]['hasVersions']:
			self.show_versions_view(self.data[row])
		
	def tableview_number_of_sections(self, tableview):
		return 1
		
	def tableview_number_of_rows(self, tableview, section):
		return len(self.data)
		
	def tableview_cell_for_row(self, tableview, section, row):
		status = self.data[row]['status']
		cell = ui.TableViewCell('subtitle')
		cell.text_label.text = self.data[row]['name']
		cell.border_color = self.theme_manager.currentTheme.tintColour
		cell.background_color = self.theme_manager.currentTheme.backgroundColour
		cell.bar_tint_color = self.theme_manager.currentTheme.tintColour
		cell.bg_color = self.theme_manager.currentTheme.backgroundColour
		cell.tint_color = self.theme_manager.currentTheme.tintColour
		cell.text_label.text_color = self.theme_manager.currentTheme.textColour
		cell.detail_text_label.text_color = self.theme_manager.currentTheme.subTextColour
		if not self.data[row]['hasVersions']:
			if not status == 'downloading' or not 'stats' in self.data[row].keys():
				cell.detail_text_label.text = status
			else:
				cell.detail_text_label.text = self.data[row]['stats']
		if not self.data[row]['image'] == None:
			cell.image_view.image = self.data[row]['image']
		if self.data[row]['hasVersions']:
			cell.accessory_type = 'disclosure_indicator'
		else:
			iv = self.__getDetailButtonForStatus(status, cell.height, self.action, self.data[row])
			iv.x = cell.content_view.width - (iv.width * 1.5)
			iv.y = (cell.content_view.height) - (iv.height * 1.05)
			iv.flex = 'L'
			cell.content_view.add_subview(iv)
			cell.selectable = False
		return cell
		
	def __getDetailImageForStatus(self, status):
		if status == 'online' or status == 'Update Available':
			return 'iob:ios7_cloud_download_outline_24'
		else:
			return 'iob:ios7_close_outline_24'
			
	def __getDetailButtonForStatus(self, status, height, action, row):
		img = ui.Image.named(self.__getDetailImageForStatus(status))
		button = ui.Button()
		button.image = img
		size = img.size
		ratio = size.y / size.x
		button.height = height * 0.9
		button.width = button.height * ratio
		ca = CustomAction(button)
		ca.action = self.action
		ca.row = row
		button.action = ca
		return button

	def refresh_all_views(self):
		self.refresh_main_view()
		d = self.refresh_docsets_action()
		refresh_view(d)
	
	@ui.in_background
	def action(self, sender):
		if sender.action.row['status'] == 'Update Available':
			sender.action.row['status'] = 'removing...'
			self.refresh()
			self.delete_action(sender.action.row, None, False)
			sender.action.row['path'] = None
			self.download_action(sender.action.row, self.refresh, self.refresh_all_views)
		else:
			if 'path' in sender.action.row and not sender.action.row['path'] == None:
				
				self.delete_action(sender.action.row, self.refresh_all_views)
				sender.action.row['path'] = None
			else:
				self.download_action(sender.action.row, self.refresh, self.refresh_all_views)
				
	def refresh(self):
		tv.reload() class SettingsView (object):
	def __init__(self, show_docset_management_view, show_cheatsheet_management_view, show_usercontributed_management_view, theme_manager, show_stackoverflow_management_view, websearch_manager, show_transfer_management_view, settings_manager):
		self.data = ['Standard Docsets', 'Cheat Sheets', 'User Contributed Docsets', 'Stack Overflow Docsets', 'Transfer Docsets']
		self.webSearch_data = ['Add Web Search', 'Enable/Disable Web Searches', 'Remove Web Search', 'Refresh Web Search Icons']
		self.ack_data = [{'text':'Dash','url':'https://kapeli.com/dash'}]
		self.updates_data = ['Check for Update', 'Reinstall Current Version', 'Install Version', 'Install Pre-release Version']
		self.ui_data = ['Change theme', 'Presentation Style']
		self.manage_docset_row = 0
		self.manage_cheatsheet_row = 1
		self.manage_usercontributed_row = 2
		self.manage_stackoverflow_row = 3
		self.manage_transfer_row = 4
		self.add_websearches_row = 0
		self.en_di_websearches_row = 1
		self.del_websearches_row = 2
		self.refresh_websearch_icons = 3
		self.show_docset_management_view = show_docset_management_view
		self.show_cheatsheet_management_view = show_cheatsheet_management_view
		self.show_usercontributed_management_view = show_usercontributed_management_view
		self.show_stackoverflow_management_view = show_stackoverflow_management_view
		self.show_transfer_management_view = show_transfer_management_view
		self.docset_section_number = 0
		self.ack_section_number = 1
		self.websearch_section_number = 2
		self.pydoc_updates_section_number = 3
		self.ui_section_number = 4
		self.updater = Updater.Updater()
		self.theme_manager = theme_manager
		self.websearch_manager = websearch_manager
		self.settings_manager = settings_manager
	
	@ui.in_background
	def tableview_did_select(self, tableview, section, row):
		if self.docset_section_number == section:
			if self.manage_docset_row == row:
				self.show_docset_management_view()
			elif self.manage_cheatsheet_row == row:
				console.show_activity('Loading Cheat Sheets...')
				uiThread = threading.Thread(target=self.show_cheatsheet_management_view)
				uiThread.start()
			elif self.manage_usercontributed_row == row:
				console.show_activity('Loading User Contributed Docsets...')
				uiThread = threading.Thread(target=self.show_usercontributed_management_view)
				uiThread.start()
			elif self.manage_stackoverflow_row == row:
				console.show_activity('Loading Stack Overflow Docsets...')
				uiThread = threading.Thread(target=self.show_stackoverflow_management_view)
				uiThread.start()
			elif self.manage_transfer_row == row:
				uiThread = threading.Thread(target=self.show_transfer_management_view)
				uiThread.start()
		if self.ack_section_number == section:
			self.open_url(self.ack_data[row]['url'])
		if self.pydoc_updates_section_number == section:
			if row == 0:
				self.updater.checkForUpdate()
			elif row == 1:
				self.updater.reinstallCurrentVersion()
			elif row == 2:
				self.updater.showAvailableVersions()
			elif row == 3:
				ret = console.alert('Warning','Pre-release versions can have bugs and be unstable, including corrupting your installation. Only install a pre-release version if you are sure. Would you like to install a pre-release version?', hide_cancel_button=True, button1='No', button2 = 'Yes')
				if ret == 2:
					self.updater.showAvailableVersions(True)
		if self.ui_section_number == section:
			if row == 0:
				themes = self.theme_manager.themes
				data = [x for x in themes.keys()]
				t = dialogs.list_dialog('Please choose your theme', data)
				if not t == None:
					if not t == self.theme_manager.themeFileName:
						self.theme_manager.saveThemeToUse(t)
						ret = console.alert('Saved', 'Please restart PyDoc for your theme change to take affect.', button1 = 'Ok', hide_cancel_button=True)
			elif row == 1:
				styles = ['default', 'panel']
				t = dialogs.list_dialog('Please choose your presentation style', styles)
				if not t == None:
					if not t == self.settings_manager.settings['ui_style']:
						self.settings_manager.settings['ui_style'] = t
						self.settings_manager.save_settings()
						ret = console.alert('Saved', 'Please restart PyDoc for your presentation to take affect.', button1 = 'Ok', hide_cancel_button=True)
		if section == self.websearch_section_number:
			if row == self.add_websearches_row:
				dialogs.alert('Info','Please include {query} in your url this will be replaced with your search query.',hide_cancel_button=True,button1='Ok')
				ok = True
				name = ''
				url = ''
				while ok:
					s = [{'title':'Name','type':'text', 'value':name},{'title':'URL','type':'text', 'autocorrection':False, 'autocapitalization':False,'value':url}]
					data = dialogs.form_dialog(title='Add Web Search',fields=s)
					if not data == None:
						ret, error = self.websearch_manager.AddWebSearch(data['Name'],data['URL'])
						if not ret:
							name = data['Name']
							url = data['URL']
							dialogs.alert('Error',error, hide_cancel_button=True, button1='Ok')
						else:
							ok = False
							dialogs.alert('Save','Web Search ' +data['Name']+' has been saved.', hide_cancel_button=True, button1='Ok')
					else:
						ok = False
					
			if row == self.en_di_websearches_row:
				searches = self.websearch_manager.GetWebSearches()
				list = []
				for search in searches:
					v = False
					if search[3] == 1:
						v = True
					s = {'key': str(search[0]),'type':'switch','value':v,'title':str(search[1])}
					list.append(s)
				data = dialogs.form_dialog(title='Enable/Disable Web Searches',fields=list)
				if not data == None:
					for v in data:
						if data[v]:
							self.websearch_manager.EnableWebSearch(v)
						else:
							self.websearch_manager.DisableWebSearch(v)
					
			if row == self.del_websearches_row:
				searches = self.websearch_manager.GetWebSearches()
				d = []
				for search in searches:
					d.append(search[1])
				data = dialogs.edit_list_dialog(title='Please swipe to delete', items=d,move=False,delete=True)
				if not data == None:
					for search in searches:
						if not search[1] in data:
							self.websearch_manager.RemoveWebSearch(search[0])
			if row == self.refresh_websearch_icons:
				console.show_activity('Saving icons...')
				searches = self.websearch_manager.GetWebSearches()
				for search in searches:
					self.websearch_manager.SaveIconForWebSearch(search[0], search[2])
				console.hide_activity()
		
	def tableview_number_of_sections(self, tableview):
		return 5
		
	def tableview_number_of_rows(self, tableview, section):
		if section == self.docset_section_number:
			return len(self.data)
		if section == self.ack_section_number:
			return len(self.ack_data)
		if section == self.pydoc_updates_section_number:
			return len(self.updates_data)
		if section == self.ui_section_number:
			return len(self.ui_data)
		if section == self.websearch_section_number:
			return len(self.webSearch_data)
		
		
	def tableview_cell_for_row(self, tableview, section, row):
		cell = ui.TableViewCell()		
		cell.border_color = self.theme_manager.currentTheme.borderColour
		cell.background_color = self.theme_manager.currentTheme.backgroundColour
		cell.bg_color = self.theme_manager.currentTheme.backgroundColour
		cell.tint_color = self.theme_manager.currentTheme.tintColour
		cell.text_label.text_color = self.theme_manager.currentTheme.textColour
		selectedBackgroundView = ui.View()
		selectedBackgroundView.background_color = self.theme_manager.currentTheme.settingsCellSelectionColour
		if not self.theme_manager.currentTheme.showSettingsCellSelection:
			selectedBackgroundView.alpha = 0
		cell.selected_background_view = selectedBackgroundView
		if section == self.docset_section_number:
			cell.text_label.text = self.data[row]
			cell.accessory_type = 'disclosure_indicator'
		elif section == self.ack_section_number:
			cell.text_label.text = self.ack_data[row]['text']
		elif section == self.pydoc_updates_section_number:
			cell.text_label.text = self.updates_data[row]
		elif section == self.ui_section_number:
			cell.text_label.text = self.ui_data[row]
		elif section == self.websearch_section_number:
			cell.text_label.text = self.webSearch_data[row]
		return cell
	
	def tableview_title_for_header(self, tableview, section):
		if section == self.docset_section_number:
			return 'Manage'
		if section == self.ack_section_number:
			return 'Docsets are provided by Dash the MacOS docset browser. Please checkout Dash please by clicking the link below.'
		if section == self.pydoc_updates_section_number:
			return 'Update PyDoc'
		if section == self.ui_section_number:
			return 'UI'
		if section == self.websearch_section_number:
			return 'Manage Web Search'
		
	def tableview_title_for_footer(self, tableview, section):
		if section == self.pydoc_updates_section_number:
			return 'Current Version - v' + self.updater.currentVersion
		elif section == self.ui_section_number:
			return 'Current Theme - ' + self.theme_manager.currentTheme.themeName + '\r\nPresentation Style - ' + self.settings_manager.settings['ui_style']
			
	def open_url(self, url):
		UIApplication = ObjCClass('UIApplication')
		sharedApplication = UIApplication.sharedApplication()
		internalurl = NSURL.URLWithString_(ns(url))
		sharedApplication.openURL_(internalurl) class DocsetListView (object):
	def __init__(self, docsets, cheatsheets, usercontributed, stackoverflows, transfers, docset_selected_callback, cheatsheet_selected_callback, usercontributed_selected_callback, stackoverflow_selected_callback, transfer_selected_callback, theme_manager):
		self.docsets = docsets
		self.cheatsheets = cheatsheets
		self.usercontributed = usercontributed
		self.stackoverflows = stackoverflows
		self.transfers = transfers
		self.docset_selected_callback = docset_selected_callback
		self.cheatsheet_selected_callback = cheatsheet_selected_callback
		self.usercontributed_selected_callback = usercontributed_selected_callback
		self.stackoverflow_selected_callback = stackoverflow_selected_callback
		self.transfer_selected_callback = transfer_selected_callback
		self.docsetSection = -1
		self.cheatsheetSection = -1
		self.usercontributedSection = -1
		self.stackoverflowSection = -1
		self.transfersSection = -1
		self.numberOfSections = 0
		self.theme_manager = theme_manager
	
		
	def tableview_did_select(self, tableview, section, row):
		if section == self.docsetSection:
			self.docset_selected_callback(self.docsets[row])
		elif section == self.cheatsheetSection:
			self.cheatsheet_selected_callback(self.cheatsheets[row])
		elif section == self.usercontributedSection:
			self.usercontributed_selected_callback(self.usercontributed[row])
		elif section == self.stackoverflowSection:
			self.stackoverflow_selected_callback(self.stackoverflows[row])
		elif section == self.transfersSection:
			self.transfer_selected_callback(self.transfers[row])
	
	def tableview_title_for_header(self, tableview, section):
		if section == self.docsetSection:
			return 'Docsets'
		elif section == self.cheatsheetSection:
			return 'Cheat Sheets'
		elif section == self.usercontributedSection:
			return 'User Contributed Docsets'	
		elif section == self.stackoverflowSection:
			return 'Stack Overflow Docsets'
		elif section == self.transfersSection:
			return 'Transferred Docsets'
		
	
	def tableview_number_of_sections(self, tableview):
		self.determineSections()
		return self.numberOfSections
		
	def tableview_number_of_rows(self, tableview, section):
		if section == self.docsetSection:
			return len(self.docsets)
		elif section == self.cheatsheetSection:
			return len(self.cheatsheets)
		elif section == self.usercontributedSection:
			return len(self.usercontributed)
		elif section == self.stackoverflowSection:
			return len(self.stackoverflows)
		elif section == self.transfersSection:
			return len(self.transfers)
		
		
	def tableview_cell_for_row(self, tableview, section, row):
		selectedBackgroundView = ui.View()
		selectedBackgroundView.background_color = self.theme_manager.currentTheme.cellSelectionColour
		if not self.theme_manager.currentTheme.showCellSelection:
			selectedBackgroundView.alpha = 0
		cell = ui.TableViewCell('subtitle')
		cell.border_color = self.theme_manager.currentTheme.tintColour
		cell.background_color = self.theme_manager.currentTheme.backgroundColour
		cell.bar_tint_color = self.theme_manager.currentTheme.toolbarBackgroundColour
		cell.bg_color = self.theme_manager.currentTheme.backgroundColour
		cell.tint_color = self.theme_manager.currentTheme.tintColour
		cell.title_color = self.theme_manager.currentTheme.tintColour
		cell.text_label.text_color = self.theme_manager.currentTheme.textColour
		cell.detail_text_label.text_color = self.theme_manager.currentTheme.subTextColour
		cell.selected_background_view = selectedBackgroundView
		if section == self.docsetSection:
			versionText = str(self.docsets[row]['version'])
			if not self.docsets[row]['hasVersions']:
				versionText = ''
			cell.detail_text_label.text = versionText
			cell.text_label.text = self.docsets[row]['name']
			cell.accessory_type = 'disclosure_indicator'
			if not self.docsets[row]['image'] == None:
				cell.image_view.image = self.docsets[row]['image']
		elif section == self.cheatsheetSection:
			cell.text_label.text = self.cheatsheets[row].name
			cell.accessory_type = 'disclosure_indicator'
			if not self.cheatsheets[row].image == None:
				cell.image_view.image = self.cheatsheets[row].image
		elif section == self.transfersSection:
			cell.text_label.text = self.transfers[row].name
			cell.accessory_type = 'disclosure_indicator'
			if not self.transfers[row].image == None:
				cell.image_view.image = self.transfers[row].image
		elif section == self.usercontributedSection:
			cell.text_label.text = self.usercontributed[row].name
			versionText = ' - ' + str(self.usercontributed[row].version)
			if not self.usercontributed[row].hasVersions:
				versionText = ''
			cell.detail_text_label.text = 'Contributed by ' + self.usercontributed[row].authorName + versionText
			cell.accessory_type = 'disclosure_indicator'
			if not self.usercontributed[row].image == None:
				cell.image_view.image = self.usercontributed[row].image
		elif section == self.stackoverflowSection:
			head, _sep, tail = self.stackoverflows[row].name.rpartition(self.stackoverflows[row].type)
			cell.text_label.text = head + tail + ' (' +self.stackoverflows[row].type + ')' + ' '
			cell.accessory_type = 'disclosure_indicator'
			if not self.stackoverflows[row].image == None:
				cell.image_view.image = self.stackoverflows[row].image
		return cell
	
	def determineSections(self):
		self.numberOfSections = 0
		if len(self.docsets) > 0:
			self.docsetSection = self.numberOfSections
			self.numberOfSections = self.numberOfSections + 1
		if len(self.cheatsheets) > 0:
			self.cheatsheetSection = self.numberOfSections
			self.numberOfSections = self.numberOfSections + 1
		if len(self.usercontributed) > 0:
			self.usercontributedSection = self.numberOfSections
			self.numberOfSections = self.numberOfSections + 1
		if len(self.stackoverflows) > 0:
			self.stackoverflowSection = self.numberOfSections
			self.numberOfSections = self.numberOfSections + 1
		if len(self.transfers) > 0:
			self.transfersSection = self.numberOfSections
			self.numberOfSections = self.numberOfSections + 1 class DocsetView (object):
	def __init__(self, theme_manager):
		self.data = []
		self.docset = None
		self.indexSelectCallback = None
		self.theme_manager = theme_manager
		
	def tableview_did_select(self, tableview, section, row):
		self.indexSelectCallback(self.docset, self.data[row])
		
	def tableview_number_of_sections(self, tableview):
		return 1
		
	def tableview_number_of_rows(self, tableview, section):
		return len(self.data)
		
	def tableview_cell_for_row(self, tableview, section, row):
		cell = ui.TableViewCell()
		cell.border_color = self.theme_manager.currentTheme.tintColour
		cell.background_color = self.theme_manager.currentTheme.backgroundColour
		cell.bar_tint_color = self.theme_manager.currentTheme.toolbarBackgroundColour
		cell.bg_color = self.theme_manager.currentTheme.backgroundColour
		cell.tint_color = self.theme_manager.currentTheme.tintColour
		cell.title_color = self.theme_manager.currentTheme.tintColour
		cell.text_label.text_color = self.theme_manager.currentTheme.textColour
		cell.text_label.text = self.data[row].plural
		cell.accessory_type = 'disclosure_indicator'
		selectedBackgroundView = ui.View()
		selectedBackgroundView.background_color = self.theme_manager.currentTheme.cellSelectionColour
		if not self.theme_manager.currentTheme.showCellSelection:
			selectedBackgroundView.alpha = 0
		cell.selected_background_view = selectedBackgroundView
		if not self.data[row].icon == None:
			cell.image_view.image = self.data[row].icon
		return cell
	
	def update_with_docset(self, docset, types, indexSelectCallback):
		self.data = types
		self.docset = docset
		self.indexSelectCallback = indexSelectCallback class DocsetIndexView (object):
	def __init__(self, theme_manager, stackOverflowOnlineCallback):
		self.data = []
		self.docset = None
		self.indexSelectCallback = None
		self.docsetType = None
		self.theme_manager = theme_manager
		self.stackOverflowOnlineCallback = stackOverflowOnlineCallback
		
		
	def tableview_did_select(self, tableview, section, row):
		if self.docsetType == 'docset':
			data = 'file://' + os.path.join(self.docset['path'], 'Contents/Resources/Documents', self.data[row]['path'])
			data = data.replace(' ', '%20').replace('<', '%3E').replace('>', '%3C')
		elif self.docsetType == 'cheatsheet':
			data = 'file://' + os.path.join(self.docset.path, 'Contents/Resources/Documents', self.data[row]['path'])
			data = data.replace(' ', '%20').replace('<', '%3E').replace('>', '%3C')
		elif self.docsetType == 'usercontributed':
			data = 'file://' + os.path.join(self.docset.path, 'Contents/Resources/Documents', self.data[row]['path'])
			data = data.replace(' ', '%20').replace('<', '%3E').replace('>', '%3C')
		elif self.docsetType == 'transfer':
			data = 'file://' + os.path.join(self.docset.path, 'Contents/Resources/Documents', self.data[row]['path'])
			data = data.replace(' ', '%20').replace('<', '%3E').replace('>', '%3C')
		elif self.docsetType == 'stackoverflow' and self.docset.type == 'Offline':
			#url = 'file://' + os.path.join(self.docset.path, 'Contents/Resources/Documents', self.data[row]['path'])
			data = self.stackOverflowOnlineCallback(self.data[row], self.docset)
		elif self.docsetType == 'stackoverflow' and self.docset.type == 'Online':
			data = self.data[row]['path']
			data = data.replace(' ', '%20').replace('<', '%3E').replace('>', '%3C')
		self.indexSelectCallback(data)
		
	def tableview_number_of_sections(self, tableview):
		return 1
		
	def tableview_number_of_rows(self, tableview, section):
		return len(self.data)
		
	def tableview_cell_for_row(self, tableview, section, row):
		cell = ui.TableViewCell()
		cell.text_label.text = self.data[row]['name']
		cell.accessory_type = 'disclosure_indicator'
		cell.border_color = self.theme_manager.currentTheme.tintColour
		cell.background_color = self.theme_manager.currentTheme.backgroundColour
		cell.bar_tint_color = self.theme_manager.currentTheme.toolbarBackgroundColour
		cell.bg_color = self.theme_manager.currentTheme.backgroundColour
		cell.tint_color = self.theme_manager.currentTheme.tintColour
		cell.title_color = self.theme_manager.currentTheme.tintColour
		cell.text_label.text_color = self.theme_manager.currentTheme.textColour
		selectedBackgroundView = ui.View()
		selectedBackgroundView.background_color = self.theme_manager.currentTheme.cellSelectionColour
		if not self.theme_manager.currentTheme.showCellSelection:
			selectedBackgroundView.alpha = 0
		cell.selected_background_view = selectedBackgroundView		
		if not self.data[row]['type'].icon == None:
			cell.image_view.image = self.data[row]['type'].icon
		return cell
	
	def update_with_docset(self, docset, indexes, indexSelectCallback, docsetType):
		self.data = indexes
		self.docset = docset
		self.indexSelectCallback = indexSelectCallback
		self.docsetType = docsetType class webViewDelegate (object):
class buttonHandler (object):
	def __init__(self, invert, buttonHandler):
	def webview_should_start_load(self, webview, url, nav_type):
	def webview_did_start_load(self, webview):
	def webview_did_finish_load(self, webview):
	def webview_did_fail_load(self, webview, error_code, error_msg):
	def __init__(self, webView, tintColour):
	def reload(self, sender):
	def back(self, sender):
	def forward(self, sender):
	def stop(self, sender):
	def getReloadButtons(self):
	def getStopButtons(self):
def get_view(theme_manager): class CheatsheetManagementView (object):
	def __init__(self, download_action, refresh_main_view, delete_action, refresh_cheatsheets_action, theme_manager):
		self.data = []
		self.delete_action = delete_action
		self.download_action = download_action
		self.refresh_main_view = refresh_main_view
		self.refresh_cheatsheets_action = refresh_cheatsheets_action
		self.theme_manager = theme_manager
		
		
	def tableview_did_select(self, tableview, section, row):
		pass
		
	def tableview_number_of_sections(self, tableview):
		return 1
		
	def tableview_number_of_rows(self, tableview, section):
		return len(self.data)
		
	def tableview_cell_for_row(self, tableview, section, row):
		status = self.data[row].status
		cell = ui.TableViewCell('subtitle')
		cell.text_label.text = self.data[row].name
		cell.border_color = self.theme_manager.currentTheme.tintColour
		cell.background_color = self.theme_manager.currentTheme.backgroundColour
		cell.bar_tint_color = self.theme_manager.currentTheme.tintColour
		cell.bg_color = self.theme_manager.currentTheme.backgroundColour
		cell.tint_color = self.theme_manager.currentTheme.tintColour
		cell.text_label.text_color = self.theme_manager.currentTheme.textColour
		cell.detail_text_label.text_color = self.theme_manager.currentTheme.subTextColour
		if not status == 'downloading':
			cell.detail_text_label.text = status
		else:
			cell.detail_text_label.text = self.data[row].stats
		if not self.data[row].image == None:
			cell.image_view.image = self.data[row].image
		iv = self.__getDetailButtonForStatus(status, cell.height, self.action, self.data[row])
		iv.x = cell.content_view.width - (iv.width * 1.5)
		iv.y = (cell.content_view.height) - (iv.height * 1.05)
		iv.flex = 'L'
		cell.content_view.add_subview(iv)
		cell.selectable = False
		return cell
		
	def __getDetailImageForStatus(self, status):
		if status == 'online' or status == 'Update Available':
			return 'iob:ios7_cloud_download_outline_24'
		else:
			return 'iob:ios7_close_outline_24'
			
	def __getDetailButtonForStatus(self, status, height, action, row):
		img = ui.Image.named(self.__getDetailImageForStatus(status))
		button = ui.Button()
		button.image = img
		size = img.size
		ratio = size.y / size.x
		button.height = height * 0.9
		button.width = button.height * ratio
		ca = CustomAction(button)
		ca.action = self.action
		ca.row = row
		button.action = ca
		return button

	def refresh_all_views(self):
		self.refresh_main_view()
		d = self.refresh_cheatsheets_action()
		refresh_view(d)
						
	def action(self, sender):
		if sender.action.row.status == 'Update Available':
			sender.action.row.status = 'removing...'
			self.refresh()
			self.delete_action(sender.action.row, None, False)
			sender.action.row.path = None
			self.download_action(sender.action.row, self.refresh, self.refresh_all_views)
		else:
			if not sender.action.row.path == None:
				self.delete_action(sender.action.row, self.refresh_all_views)
				sender.action.row.path = None
			else:
				self.download_action(sender.action.row, self.refresh, self.refresh_all_views)
				
	def refresh(self):
		tv.reload() class UserContributedManagementView (object):
	def __init__(self, download_action, refresh_main_view, delete_action, refresh_usercontributed_action, theme_manager, show_versions_view):
		self.data = []
		self.delete_action = delete_action
		self.download_action = download_action
		self.refresh_main_view = refresh_main_view
		self.refresh_usercontributed_action = refresh_usercontributed_action
		self.theme_manager = theme_manager
		self.show_versions_view = show_versions_view
		
	def tableview_did_select(self, tableview, section, row):
		if self.data[row].hasVersions:
			self.show_versions_view(self.data[row])
		
	def tableview_number_of_sections(self, tableview):
		return 1
		
	def tableview_number_of_rows(self, tableview, section):
		return len(self.data)
		
	def tableview_cell_for_row(self, tableview, section, row):
		status = self.data[row].status
		cell = ui.TableViewCell('subtitle')
		cell.text_label.text = self.data[row].name
		cell.border_color = self.theme_manager.currentTheme.tintColour
		cell.background_color = self.theme_manager.currentTheme.backgroundColour
		cell.bar_tint_color = self.theme_manager.currentTheme.tintColour
		cell.bg_color = self.theme_manager.currentTheme.backgroundColour
		cell.tint_color = self.theme_manager.currentTheme.tintColour
		cell.text_label.text_color = self.theme_manager.currentTheme.textColour
		cell.detail_text_label.text_color = self.theme_manager.currentTheme.subTextColour
		cell.selectable = False
		if not self.data[row].hasVersions:
			if not status == 'downloading':
				cell.detail_text_label.text = status
			else:
				cell.detail_text_label.text = self.data[row].stats
			if not self.data[row].authorName == None:
				cell.detail_text_label.text = cell.detail_text_label.text + ' - Contributed by ' + self.data[row].authorName
			iv = self.__getDetailButtonForStatus(status, cell.height, self.action, self.data[row])
			iv.x = cell.content_view.width - (iv.width * 1.5)
			iv.y = (cell.content_view.height) - (iv.height * 1.05)
			iv.flex = 'L'
			cell.content_view.add_subview(iv)
		else:
			cell.accessory_type = 'disclosure_indicator'
		if not self.data[row].image == None:
			cell.image_view.image = self.data[row].image
		return cell
		
	def __getDetailImageForStatus(self, status):
		if status == 'online' or status == 'Update Available':
			return 'iob:ios7_cloud_download_outline_24'
		else:
			return 'iob:ios7_close_outline_24'
			
	def __getDetailButtonForStatus(self, status, height, action, row):
		img = ui.Image.named(self.__getDetailImageForStatus(status))
		button = ui.Button()
		button.image = img
		size = img.size
		ratio = size.y / size.x
		button.height = height * 0.9
		button.width = button.height * ratio
		ca = CustomAction(button)
		ca.action = self.action
		ca.row = row
		button.action = ca
		return button

	def refresh_all_views(self):
		self.refresh_main_view()
		d = self.refresh_usercontributed_action()
		refresh_view(d)
						
	@ui.in_background
	def action(self, sender):
		if sender.action.row.status == 'Update Available':
			sender.action.row.status = 'removing...'
			self.refresh()
			self.delete_action(sender.action.row, None, False)
			sender.action.row.path = None
			self.download_action(sender.action.row, self.refresh, self.refresh_all_views)
		else:
			if not sender.action.row.path == None:
				self.delete_action(sender.action.row, self.refresh_all_views)
			else:
				self.download_action(sender.action.row, self.refresh, self.refresh_all_views)
				
	def refresh(self):
		tv.reload() class StackOverflowManagementView (object):
	def __init__(self, download_action, refresh_main_view, delete_action, refresh_stackoverflow_action, theme_manager):
		self.data = []
		self.delete_action = delete_action
		self.download_action = download_action
		self.refresh_main_view = refresh_main_view
		self.refresh_stackoverflow_action = refresh_stackoverflow_action
		self.theme_manager = theme_manager
		
		
	def tableview_did_select(self, tableview, section, row):
		pass
		
	def tableview_number_of_sections(self, tableview):
		return 1
		
	def tableview_number_of_rows(self, tableview, section):
		return len(self.data)
		
	def tableview_cell_for_row(self, tableview, section, row):
		status = self.data[row].status
		cell = ui.TableViewCell('subtitle')
		cell.text_label.text = self.data[row].name + ' (' + self.data[row].type + ')'
		cell.border_color = self.theme_manager.currentTheme.tintColour
		cell.background_color = self.theme_manager.currentTheme.backgroundColour
		cell.bar_tint_color = self.theme_manager.currentTheme.tintColour
		cell.bg_color = self.theme_manager.currentTheme.backgroundColour
		cell.tint_color = self.theme_manager.currentTheme.tintColour
		cell.text_label.text_color = self.theme_manager.currentTheme.textColour
		cell.detail_text_label.text_color = self.theme_manager.currentTheme.subTextColour
		if not status == 'downloading':
			cell.detail_text_label.text = status
		else:
			cell.detail_text_label.text = self.data[row].stats
		if not self.data[row].image == None:
			cell.image_view.image = self.data[row].image
		iv = self.__getDetailButtonForStatus(status, cell.height, self.action, self.data[row])
		iv.x = cell.content_view.width - (iv.width * 1.5)
		iv.y = (cell.content_view.height) - (iv.height * 1.05)
		iv.flex = 'L'
		cell.content_view.add_subview(iv)
		cell.selectable = False
		return cell
		
	def __getDetailImageForStatus(self, status):
		if status == 'online' or status == 'Update Available':
			return 'iob:ios7_cloud_download_outline_24'
		else:
			return 'iob:ios7_close_outline_24'
			
	def __getDetailButtonForStatus(self, status, height, action, row):
		img = ui.Image.named(self.__getDetailImageForStatus(status))
		button = ui.Button()
		button.image = img
		size = img.size
		ratio = size.y / size.x
		button.height = height * 0.9
		button.width = button.height * ratio
		ca = CustomAction(button)
		ca.action = self.action
		ca.row = row
		button.action = ca
		return button

	def refresh_all_views(self):
		self.refresh_main_view()
		d = self.refresh_stackoverflow_action()
		refresh_view(d)
						
	def action(self, sender):
		if sender.action.row.status == 'Update Available':
			sender.action.row.status = 'removing...'
			self.refresh()
			self.delete_action(sender.action.row, None, False)
			sender.action.row.path = None
			self.download_action(sender.action.row, self.refresh, self.refresh_all_views)
		else:
			if not sender.action.row.path == None:
				self.delete_action(sender.action.row, self.refresh_all_views)
				sender.action.row.path = None
				#self.refresh()
			else:
				self.download_action(sender.action.row, self.refresh, self.refresh_all_views)
				
	def refresh(self):
		tv.reload() class TransferManagementView (object):
	def __init__(self, install_action, refresh_main_view, delete_action, refresh_transfer_action, theme_manager, transfer_manager, tv):
		self.data = refresh_transfer_action()
		self.delete_action = delete_action
		self.install_action = install_action
		self.refresh_main_view = refresh_main_view
		self.refresh_transfer_action = refresh_transfer_action
		self.theme_manager = theme_manager
		self.transfer_manager = transfer_manager
		self.view = tv
		button = self.getButton()
		self.view.right_button_items = [button]
		
	def getButton(self):
		title = ''
		action = None
		if self.transfer_manager.running:
			title = 'Stop Server'
			action = self.stopServer
		else:
			title = 'Start Server'
			action = self.startServer
		return ui.ButtonItem(title=title, action=action)
	
	def startServer(self, sender):
		try:
			data = self.transfer_manager.startTransferService('Resources', 'Docsets/Transfer', 8080, self.refresh_all_views)
			console.alert('Started', 'Server is available on http://' + str(data['hostname']) + ':' + str(data['port']) + ' IP:' + str(data['ip']), hide_cancel_button = True, button1='Ok')
			button = self.getButton()
			self.view.right_button_items = [button]
		except OSError as e:
			console.alert('Error', 'Unable to start http server, please restart Pythonista \r'+ e.strerror, hide_cancel_button = True, button1='Ok')

	@objc_util.on_main_thread
	def stopServer(self, sender):
		self.transfer_manager.stopTransferService(self.refresh_all_views)
		button = self.getButton()
		self.view.right_button_items = [button]
		
		
	def tableview_did_select(self, tableview, section, row):
		pass
		
	def tableview_number_of_sections(self, tableview):
		return len(self.data.keys())
		
	def tableview_number_of_rows(self, tableview, section):
		k = list(self.data.keys())[section]
		return len(self.data[k])
		
	def tableview_title_for_header(self, tableview, section):
		return list(self.data.keys())[section]
		
	def tableview_cell_for_row(self, tableview, section, row):
		k = list(self.data.keys())[section]
		cell = ui.TableViewCell('subtitle')
		cell.text_label.text = self.data[k][row].name
		cell.border_color = self.theme_manager.currentTheme.tintColour
		cell.background_color = self.theme_manager.currentTheme.backgroundColour
		cell.bar_tint_color = self.theme_manager.currentTheme.tintColour
		cell.bg_color = self.theme_manager.currentTheme.backgroundColour
		cell.tint_color = self.theme_manager.currentTheme.tintColour
		cell.text_label.text_color = self.theme_manager.currentTheme.textColour
		cell.detail_text_label.text_color = self.theme_manager.currentTheme.subTextColour
		if not self.data[k][row].status == 'Installing':
			cell.detail_text_label.text = self.data[k][row].status
		else:
			cell.detail_text_label.text = self.data[k][row].stats
		if not self.data[k][row].image == None:
			cell.image_view.image = self.data[k][row].image
		iv = self.__getDetailButtonForStatus(self.data[k][row].status, cell.height, self.action, self.data[k][row])
		iv.x = cell.content_view.width - (iv.width * 1.5)
		iv.y = (cell.content_view.height) - (iv.height * 1.05)
		iv.flex = 'L'
		cell.content_view.add_subview(iv)
		cell.selectable = False
		return cell
		
	def __getDetailImageForStatus(self, status):
		if status == 'Not Installed':
			return 'iob:ios7_cloud_download_outline_24'
		else:
			return 'iob:ios7_close_outline_24'
			
	def __getDetailButtonForStatus(self, status, height, action, row):
		img = ui.Image.named(self.__getDetailImageForStatus(status))
		button = ui.Button()
		button.image = img
		size = img.size
		ratio = size.y / size.x
		button.height = height * 0.9
		button.width = button.height * ratio
		ca = CustomAction(button)
		ca.action = self.action
		ca.row = row
		button.action = ca
		return button

	def refresh_all_views(self):
		self.refresh_main_view()
		d = self.refresh_transfer_action()
		self.data = d
		self.view.reload()
	
	@ui.in_background				
	def action(self, sender):
		if not sender.action.row.path == None:
			sender.action.row.status = 'removing...'
			self.refresh()
			self.delete_action(sender.action.row, self.refresh_all_views)
			sender.action.row.path = None
		else:
			self.install_action(sender.action.row, self.refresh, self.refresh_all_views)
				
	def refresh(self):
		tv.reload() class DocsetManagementVersionView (object):
	def __init__(self, docsets, download_action, refresh_docsets_action, delete_action,refresh_main_view, theme_manager, show_versions_view):
		self.data = docsets
		self.download_action = download_action
		self.refresh_docsets_action = refresh_docsets_action
		self.delete_action = delete_action
		self.refresh_main_view = refresh_main_view	
		self.theme_manager = theme_manager
		self.show_versions_view = show_versions_view
		
	def tableview_did_select(self, tableview, section, row):
		pass
		
	def tableview_number_of_sections(self, tableview):
		return 1
		
	def tableview_number_of_rows(self, tableview, section):
		return len(self.data)
		
	def tableview_cell_for_row(self, tableview, section, row):
		status = self.data[row]['status']
		cell = ui.TableViewCell('subtitle')
		cell.text_label.text = self.data[row]['name'] + ' ' + str(self.data[row]['version'])
		cell.border_color = self.theme_manager.currentTheme.tintColour
		cell.background_color = self.theme_manager.currentTheme.backgroundColour
		cell.bar_tint_color = self.theme_manager.currentTheme.tintColour
		cell.bg_color = self.theme_manager.currentTheme.backgroundColour
		cell.tint_color = self.theme_manager.currentTheme.tintColour
		cell.text_label.text_color = self.theme_manager.currentTheme.textColour
		cell.detail_text_label.text_color = self.theme_manager.currentTheme.subTextColour
		if not status == 'downloading' or not 'stats' in self.data[row].keys():
			cell.detail_text_label.text = status
		else:
			cell.detail_text_label.text = self.data[row]['stats']
		if not self.data[row]['image'] == None:
			cell.image_view.image = self.data[row]['image']
		iv = self.__getDetailButtonForStatus(status, cell.height, self.action, self.data[row])
		iv.x = cell.content_view.width - (iv.width * 1.5)
		iv.y = (cell.content_view.height) - (iv.height * 1.05)
		iv.flex = 'L'
		cell.content_view.add_subview(iv)
		cell.selectable = False
		return cell
		
	def __getDetailImageForStatus(self, status):
		if status == 'online' or status == 'Update Available':
			return 'iob:ios7_cloud_download_outline_24'
		else:
			return 'iob:ios7_close_outline_24'
			
	def __getDetailButtonForStatus(self, status, height, action, row):
		img = ui.Image.named(self.__getDetailImageForStatus(status))
		button = ui.Button()
		button.image = img
		size = img.size
		ratio = size.y / size.x
		button.height = height * 0.9
		button.width = button.height * ratio
		ca = CustomAction(button)
		ca.action = self.action
		ca.row = row
		button.action = ca
		return button

	def refresh_all_views(self):
		self.refresh_main_view()
		d = self.refresh_docsets_action()
		refresh_view(d)
	
	@ui.in_background
	def action(self, sender):
		if sender.action.row['status'] == 'Update Available':
			sender.action.row['status'] = 'removing...'
			self.refresh()
			self.delete_action(sender.action.row, None, False)
			sender.action.row['path'] = None
			self.download_action(sender.action.row, self.refresh, self.refresh_all_views)
		else:
			if 'path' in sender.action.row and not sender.action.row['path'] == None:
				sender.action.row['status'] = 'removing...'
				self.refresh()
				self.delete_action(sender.action.row, self.refresh_all_views)
				sender.action.row['path'] = None
				self.refresh_all_views()
			else:
				self.download_action(sender.action.row, self.refresh, self.refresh_all_views)
				
	def refresh(self):
		tv.reload() class UserContributedManagementVersionView (object):
	def __init__(self, docsets, download_action, refresh_docsets_action, delete_action,refresh_main_view, theme_manager, show_versions_view):
		self.data = docsets
		self.download_action = download_action
		self.refresh_docsets_action = refresh_docsets_action
		self.delete_action = delete_action
		self.refresh_main_view = refresh_main_view	
		self.theme_manager = theme_manager
		self.show_versions_view = show_versions_view
		
	def tableview_did_select(self, tableview, section, row):
		pass
		
	def tableview_number_of_sections(self, tableview):
		return 1
		
	def tableview_number_of_rows(self, tableview, section):
		return len(self.data)
		
	def tableview_cell_for_row(self, tableview, section, row):
		status = self.data[row].status
		cell = ui.TableViewCell('subtitle')
		cell.text_label.text = self.data[row].name + ' ' + str(self.data[row].version)
		cell.border_color = self.theme_manager.currentTheme.tintColour
		cell.background_color = self.theme_manager.currentTheme.backgroundColour
		cell.bar_tint_color = self.theme_manager.currentTheme.tintColour
		cell.bg_color = self.theme_manager.currentTheme.backgroundColour
		cell.tint_color = self.theme_manager.currentTheme.tintColour
		cell.text_label.text_color = self.theme_manager.currentTheme.textColour
		cell.detail_text_label.text_color = self.theme_manager.currentTheme.subTextColour
		if not status == 'downloading' or self.data[row].stats == '':
			cell.detail_text_label.text = status
		else:
			cell.detail_text_label.text = self.data[row].stats
		if not self.data[row].image == None:
			cell.image_view.image = self.data[row].image
		iv = self.__getDetailButtonForStatus(status, cell.height, self.action, self.data[row])
		iv.x = cell.content_view.width - (iv.width * 1.5)
		iv.y = (cell.content_view.height) - (iv.height * 1.05)
		iv.flex = 'L'
		cell.content_view.add_subview(iv)
		cell.selectable = False
		cell.detail_text_label.text = cell.detail_text_label.text + ' - Contributed by ' + self.data[row].authorName
		return cell
		
	def __getDetailImageForStatus(self, status):
		if status == 'online' or status == 'Update Available':
			return 'iob:ios7_cloud_download_outline_24'
		else:
			return 'iob:ios7_close_outline_24'
			
	def __getDetailButtonForStatus(self, status, height, action, row):
		img = ui.Image.named(self.__getDetailImageForStatus(status))
		button = ui.Button()
		button.image = img
		size = img.size
		ratio = size.y / size.x
		button.height = height * 0.9
		button.width = button.height * ratio
		ca = CustomAction(button)
		ca.action = self.action
		ca.row = row
		button.action = ca
		return button

	def refresh_all_views(self):
		self.refresh_main_view()
		d = self.refresh_docsets_action()
		refresh_view(d)
	
	@ui.in_background
	def action(self, sender):
		if sender.action.row.status == 'Update Available':
			sender.action.row.status = 'removing...'
			self.refresh()
			self.delete_action(sender.action.row, None, False)
			self.download_action(sender.action.row, self.refresh, self.refresh_all_views)
		else:
			if not sender.action.row.path == None:
				sender.action.row.status = 'removing...'
				self.refresh()
				self.delete_action(sender.action.row, self.refresh_all_views)
			else:
				self.download_action(sender.action.row, self.refresh, self.refresh_all_views)
				
	def refresh(self):
		tv.reload() def tableView_cellForRowAtIndexPath_(sel,cmd,tableView,indexPath):
def numberOfSectionsInTableView_(sel,cmd,tableView):
def tableView_numberOfRowsInSection_(sel,cmd, tableView,section):
def tableView_didSelectRowAtIndexPath_(sel,cmd,tableView,indexPath):
def searchBar_textDidChange_(sel, cmd, searchBar, searchText):
def searchBarTextDidBeginEditing_(sel, cmd, searchBar):
def searchBarTextDidEndEditing_(sel, cmd, searchBar):
def searchBarCancelButtonClicked_(sel, cmd, searchBar):
def searchBarSearchButtonClicked_(sel, cmd, searchBar):
def updateSearchResultsForSearchController_(sel, cmd, searchController):
def createSearchDelegateClass():	
def createTableViewDelegateClass(tm):
	def __init__(self, tableView, filterData, selectCallBack, theme_manager, soofflineSelectCallBack, *args, **kwargs):
	def performSelectCallBack(self, data):
def getUIColourFromHex(hexColour):
def get_view(tableview, filter, selectCallBack, theme_manager, soofflineSelectCallBack):
class SearchTableView(ui.View):
from Managers import DocsetManager, ServerManager, CheatsheetManager , UserContributedManager, DBManager, ThemeManager, StackOverflowManager, WebSearchManager, TransferManager, MigrationManager, SettingsManager
from Views import DocsetManagementView, SettingsView, DocsetListView, DocsetView, DocsetIndexView, DocsetWebView, CheatsheetManagementView, UserContributedManagementView, StackOverflowManagementView, TransferManagementView, DocsetManagementVersionView, UserContributedManagementVersionView
from Utilities import UISearchControllerWrapper
import ui
import console
import os
import time
		nav_view.background_color = self.theme_manager.currentTheme.backgroundColour
		nav_view.bar_tint_color = self.theme_manager.currentTheme.toolbarBackgroundColour
		nav_view.bg_color = self.theme_manager.currentTheme.backgroundColour
		nav_view.tint_color = self.theme_manager.currentTheme.tintColour
		nav_view.title_color = self.theme_manager.currentTheme.textColour
		return nav_view

	def setup_main_view(self):
		docsets = self.docset_manager.getDownloadedDocsets()
		cheatsheets = self.cheatsheet_manager.getDownloadedCheatsheets()
		usercontributed = self.usercontributed_manager.getDownloadedUserContributed()
		stackoverflows = self.stackoverflow_manager.getDownloadedStackOverflows()
		transfers = self.transfer_manager.getInstalledDocsets()
		main_view = UISearchControllerWrapper.get_view(DocsetListView.get_view(docsets, cheatsheets, usercontributed, stackoverflows, transfers, self.docset_selected_for_viewing, self.cheatsheet_selected_for_viewing, self.usercontributed_selected_for_viewing, self.stackoverflow_selected_for_viewing, self.transfer_selected_for_viewing, self.theme_manager), self.search_all_docsets, self.docset_index_selected_for_viewing, self.theme_manager, self.build_offline_index_stackoverflow_selected_for_viewing)
		settings_button = ui.ButtonItem(title='Settings')
		settings_button.action = self.show_settings_view
		main_view.left_button_items = [settings_button]
		return main_view

	def setup_docset_management_view(self):
		docsets = self.docset_manager.getAvailableDocsets()
		docset_management_view = DocsetManagementView.get_view(docsets, self.docset_manager.downloadDocset, self.docset_manager.getAvailableDocsets, self.docset_manager.deleteDocset, self.refresh_main_view_data, self.theme_manager, self.show_docset_versions_view)
		docset_management_view.right_button_items = [ui.ButtonItem(action=self.checkStandardDocsetsForUpdate, title='Check for Update')]
		docset_management_view.background_color = self.theme_manager.currentTheme.backgroundColour
		docset_management_view.bar_tint_color = self.theme_manager.currentTheme.tintColour
		docset_management_view.bg_color = self.theme_manager.currentTheme.backgroundColour
		docset_management_view.tint_color = self.theme_manager.currentTheme.tintColour
		docset_management_view.title_color = self.theme_manager.currentTheme.textColour
		return docset_management_view
	
	def setup_docset_management_version_view(self):
		docset_management_view = DocsetManagementVersionView.get_view([], self.docset_manager.downloadDocset, self.docset_manager.getOnlineVersions, self.docset_manager.deleteDocset, self.refresh_main_view_data, self.theme_manager, None)
		docset_management_view.background_color = self.theme_manager.currentTheme.backgroundColour
		docset_management_view.bar_tint_color = self.theme_manager.currentTheme.tintColour
		docset_management_view.bg_color = self.theme_manager.currentTheme.backgroundColour
		docset_management_view.tint_color = self.theme_manager.currentTheme.tintColour
		docset_management_view.title_color = self.theme_manager.currentTheme.textColour
		return docset_management_view
	
	def setup_usercontributed_management_version_view(self):
		view = UserContributedManagementVersionView.get_view([], self.usercontributed_manager.downloadUserContributed, self.usercontributed_manager.getOnlineVersions, self.usercontributed_manager.deleteUserContributed, self.refresh_main_view_data, self.theme_manager, None)
		view.background_color = self.theme_manager.currentTheme.backgroundColour
		view.bar_tint_color = self.theme_manager.currentTheme.tintColour
		view.bg_color = self.theme_manager.currentTheme.backgroundColour
		view.tint_color = self.theme_manager.currentTheme.tintColour
		view.title_color = self.theme_manager.currentTheme.textColour
		return view
	
	def refresh_main_view_data(self):
		docsets = self.docset_manager.getDownloadedDocsets()
		cheatsheets = self.cheatsheet_manager.getDownloadedCheatsheets()
		usercontributed = self.usercontributed_manager.getDownloadedUserContributed()
		stackoverflows = self.stackoverflow_manager.getDownloadedStackOverflows()
		transfers = self.transfer_manager.getInstalledDocsets()
		DocsetListView.refresh_view(docsets, cheatsheets, usercontributed, stackoverflows, transfers)
	
	def setup_cheatsheetmanagement_view(self):
		view = CheatsheetManagementView.get_view(self.cheatsheet_manager.downloadCheatsheet, self.refresh_main_view_data, self.cheatsheet_manager.deleteCheatsheet, self.cheatsheet_manager.getAvailableCheatsheets, self.theme_manager)
		view.right_button_items = [ui.ButtonItem(action=self.checkCheatsheetsForUpdate, title='Check for Update')]
		view.background_color = self.theme_manager.currentTheme.backgroundColour
		view.bar_tint_color = self.theme_manager.currentTheme.tintColour
		view.bg_color = self.theme_manager.currentTheme.backgroundColour
		view.tint_color = self.theme_manager.currentTheme.tintColour
		view.title_color = self.theme_manager.currentTheme.textColour
		return view
	
	def show_docset_versions_view(self, docset):
		versions = self.docset_manager.getOnlineVersions(docset)
		self.docset_management_version_view.data_source.data = versions
		self.docset_management_version_view.reload_data()
		self.navigation_view.push_view(self.docset_management_version_view)
	
	def show_usercontributed_versions_view(self, docset):
		versions = self.usercontributed_manager.getOnlineVersions(docset)
		self.usercontributed_management_version_view.data_source.data = versions
		self.usercontributed_management_version_view.reload_data()
		self.navigation_view.push_view(self.usercontributed_management_version_view)
	
	def show_docset_management_view(self):
		self.navigation_view.push_view(self.docset_management_view)
		
	def show_cheatsheetmanagement_view(self):
		self.cheatsheet_management_view.data_source.data = self.cheatsheet_manager.getAvailableCheatsheets()
		self.cheatsheet_management_view.reload()
		self.navigation_view.push_view(self.cheatsheet_management_view)
		console.hide_activity()
		
	def setup_usercontributedmanagement_view(self):
		view = UserContributedManagementView.get_view(self.usercontributed_manager.downloadUserContributed, self.refresh_main_view_data, self.usercontributed_manager.deleteUserContributed, self.usercontributed_manager.getAvailableUserContributed, self.theme_manager, self.show_usercontributed_versions_view)
		view.right_button_items = [ui.ButtonItem(action=self.checkUserContributedForUpdate, title='Check for Update')]
		view.background_color = self.theme_manager.currentTheme.backgroundColour
		view.bar_tint_color = self.theme_manager.currentTheme.tintColour
		view.bg_color = self.theme_manager.currentTheme.backgroundColour
		view.tint_color = self.theme_manager.currentTheme.tintColour
		view.title_color = self.theme_manager.currentTheme.textColour
		return view
	
	def setup_stackoverflowmanagement_view(self):
		view = StackOverflowManagementView.get_view(self.stackoverflow_manager.downloadStackOverflow, self.refresh_main_view_data, self.stackoverflow_manager.deleteStackOverflow, self.stackoverflow_manager.getAvailableStackOverflows, self.theme_manager)
		view.right_button_items = [ui.ButtonItem(action=self.checkStackOverflowForUpdate, title='Check for Update')]
		view.background_color = self.theme_manager.currentTheme.backgroundColour
		view.bar_tint_color = self.theme_manager.currentTheme.tintColour
		view.bg_color = self.theme_manager.currentTheme.backgroundColour
		view.tint_color = self.theme_manager.currentTheme.tintColour
		view.title_color = self.theme_manager.currentTheme.textColour
		return view
	
	def show_stackoverflowmanagement_view(self):
		self.stackoverflow_management_view.data_source.data = self.stackoverflow_manager.getAvailableStackOverflows()
		self.stackoverflow_management_view.reload()
		self.navigation_view.push_view(self.stackoverflow_management_view)
		console.hide_activity()
		
	def show_usercontributedmanagement_view(self):
		self.usercontributed_management_view.data_source.data = self.usercontributed_manager.getAvailableUserContributed()
		self.usercontributed_management_view.reload()
		self.navigation_view.push_view(self.usercontributed_management_view)
		console.hide_activity()
	
	def setup_transfermanagement_view(self):

		view = TransferManagementView.get_view(self.transfer_manager.installDocset, self.refresh_main_view_data, self.transfer_manager.deleteDocset, self.transfer_manager.getAvailableDocsets, self.theme_manager, self.transfer_manager)