PageRenderTime 45ms CodeModel.GetById 13ms RepoModel.GetById 1ms app.codeStats 0ms

/LMG/Tools/search.py

http://lh-abc.googlecode.com/
Python | 674 lines | 623 code | 46 blank | 5 comment | 31 complexity | 444c94f8602105889e6f0a0403fce426 MD5 | raw file
  1. import sys
  2. import os
  3. import re
  4. import ConfigParser
  5. import wx
  6. import wx.lib.flatnotebook as NC
  7. from threading import Thread
  8. from xml.sax import parseString, handler, saxutils, SAXParseException
  9. from wx.lib.mixins.listctrl import ColumnSorterMixin, ListCtrlAutoWidthMixin
  10. from BitTornado.zurllib import urlopen
  11. from LMG.GUI.Base.menu import Menu
  12. from LMG.GUI.Base.ArtManager import ArtManager
  13. from LMG.Utility.helpers import existsAndIsReadable
  14. regex = re.compile("(?P<name>.*?)\[(?P<seeds>[0-9]+)/(?P<peers>[0-9]+)\]")
  15. class SearchPanel(wx.Panel):
  16. def __init__(self, parent):
  17. wx.Panel.__init__(self, parent, -1, style = wx.STATIC_BORDER)
  18. self.searchHandler = SearchHandler()
  19. sizer = wx.BoxSizer(wx.HORIZONTAL)
  20. # Splitter
  21. self.splitter = wx.SplitterWindow(self, -1, style = wx.SP_LIVE_UPDATE)
  22. self.splitter.SetMinimumPaneSize(25)
  23. self.splitter.SetSashGravity(0.0)
  24. self.enginesPanel = SearchEnginesPanel(self)
  25. self.resultPanel = SearchListPanel(self)
  26. self.splitter.SplitVertically(self.enginesPanel, self.resultPanel, 150)
  27. sizer.Add(self.splitter, 1, wx.EXPAND|wx.TOP, 5)
  28. self.SetAutoLayout(True)
  29. self.SetSizerAndFit(sizer)
  30. class SearchResultsList(wx.ListCtrl, ColumnSorterMixin, ListCtrlAutoWidthMixin):
  31. def __init__(self, parent):
  32. wx.ListCtrl.__init__(self, parent, -1, style=wx.LC_REPORT | wx.LC_VRULES)
  33. ListCtrlAutoWidthMixin.__init__(self)
  34. ColumnSorterMixin.__init__(self, 6)
  35. self.parent = parent
  36. self.itemDataMap = {}
  37. self._col = self.parent.def_col
  38. self.InsertColumn(0, _("Name"))
  39. self.InsertColumn(1, _("File Size"))
  40. self.InsertColumn(2, _("Seeds"))
  41. self.InsertColumn(3, _("Peers"))
  42. self.InsertColumn(4, _("Category"))
  43. self.InsertColumn(5, _("Engine"))
  44. self.setResizeColumn(1)
  45. self.Bind(wx.EVT_LIST_ITEM_ACTIVATED, self.OnItemActivated)
  46. def OnItemActivated(self, event):
  47. selected = event.m_itemIndex
  48. data = self.itemDataMap[self.GetItemData(selected)]
  49. url = data[5]
  50. onBrowser = data[6]
  51. self.SetItemTextColour(selected, wx.BLUE)
  52. if onBrowser:
  53. wx.LaunchDefaultBrowser(url)
  54. else:
  55. utility.queue.addtorrents.ScanUrl(url, single = True, allowBrowser = True)
  56. def clearList(self):
  57. self.DeleteAllItems()
  58. self.itemDataMap = {}
  59. def feedList(self, data, engine):
  60. for item in data:
  61. # data
  62. link = item.get("Link", "---")
  63. name = item.get("Name")
  64. if not name:
  65. if '/' in link:
  66. name = link.split('/')[-1]
  67. else:
  68. name = link
  69. category = item.get("Category", "---")
  70. seeders = item.get("Seeders", "---")
  71. leechers = item.get("Leechers", "---")
  72. size = item.get("Size")
  73. if size is not None:
  74. try:
  75. if "G" in size:
  76. size = float(size.split("G")[0].strip()) * 1024**3
  77. elif "M" in size:
  78. size = float(size.split("M")[0].strip()) * 1024**2
  79. elif "K" in size:
  80. size = float(size.split("K")[0].strip()) * 1024
  81. elif "B" in size and size.find(" ") > 0:
  82. size = float(size.split(" ")[0])
  83. else:
  84. size = float(size)
  85. except:
  86. size = -1
  87. if size is not None and size > -1:
  88. strsize = utility.size_format(size)
  89. else:
  90. strsize = "---"
  91. # insert item
  92. listIndex = self.InsertStringItem(sys.maxint, name)
  93. self.SetStringItem(listIndex, 1, strsize)
  94. self.SetStringItem(listIndex, 2, seeders)
  95. self.SetStringItem(listIndex, 3, leechers)
  96. self.SetStringItem(listIndex, 4, category)
  97. self.SetStringItem(listIndex, 5, engine.viewName)
  98. self.SetItemData(listIndex, listIndex)
  99. # column sorter
  100. try:
  101. seeders = int(seeders)
  102. except:
  103. seeders = -1
  104. try:
  105. leechers = int(leechers)
  106. except:
  107. leechers = -1
  108. if type(seeders) == type(leechers) == int:
  109. if seeders == 0:
  110. item = self.GetItem(listIndex)
  111. item.SetTextColour(wx.RED)
  112. self.SetItem(item)
  113. elif seeders > 5 and (leechers == 0 or seeders/float(leechers) > 0.5):
  114. item = self.GetItem(listIndex)
  115. item.SetTextColour(wx.Colour(0, 150, 0))
  116. self.SetItem(item)
  117. self.itemDataMap[listIndex] = (name, size, seeders, leechers, category, link, engine.onbrowser)
  118. self.SortItems(self.GetColumnSorter())
  119. def SortItems(self, *args, **kwargs):
  120. super(SearchResultsList, self).SortItems(*args, **kwargs)
  121. ArtManager.Get().MakeAlternateList(self)
  122. def GetListCtrl(self):
  123. return self
  124. def OnSortOrderChanged(self):
  125. self.parent.def_col = self._col
  126. class SearchListPanel(wx.Panel):
  127. def __init__(self, parent):
  128. wx.Panel.__init__(self, parent.splitter, -1)
  129. self.parent = parent
  130. self.searches = {}
  131. self.recent = []
  132. self.def_col = -1
  133. sizer = wx.BoxSizer(wx.VERTICAL)
  134. self.searchTerm = wx.SearchCtrl(self, size=(-1,-1), style=wx.TE_PROCESS_ENTER)
  135. self.searchTerm.ShowCancelButton(True)
  136. self.searchTerm.SetDescriptiveText(_("Enter Search Term..."))
  137. sizer.Add(self.searchTerm, 0, wx.ALIGN_CENTER_VERTICAL|wx.EXPAND, 0)
  138. nbstyle = NC.FNB_X_ON_TAB | NC.FNB_FF2 | NC.FNB_DROPDOWN_TABS_LIST | NC.FNB_NO_NAV_BUTTONS
  139. self.notebook = NC.FlatNotebook(self, wx.ID_ANY, style=nbstyle)
  140. sizer.Add(self.notebook, 1, wx.EXPAND|wx.ALL, 0)
  141. self.notebook.Bind(NC.EVT_FLATNOTEBOOK_PAGE_CLOSING, self.OnPageClosed)
  142. self.notebook._pages.Bind(wx.EVT_LEFT_DCLICK, self.OnLeftDClick)
  143. self.Bind(wx.EVT_SEARCHCTRL_CANCEL_BTN, self.OnClean, self.searchTerm)
  144. self.Bind(wx.EVT_TEXT_ENTER, self.onSearch, self.searchTerm)
  145. self.Bind(wx.EVT_MENU, self.onRecentSearch)
  146. self.SetSizerAndFit(sizer)
  147. def onRecentSearch(self, event):
  148. try:
  149. text = self.recent[event.GetId()-888]
  150. self.searchTerm.SetValue(text)
  151. self.searchTerm.SendTextUpdatedEvent()
  152. except:
  153. del self.recent[:]
  154. wx.CallAfter(self.searchTerm.SetMenu, None)
  155. def onSearch(self, event = None):
  156. term = self.searchTerm.GetValue()
  157. if not term:
  158. return
  159. if term in self.recent:
  160. self.recent.remove(term)
  161. self.recent.append(term)
  162. if len(self.recent) > 5:
  163. del self.recent[0]
  164. self.MakeMenu()
  165. resultList = self.newSearch(term)
  166. resultList.clearList()
  167. #self.searchTerm.Clear()
  168. self.parent.searchHandler.search(term, self.feedList)
  169. def MakeMenu(self):
  170. menu = Menu()
  171. item = menu.Append(-1, _("Recent Searches"))
  172. item.Enable(False)
  173. menu.AppendSeparator()
  174. for idx, txt in enumerate(self.recent):
  175. menu.Append(888+idx, txt)
  176. menu.AppendSeparator()
  177. menu.Append(888+len(self.recent), _("Clean"))
  178. self.searchTerm.SetMenu(menu)
  179. def OnClean(self, event):
  180. keyword = self.searchTerm.GetValue()
  181. if self.searches.has_key(keyword):
  182. del self.searches[keyword]
  183. self.searchTerm.SetValue("")
  184. self.searchTerm.SendTextUpdatedEvent()
  185. def OnLeftDClick(self, event):
  186. where, tabIdx = self.notebook._pages.HitTest(event.GetPosition())
  187. if where == NC.FNB_TAB and tabIdx >= 0:
  188. self.searchTerm.SetValue(self.notebook.GetPageText(tabIdx))
  189. self.searchTerm.SendTextUpdatedEvent()
  190. event.Skip()
  191. def newSearch(self, keyword):
  192. if keyword in self.searches:
  193. page = self.searches[keyword]
  194. self.notebook.SetSelection(self.notebook.GetPageIndex(page))
  195. else:
  196. page = SearchResultsList(self)
  197. self.notebook.AddPage(page, keyword)
  198. self.searches[keyword] = page
  199. return page
  200. def OnPageClosed(self, event):
  201. keyword = self.notebook.GetPageText(event.GetSelection())
  202. if keyword in self.searches:
  203. del self.searches[keyword]
  204. event.Skip()
  205. def feedList(self, data, keyword, engine):
  206. keyword = keyword.replace("%20", " ")
  207. if keyword in self.searches and data:
  208. wx.CallAfter(self.searches[keyword].feedList, data, engine)
  209. class SearchEnginesPanel(wx.Panel):
  210. def __init__(self, parent):
  211. wx.Panel.__init__(self, parent.splitter, -1)
  212. self.parent = parent
  213. sizer = wx.BoxSizer(wx.VERTICAL)
  214. # Make and layout the controls
  215. t = wx.StaticText(self, -1, _("Search Engines"))
  216. t.SetFont(wx.Font(self.GetFont().GetPointSize(), wx.SWISS, wx.NORMAL, wx.BOLD))
  217. sizer.Add(t, 0, wx.CENTER|wx.ALL, 5)
  218. self.enginesList = wx.CheckListBox(self, -1)
  219. sizer.Add(self.enginesList, 1, wx.EXPAND|wx.ALL, 0)
  220. btnsizer = wx.BoxSizer(wx.HORIZONTAL)
  221. AddButton = utility.makeBitmapButton(self, wx.ArtProvider.GetBitmap(wx.ART_ADD_BOOKMARK, wx.ART_TOOLBAR), _('Add'), self.onAdd)
  222. btnsizer.Add(AddButton, 1, wx.EXPAND|wx.ALL, 0)
  223. RemButton = utility.makeBitmapButton(self, wx.ArtProvider.GetBitmap(wx.ART_DEL_BOOKMARK, wx.ART_TOOLBAR), _('Remove'), self.onRemove)
  224. btnsizer.Add(RemButton, 1, wx.EXPAND|wx.ALL, 0)
  225. EditButton = utility.makeBitmapButton(self, wx.ArtProvider.GetBitmap(wx.ART_HELP_SETTINGS, wx.ART_TOOLBAR), _('Edit'), self.onEdit)
  226. btnsizer.Add(EditButton, 1, wx.EXPAND|wx.ALL, 0)
  227. sizer.Add(btnsizer, 0, wx.EXPAND|wx.ALL, 0)
  228. self.SetAutoLayout(True)
  229. self.SetSizerAndFit(sizer)
  230. self.Bind(wx.EVT_LISTBOX_DCLICK, self.onEdit, self.enginesList)
  231. self.Bind(wx.EVT_CHECKLISTBOX, self.OnUpdateCheck, self.enginesList)
  232. self.load()
  233. def load(self):
  234. engines = self.parent.searchHandler.engines
  235. engines.sort(key = lambda x: x.viewName.lower())
  236. self.enginesList.Set([engine.viewName for engine in engines])
  237. for index in range(len(engines)):
  238. self.enginesList.Check(index, engines[index].active)
  239. def OnUpdateCheck(self, event):
  240. index = event.GetSelection()
  241. engines = self.parent.searchHandler.engines
  242. if 0 <= index < len(engines):
  243. engines[index].active = self.enginesList.IsChecked(index)
  244. self.parent.searchHandler.save()
  245. event.Skip()
  246. def onAdd(self, event):
  247. dlg = AddEngineDialog(self, _("Add Engine"))
  248. result = dlg.ShowModal()
  249. dlg.Destroy()
  250. if result == wx.ID_OK:
  251. self.parent.searchHandler.addEngine(dlg.engine)
  252. self.parent.searchHandler.save()
  253. self.load()
  254. def onRemove(self, event):
  255. index = self.enginesList.GetSelection()
  256. if index < 0:
  257. return
  258. del self.parent.searchHandler.engines[index]
  259. self.parent.searchHandler.save()
  260. self.load()
  261. def onEdit(self, event):
  262. index = self.enginesList.GetSelection()
  263. if index < 0:
  264. return
  265. engine = self.parent.searchHandler.engines[index]
  266. dlg = AddEngineDialog(self, _("Edit Engine"), engine.url, engine.viewName, engine.onbrowser, *engine.headers)
  267. result = dlg.ShowModal()
  268. dlg.Destroy()
  269. if result == wx.ID_OK:
  270. dlg.engine.active = engine.active
  271. self.parent.searchHandler.engines[index] = dlg.engine
  272. self.parent.searchHandler.save()
  273. self.load()
  274. class AddEngineDialog(wx.Dialog):
  275. def __init__(self, parent, title, url = "", viewName = "", onbrowser = True,
  276. GroupTag = "item", linkTag = "link", nameTag = "title",
  277. sizeTag = "size", seedersTag = "seeders", leechersTag = "leechers",
  278. categoryTag = "category", descriptionTag = "description"):
  279. wx.Dialog.__init__(self, parent, -1, title)
  280. sizer = wx.BoxSizer(wx.VERTICAL)
  281. engineBox = wx.StaticBoxSizer(wx.StaticBox(self, -1, _('Engine')), wx.VERTICAL)
  282. engineSizer = wx.FlexGridSizer(0, 2, 5, 5)
  283. self.viewNameCtrl = wx.TextCtrl(self, -1, viewName, size = (300, -1))
  284. self.urlCtrl = wx.TextCtrl(self, -1, url, size = (300, -1))
  285. engineSizer.Add(wx.StaticText(self, -1, _("Name:")), 0, wx.ALL|wx.ALIGN_CENTER_VERTICAL, 3)
  286. engineSizer.Add(self.viewNameCtrl, 0, wx.ALIGN_CENTER_VERTICAL|wx.EXPAND, 0)
  287. engineSizer.Add(wx.StaticText(self, -1, _("URL:")), 0, wx.ALL|wx.ALIGN_CENTER_VERTICAL, 3)
  288. engineSizer.Add(self.urlCtrl, 0, wx.ALIGN_CENTER_VERTICAL|wx.EXPAND, 0)
  289. engineBox.Add(engineSizer, 0, wx.EXPAND|wx.ALL, 5)
  290. sizer.Add(engineBox, 0, wx.EXPAND|wx.ALL, 3)
  291. xmltagsBox = wx.StaticBoxSizer(wx.StaticBox(self, -1, _('XML Tags')), wx.VERTICAL)
  292. xmltagsSizer = wx.FlexGridSizer(0, 2, 5, 5)
  293. self.GroupTagCtrl = wx.TextCtrl(self, -1, GroupTag)
  294. self.linkTagCtrl = wx.TextCtrl(self, -1, linkTag)
  295. self.nameTagCtrl = wx.TextCtrl(self, -1, nameTag)
  296. self.sizeTagCtrl = wx.TextCtrl(self, -1, sizeTag)
  297. self.seedersTagCtrl = wx.TextCtrl(self, -1, seedersTag)
  298. self.leechersTagCtrl = wx.TextCtrl(self, -1, leechersTag)
  299. self.categoryTagCtrl = wx.TextCtrl(self, -1, categoryTag)
  300. self.descriptionTagCtrl = wx.TextCtrl(self, -1, descriptionTag)
  301. xmltagsSizer.Add(wx.StaticText(self, -1, _("New Group:")), 0, wx.ALL|wx.ALIGN_CENTER_VERTICAL, 3)
  302. xmltagsSizer.Add(self.GroupTagCtrl, 0, wx.ALIGN_CENTER_VERTICAL|wx.EXPAND, 0)
  303. xmltagsSizer.Add(wx.StaticText(self, -1, _("Link:")), 0, wx.ALL|wx.ALIGN_CENTER_VERTICAL, 3)
  304. xmltagsSizer.Add(self.linkTagCtrl, 0, wx.ALIGN_CENTER_VERTICAL|wx.EXPAND, 0)
  305. xmltagsSizer.Add(wx.StaticText(self, -1, _("Name:")), 0, wx.ALL|wx.ALIGN_CENTER_VERTICAL, 3)
  306. xmltagsSizer.Add(self.nameTagCtrl, 0, wx.ALIGN_CENTER_VERTICAL|wx.EXPAND, 0)
  307. xmltagsSizer.Add(wx.StaticText(self, -1, _("Size:")), 0, wx.ALL|wx.ALIGN_CENTER_VERTICAL, 3)
  308. xmltagsSizer.Add(self.sizeTagCtrl, 0, wx.ALIGN_CENTER_VERTICAL|wx.EXPAND, 0)
  309. xmltagsSizer.Add(wx.StaticText(self, -1, _("Seeders:")), 0, wx.ALL|wx.ALIGN_CENTER_VERTICAL, 3)
  310. xmltagsSizer.Add(self.seedersTagCtrl, 0, wx.ALIGN_CENTER_VERTICAL|wx.EXPAND, 0)
  311. xmltagsSizer.Add(wx.StaticText(self, -1, _("Leechers:")), 0, wx.ALL|wx.ALIGN_CENTER_VERTICAL, 3)
  312. xmltagsSizer.Add(self.leechersTagCtrl, 0, wx.ALIGN_CENTER_VERTICAL|wx.EXPAND, 0)
  313. xmltagsSizer.Add(wx.StaticText(self, -1, _("Category:")), 0, wx.ALL|wx.ALIGN_CENTER_VERTICAL, 3)
  314. xmltagsSizer.Add(self.categoryTagCtrl, 0, wx.ALIGN_CENTER_VERTICAL|wx.EXPAND, 0)
  315. xmltagsSizer.Add(wx.StaticText(self, -1, _("*Extended*:")), 0, wx.ALL|wx.ALIGN_CENTER_VERTICAL, 3)
  316. xmltagsSizer.Add(self.descriptionTagCtrl, 0, wx.ALIGN_CENTER_VERTICAL|wx.EXPAND, 0)
  317. xmltagsBox.Add(xmltagsSizer, 0, wx.EXPAND|wx.ALL, 5)
  318. sizer.Add(xmltagsBox, 0, wx.EXPAND|wx.ALL, 3)
  319. optionsBox = wx.StaticBoxSizer(wx.StaticBox(self, -1, _('Options')), wx.VERTICAL)
  320. self.onbrowser = wx.CheckBox(self, -1, _("Open results in default browser"))
  321. self.onbrowser.SetValue(onbrowser)
  322. optionsBox.Add(self.onbrowser, 0, wx.EXPAND|wx.ALL, 5)
  323. sizer.Add(optionsBox, 0, wx.EXPAND|wx.ALL, 3)
  324. sizer.Add(wx.StaticLine(self), 0, wx.GROW, 0)
  325. buttonSizer = wx.BoxSizer(wx.HORIZONTAL)
  326. okButton = wx.Button(self, wx.ID_OK, _("OK"))
  327. cancelButton = wx.Button(self, wx.ID_CANCEL, _("Cancel"))
  328. buttonSizer.Add(okButton, 0, wx.EXPAND|wx.ALL, 5)
  329. buttonSizer.Add(cancelButton, 0, wx.EXPAND|wx.ALL, 5)
  330. sizer.Add(buttonSizer, 0, wx.CENTER, 0)
  331. self.SetSizerAndFit(sizer)
  332. self.Bind(wx.EVT_BUTTON, self.onOK, id = wx.ID_OK)
  333. self.Bind(wx.EVT_BUTTON, self.onCancel, id = wx.ID_CANCEL)
  334. def onOK(self, event):
  335. if not self.urlCtrl.GetValue():
  336. dlg = wx.MessageDialog(self, _("You must enter a value for the URL field"), _("Input Error"), wx.OK)
  337. dlg.ShowModal()
  338. dlg.Destroy()
  339. self.urlCtrl.SetFocus()
  340. return
  341. headers = (self.GroupTagCtrl.GetValue(), self.linkTagCtrl.GetValue(), self.nameTagCtrl.GetValue(),
  342. self.sizeTagCtrl.GetValue(), self.seedersTagCtrl.GetValue(), self.leechersTagCtrl.GetValue(),
  343. self.categoryTagCtrl.GetValue(), self.descriptionTagCtrl.GetValue())
  344. self.engine = SearchEngine(self.urlCtrl.GetValue(), self.viewNameCtrl.GetValue(),
  345. headers, onbrowser = self.onbrowser.GetValue())
  346. self.EndModal(wx.ID_OK)
  347. def onCancel(self, event):
  348. self.EndModal(wx.ID_CANCEL)
  349. class SearchEngine(object):
  350. def __init__(self, url = "", viewName = None,
  351. headers = ("item", "link", "title", "size", "seeders", "leechers", "category", "description"),
  352. active = True, onbrowser = True):
  353. self.url = url
  354. self.viewName = viewName or url
  355. self.headers = headers
  356. self.active = active
  357. self.onbrowser = onbrowser
  358. def toFile(self):
  359. return [self.url, self.viewName, self.headers, self.active, self.onbrowser]
  360. def fromFile(self, data):
  361. self.url = data[0]
  362. self.viewName = data[1]
  363. self.headers = data[2]
  364. self.active = data[3]
  365. self.onbrowser = data[4]
  366. return self
  367. class SearchHandler(object):
  368. def __init__(self):
  369. # Setup config file
  370. self.config_file = os.path.join(utility.getConfigPath(), "search.conf")
  371. # Load Engines
  372. self.engines = []
  373. self.load()
  374. self.current = {}
  375. def addEngine(self, engine):
  376. self.engines.append(engine)
  377. def search(self, keyword, callback):
  378. keyword = keyword.replace(" ", "%20")
  379. engines = [engine for engine in self.engines if engine.active]
  380. for engine in engines:
  381. if "%@" in engine.url:
  382. url = engine.url.replace("%@", keyword)
  383. else:
  384. url = engine.url + keyword
  385. searchThread = Thread(target=self.__perform_search, args = [url, engine, callback, keyword])
  386. searchThread.setDaemon(True)
  387. searchThread.start()
  388. def __perform_search(self, url, engine, callback, keyword):
  389. try:
  390. answer = urlopen(url, encoding = None)
  391. if answer:
  392. xmlHandler = XMLHandler(engine)
  393. parseString(answer.read(), xmlHandler)
  394. callback(xmlHandler.data, keyword, engine)
  395. except (IOError, ValueError, SAXParseException), e:
  396. print url, e
  397. wx.LogDebug(str(e))
  398. def save(self):
  399. """
  400. Save to config file
  401. """
  402. try:
  403. cp = ConfigParser.ConfigParser()
  404. cp.add_section('SEARCH')
  405. i = 0
  406. for item in self.engines:
  407. cp.set('SEARCH', str(i), item.toFile())
  408. i = i+1
  409. file = open(self.config_file, 'w')
  410. cp.write(file)
  411. file.close()
  412. except:
  413. pass
  414. def load(self):
  415. """
  416. Load from config file
  417. """
  418. if not existsAndIsReadable(self.config_file):
  419. return
  420. try:
  421. cp = ConfigParser.ConfigParser()
  422. file = open(self.config_file, 'r')
  423. cp.readfp(file)
  424. items = cp.items('SEARCH')
  425. for key, param in items:
  426. param = eval(param)
  427. self.addEngine(SearchEngine().fromFile(param))
  428. file.close()
  429. except:
  430. wx.LogError("couldn't read from search file")
  431. class XMLHandler(handler.ContentHandler):
  432. TORRENT = 0
  433. LINK = 1
  434. NAME = 2
  435. SIZE = 3
  436. SEEDERS = 4
  437. LEECHERS = 5
  438. CATEGORY = 6
  439. DESCRIPTION = 7
  440. def __init__(self, engine):
  441. handler.ContentHandler.__init__(self)
  442. self.headers = ("Torrent", "Link", "Name", "Size", "Seeders", "Leechers", "Category", "description")
  443. self.engine = engine
  444. self.currentHeader = None
  445. self.data = []
  446. def startElement(self, name, attrs):
  447. if name != self.engine.headers[self.TORRENT] and not self.data:
  448. return
  449. if name == self.engine.headers[self.TORRENT]:
  450. assert self.currentHeader == None
  451. self.currentHeader = self.TORRENT
  452. self.data.append({})
  453. elif name == self.engine.headers[self.LINK]:
  454. self.currentHeader = self.LINK
  455. obj = self.data[-1]
  456. if attrs.get("href"):
  457. obj[self.headers[self.LINK]] = attrs["href"]
  458. elif attrs.get("url"):
  459. obj[self.headers[self.LINK]] = attrs["url"]
  460. if attrs.get("length") and not obj.has_key(self.headers[self.SIZE]):
  461. size = int(attrs["length"]) / (1024*1024)
  462. obj[self.headers[self.SIZE]] = str(size) + " MB"
  463. elif name == self.engine.headers[self.NAME]:
  464. self.currentHeader = self.NAME
  465. elif name == self.engine.headers[self.SIZE]:
  466. self.currentHeader = self.SIZE
  467. elif name == self.engine.headers[self.SEEDERS]:
  468. self.currentHeader = self.SEEDERS
  469. elif name == self.engine.headers[self.LEECHERS]:
  470. self.currentHeader = self.LEECHERS
  471. elif name == self.engine.headers[self.CATEGORY]:
  472. self.currentHeader = self.CATEGORY
  473. elif name == self.engine.headers[self.DESCRIPTION]:
  474. self.currentHeader = self.DESCRIPTION
  475. else:
  476. self.currentHeader = None
  477. def endElement(self, name):
  478. # Group ended
  479. if name == self.engine.headers[self.TORRENT]:
  480. self.currentHeader = None
  481. def characters(self, content):
  482. try:
  483. self._characters(content)
  484. except:
  485. pass
  486. def _characters(self, content):
  487. def isnumber(s):
  488. try:
  489. float(s)
  490. return True
  491. except:
  492. return False
  493. content = content.strip()
  494. # Bad state
  495. if self.currentHeader == None or not content or not self.data:
  496. return
  497. obj = self.data[-1]
  498. # Already have this data
  499. if obj.has_key(self.headers[self.currentHeader]) \
  500. and self.currentHeader != self.DESCRIPTION:
  501. return
  502. # fix content
  503. content = saxutils.unescape(content)
  504. # Name
  505. if self.currentHeader == self.NAME:
  506. if not self.engine.headers[self.SEEDERS] and not self.engine.headers[self.LEECHERS]:
  507. match = regex.match(content)
  508. if match:
  509. obj[self.headers[self.NAME]] = match.group(1)
  510. obj[self.headers[self.SEEDERS]] = match.group(2)
  511. obj[self.headers[self.LEECHERS]] = match.group(3)
  512. else:
  513. obj[self.headers[self.NAME]] = content
  514. else:
  515. obj[self.headers[self.NAME]] = content
  516. # Description
  517. elif self.currentHeader == self.DESCRIPTION:
  518. sizekeys = ("size:", "filesize:", "file size:")
  519. seedkeys = ("seeders:", "seeds:", "seed:")
  520. peerkeys = ("leechers:", "peers:", "leech:", "downloaders:")
  521. content = content.replace(" :", ":").lower()
  522. content = content.split(" ")
  523. for index in xrange(len(content)):
  524. item = content[index]
  525. if not item.strip():
  526. continue
  527. data = ""
  528. for key in sizekeys:
  529. if item == key:
  530. data = content[index+1].upper()
  531. if isnumber(data):
  532. data += " " + content[index+2].upper()
  533. elif item.startswith(key):
  534. data = item.split(key)[1].upper()
  535. if isnumber(data):
  536. data += " " + content[index+1].upper()
  537. if data:
  538. obj[self.headers[self.SIZE]] = data
  539. data = ""
  540. for key in seedkeys:
  541. if item == key:
  542. data = content[index+1]
  543. elif item.startswith(key):
  544. data = item.split(key)[1]
  545. if data:
  546. obj[self.headers[self.SEEDERS]] = data
  547. data = ""
  548. for key in peerkeys:
  549. if item == key:
  550. data = content[index+1]
  551. elif item.startswith(key):
  552. data = item.split(key)[1]
  553. if data:
  554. obj[self.headers[self.LEECHERS]] = data
  555. # Other
  556. else:
  557. obj[self.headers[self.currentHeader]] = content