PageRenderTime 53ms CodeModel.GetById 14ms RepoModel.GetById 0ms app.codeStats 0ms

/profile.py

https://github.com/602p/pesterchum
Python | 615 lines | 611 code | 4 blank | 0 comment | 7 complexity | 5e3f1c8f331b63869a7d013d2952858e MD5 | raw file
Possible License(s): GPL-3.0, Cube
  1. import logging
  2. import os
  3. from string import Template
  4. import json
  5. import re
  6. import codecs
  7. import platform
  8. from datetime import *
  9. from time import strftime, time
  10. from PyQt4 import QtGui, QtCore
  11. import ostools
  12. from mood import Mood
  13. from dataobjs import PesterProfile, pesterQuirk, pesterQuirks
  14. from parsetools import convertTags, addTimeInitial, themeChecker, ThemeException
  15. _datadir = ostools.getDataDir()
  16. class PesterLog(object):
  17. def __init__(self, handle, parent=None):
  18. global _datadir
  19. self.parent = parent
  20. self.handle = handle
  21. self.convos = {}
  22. self.logpath = _datadir+"logs"
  23. def log(self, handle, msg):
  24. if self.parent.config.time12Format():
  25. time = strftime("[%I:%M")
  26. else:
  27. time = strftime("[%H:%M")
  28. if self.parent.config.showSeconds():
  29. time += strftime(":%S] ")
  30. else:
  31. time += "] "
  32. if handle[0] == '#':
  33. if not self.parent.config.logMemos() & self.parent.config.LOG: return
  34. if not self.parent.config.logMemos() & self.parent.config.STAMP:
  35. time = ""
  36. else:
  37. if not self.parent.config.logPesters() & self.parent.config.LOG: return
  38. if not self.parent.config.logPesters() & self.parent.config.STAMP:
  39. time = ""
  40. if unicode(handle).upper() == "NICKSERV": return
  41. #watch out for illegal characters
  42. handle = re.sub(r'[<>:"/\\|?*]', "_", handle)
  43. bbcodemsg = time + convertTags(msg, "bbcode")
  44. html = time + convertTags(msg, "html")+"<br />"
  45. msg = time +convertTags(msg, "text")
  46. modes = {"bbcode": bbcodemsg, "html": html, "text": msg}
  47. if not self.convos.has_key(handle):
  48. time = datetime.now().strftime("%Y-%m-%d.%H.%M")
  49. self.convos[handle] = {}
  50. for (format, t) in modes.iteritems():
  51. if not os.path.exists("%s/%s/%s/%s" % (self.logpath, self.handle, handle, format)):
  52. os.makedirs("%s/%s/%s/%s" % (self.logpath, self.handle, handle, format))
  53. try:
  54. fp = codecs.open("%s/%s/%s/%s/%s.%s.txt" % (self.logpath, self.handle, handle, format, handle, time), encoding='utf-8', mode='a')
  55. except IOError:
  56. errmsg = QtGui.QMessageBox(self)
  57. errmsg.setText("Warning: Pesterchum could not open the log file for %s!" % (handle))
  58. errmsg.setInformativeText("Your log for %s will not be saved because something went wrong. We suggest restarting Pesterchum. Sorry :(" % (handle))
  59. errmsg.show()
  60. continue
  61. self.convos[handle][format] = fp
  62. for (format, t) in modes.iteritems():
  63. f = self.convos[handle][format]
  64. if platform.system() == "Windows":
  65. f.write(t+"\r\n")
  66. else:
  67. f.write(t+"\r\n")
  68. f.flush()
  69. def finish(self, handle):
  70. if not self.convos.has_key(handle):
  71. return
  72. for f in self.convos[handle].values():
  73. f.close()
  74. del self.convos[handle]
  75. def close(self):
  76. for h in self.convos.keys():
  77. for f in self.convos[h].values():
  78. f.close()
  79. class userConfig(object):
  80. def __init__(self, parent):
  81. self.parent = parent
  82. # Use for bit flag log setting
  83. self.LOG = 1
  84. self.STAMP = 2
  85. # Use for bit flag blink
  86. self.PBLINK = 1
  87. self.MBLINK = 2
  88. # Use for bit flag notfications
  89. self.SIGNIN = 1
  90. self.SIGNOUT = 2
  91. self.NEWMSG = 4
  92. self.NEWCONVO = 8
  93. self.INITIALS = 16
  94. self.filename = _datadir+"pesterchum.js"
  95. fp = open(self.filename)
  96. self.config = json.load(fp)
  97. fp.close()
  98. if self.config.has_key("defaultprofile"):
  99. self.userprofile = userProfile(self.config["defaultprofile"])
  100. else:
  101. self.userprofile = None
  102. self.logpath = _datadir+"logs"
  103. if not os.path.exists(self.logpath):
  104. os.makedirs(self.logpath)
  105. try:
  106. fp = open("%s/groups.js" % (self.logpath), 'r')
  107. self.groups = json.load(fp)
  108. fp.close()
  109. except IOError:
  110. self.groups = {}
  111. fp = open("%s/groups.js" % (self.logpath), 'w')
  112. json.dump(self.groups, fp)
  113. fp.close()
  114. except ValueError:
  115. self.groups = {}
  116. fp = open("%s/groups.js" % (self.logpath), 'w')
  117. json.dump(self.groups, fp)
  118. fp.close()
  119. def chums(self):
  120. if not self.config.has_key('chums'):
  121. self.set("chums", [])
  122. return self.config.get('chums', [])
  123. def setChums(self, newchums):
  124. fp = open(self.filename) # what if we have two clients open??
  125. newconfig = json.load(fp)
  126. fp.close()
  127. oldchums = newconfig['chums']
  128. # Time to merge these two! :OOO
  129. for c in list(set(oldchums) - set(newchums)):
  130. newchums.append(c)
  131. self.set("chums", newchums)
  132. def hideOfflineChums(self):
  133. return self.config.get('hideOfflineChums', False)
  134. def defaultprofile(self):
  135. try:
  136. return self.config['defaultprofile']
  137. except KeyError:
  138. return None
  139. def tabs(self):
  140. return self.config.get("tabs", True)
  141. def tabMemos(self):
  142. if not self.config.has_key('tabmemos'):
  143. self.set("tabmemos", self.tabs())
  144. return self.config.get("tabmemos", True)
  145. def showTimeStamps(self):
  146. if not self.config.has_key('showTimeStamps'):
  147. self.set("showTimeStamps", True)
  148. return self.config.get('showTimeStamps', True)
  149. def time12Format(self):
  150. if not self.config.has_key('time12Format'):
  151. self.set("time12Format", True)
  152. return self.config.get('time12Format', True)
  153. def showSeconds(self):
  154. if not self.config.has_key('showSeconds'):
  155. self.set("showSeconds", False)
  156. return self.config.get('showSeconds', False)
  157. def sortMethod(self):
  158. return self.config.get('sortMethod', 0)
  159. def useGroups(self):
  160. return self.config.get('useGroups', False)
  161. def openDefaultGroup(self):
  162. groups = self.getGroups()
  163. for g in groups:
  164. if g[0] == "Chums":
  165. return g[1]
  166. return True
  167. def showEmptyGroups(self):
  168. if not self.config.has_key('emptyGroups'):
  169. self.set("emptyGroups", False)
  170. return self.config.get('emptyGroups', False)
  171. def showOnlineNumbers(self):
  172. if not self.config.has_key('onlineNumbers'):
  173. self.set("onlineNumbers", False)
  174. return self.config.get('onlineNumbers', False)
  175. def logPesters(self):
  176. return self.config.get('logPesters', self.LOG | self.STAMP)
  177. def logMemos(self):
  178. return self.config.get('logMemos', self.LOG)
  179. def disableUserLinks(self):
  180. return not self.config.get('userLinks', True)
  181. def idleTime(self):
  182. return self.config.get('idleTime', 10)
  183. def minimizeAction(self):
  184. return self.config.get('miniAction', 0)
  185. def closeAction(self):
  186. return self.config.get('closeAction', 1)
  187. def opvoiceMessages(self):
  188. return self.config.get('opvMessages', True)
  189. def animations(self):
  190. return self.config.get('animations', True)
  191. def checkForUpdates(self):
  192. u = self.config.get('checkUpdates', 0)
  193. if type(u) == type(bool()):
  194. if u: u = 2
  195. else: u = 3
  196. return u
  197. # Once a day
  198. # Once a week
  199. # Only on start
  200. # Never
  201. def lastUCheck(self):
  202. return self.config.get('lastUCheck', 0)
  203. def checkMSPA(self):
  204. return self.config.get('mspa', False)
  205. def blink(self):
  206. return self.config.get('blink', self.PBLINK | self.MBLINK)
  207. def notify(self):
  208. return self.config.get('notify', True)
  209. def notifyType(self):
  210. return self.config.get('notifyType', "default")
  211. def notifyOptions(self):
  212. return self.config.get('notifyOptions', self.SIGNIN | self.NEWMSG | self.NEWCONVO | self.INITIALS)
  213. def lowBandwidth(self):
  214. return self.config.get('lowBandwidth', False)
  215. def ghostchum(self):
  216. return self.config.get('ghostchum', False)
  217. def addChum(self, chum):
  218. if chum.handle not in self.chums():
  219. fp = open(self.filename) # what if we have two clients open??
  220. newconfig = json.load(fp)
  221. fp.close()
  222. newchums = newconfig['chums'] + [chum.handle]
  223. self.set("chums", newchums)
  224. def removeChum(self, chum):
  225. if type(chum) is PesterProfile:
  226. handle = chum.handle
  227. else:
  228. handle = chum
  229. newchums = [c for c in self.config['chums'] if c != handle]
  230. self.set("chums", newchums)
  231. def getBlocklist(self):
  232. if not self.config.has_key('block'):
  233. self.set('block', [])
  234. return self.config['block']
  235. def addBlocklist(self, handle):
  236. l = self.getBlocklist()
  237. if handle not in l:
  238. l.append(handle)
  239. self.set('block', l)
  240. def delBlocklist(self, handle):
  241. l = self.getBlocklist()
  242. l.pop(l.index(handle))
  243. self.set('block', l)
  244. def getGroups(self):
  245. if not self.groups.has_key('groups'):
  246. self.saveGroups([["Chums", True]])
  247. return self.groups.get('groups', [["Chums", True]])
  248. def addGroup(self, group, open=True):
  249. l = self.getGroups()
  250. exists = False
  251. for g in l:
  252. if g[0] == group:
  253. exists = True
  254. break
  255. if not exists:
  256. l.append([group,open])
  257. l.sort()
  258. self.saveGroups(l)
  259. def delGroup(self, group):
  260. l = self.getGroups()
  261. i = 0
  262. for g in l:
  263. if g[0] == group: break
  264. i = i+1
  265. l.pop(i)
  266. l.sort()
  267. self.saveGroups(l)
  268. def expandGroup(self, group, open=True):
  269. l = self.getGroups()
  270. for g in l:
  271. if g[0] == group:
  272. g[1] = open
  273. break
  274. self.saveGroups(l)
  275. def saveGroups(self, groups):
  276. self.groups['groups'] = groups
  277. try:
  278. jsonoutput = json.dumps(self.groups)
  279. except ValueError, e:
  280. raise e
  281. fp = open("%s/groups.js" % (self.logpath), 'w')
  282. fp.write(jsonoutput)
  283. fp.close()
  284. def server(self):
  285. if hasattr(self.parent, 'serverOverride'):
  286. return self.parent.serverOverride
  287. return self.config.get('server', 'irc.mindfang.org')
  288. def port(self):
  289. if hasattr(self.parent, 'portOverride'):
  290. return self.parent.portOverride
  291. return self.config.get('port', '6667')
  292. def soundOn(self):
  293. if not self.config.has_key('soundon'):
  294. self.set('soundon', True)
  295. return self.config['soundon']
  296. def chatSound(self):
  297. return self.config.get('chatSound', True)
  298. def memoSound(self):
  299. return self.config.get('memoSound', True)
  300. def memoPing(self):
  301. return self.config.get('pingSound', True)
  302. def nameSound(self):
  303. return self.config.get('nameSound', True)
  304. def volume(self):
  305. return self.config.get('volume', 100)
  306. def trayMessage(self):
  307. return self.config.get('traymsg', True)
  308. def set(self, item, setting):
  309. self.config[item] = setting
  310. try:
  311. jsonoutput = json.dumps(self.config)
  312. except ValueError, e:
  313. raise e
  314. fp = open(self.filename, 'w')
  315. fp.write(jsonoutput)
  316. fp.close()
  317. def availableThemes(self):
  318. themes = []
  319. # Load user themes.
  320. for dirname, dirnames, filenames in os.walk(_datadir+'themes'):
  321. for d in dirnames:
  322. themes.append(d)
  323. # Also load embedded themes.
  324. if _datadir:
  325. for dirname, dirnames, filenames in os.walk('themes'):
  326. for d in dirnames:
  327. if d not in themes:
  328. themes.append(d)
  329. themes.sort()
  330. return themes
  331. def availableProfiles(self):
  332. profs = []
  333. profileloc = _datadir+'profiles'
  334. for dirname, dirnames, filenames in os.walk(profileloc):
  335. for filename in filenames:
  336. l = len(filename)
  337. if filename[l-3:l] == ".js":
  338. profs.append(filename[0:l-3])
  339. profs.sort()
  340. return [userProfile(p) for p in profs]
  341. class userProfile(object):
  342. def __init__(self, user):
  343. self.profiledir = _datadir+"profiles"
  344. if type(user) is PesterProfile:
  345. self.chat = user
  346. self.userprofile = {"handle":user.handle,
  347. "color": unicode(user.color.name()),
  348. "quirks": [],
  349. "theme": "pesterchum"}
  350. self.theme = pesterTheme("pesterchum")
  351. self.chat.mood = Mood(self.theme["main/defaultmood"])
  352. self.lastmood = self.chat.mood.value()
  353. self.quirks = pesterQuirks([])
  354. self.randoms = False
  355. initials = self.chat.initials()
  356. if len(initials) >= 2:
  357. initials = (initials, "%s%s" % (initials[0].lower(), initials[1]), "%s%s" % (initials[0], initials[1].lower()))
  358. self.mentions = [r"\b(%s)\b" % ("|".join(initials))]
  359. else:
  360. self.mentions = []
  361. else:
  362. fp = open("%s/%s.js" % (self.profiledir, user))
  363. self.userprofile = json.load(fp)
  364. fp.close()
  365. try:
  366. self.theme = pesterTheme(self.userprofile["theme"])
  367. except ValueError, e:
  368. self.theme = pesterTheme("pesterchum")
  369. self.lastmood = self.userprofile.get('lastmood', self.theme["main/defaultmood"])
  370. self.chat = PesterProfile(self.userprofile["handle"],
  371. QtGui.QColor(self.userprofile["color"]),
  372. Mood(self.lastmood))
  373. self.quirks = pesterQuirks(self.userprofile["quirks"])
  374. if "randoms" not in self.userprofile:
  375. self.userprofile["randoms"] = False
  376. self.randoms = self.userprofile["randoms"]
  377. if "mentions" not in self.userprofile:
  378. initials = self.chat.initials()
  379. if len(initials) >= 2:
  380. initials = (initials, "%s%s" % (initials[0].lower(), initials[1]), "%s%s" % (initials[0], initials[1].lower()))
  381. self.userprofile["mentions"] = [r"\b(%s)\b" % ("|".join(initials))]
  382. else:
  383. self.userprofile["mentions"] = []
  384. self.mentions = self.userprofile["mentions"]
  385. def setMood(self, mood):
  386. self.chat.mood = mood
  387. def setTheme(self, theme):
  388. self.theme = theme
  389. self.userprofile["theme"] = theme.name
  390. self.save()
  391. def setColor(self, color):
  392. self.chat.color = color
  393. self.userprofile["color"] = unicode(color.name())
  394. self.save()
  395. def setQuirks(self, quirks):
  396. self.quirks = quirks
  397. self.userprofile["quirks"] = self.quirks.plainList()
  398. self.save()
  399. def getRandom(self):
  400. return self.randoms
  401. def setRandom(self, random):
  402. self.randoms = random
  403. self.userprofile["randoms"] = random
  404. self.save()
  405. def getMentions(self):
  406. return self.mentions
  407. def setMentions(self, mentions):
  408. try:
  409. for (i,m) in enumerate(mentions):
  410. re.compile(m)
  411. except re.error, e:
  412. logging.error("#%s Not a valid regular expression: %s" % (i, e))
  413. else:
  414. self.mentions = mentions
  415. self.userprofile["mentions"] = mentions
  416. self.save()
  417. def getLastMood(self):
  418. return self.lastmood
  419. def setLastMood(self, mood):
  420. self.lastmood = mood.value()
  421. self.userprofile["lastmood"] = self.lastmood
  422. self.save()
  423. def getTheme(self):
  424. return self.theme
  425. def save(self):
  426. handle = self.chat.handle
  427. if handle[0:12] == "pesterClient":
  428. # dont save temp profiles
  429. return
  430. try:
  431. jsonoutput = json.dumps(self.userprofile)
  432. except ValueError, e:
  433. raise e
  434. fp = open("%s/%s.js" % (self.profiledir, handle), 'w')
  435. fp.write(jsonoutput)
  436. fp.close()
  437. @staticmethod
  438. def newUserProfile(chatprofile):
  439. if os.path.exists("%s/%s.js" % (_datadir+"profiles", chatprofile.handle)):
  440. newprofile = userProfile(chatprofile.handle)
  441. else:
  442. newprofile = userProfile(chatprofile)
  443. newprofile.save()
  444. return newprofile
  445. class PesterProfileDB(dict):
  446. def __init__(self):
  447. self.logpath = _datadir+"logs"
  448. if not os.path.exists(self.logpath):
  449. os.makedirs(self.logpath)
  450. try:
  451. fp = open("%s/chums.js" % (self.logpath), 'r')
  452. chumdict = json.load(fp)
  453. fp.close()
  454. except IOError:
  455. chumdict = {}
  456. fp = open("%s/chums.js" % (self.logpath), 'w')
  457. json.dump(chumdict, fp)
  458. fp.close()
  459. except ValueError:
  460. chumdict = {}
  461. fp = open("%s/chums.js" % (self.logpath), 'w')
  462. json.dump(chumdict, fp)
  463. fp.close()
  464. u = []
  465. for (handle, c) in chumdict.iteritems():
  466. options = dict()
  467. if 'group' in c:
  468. options['group'] = c['group']
  469. if 'notes' in c:
  470. options['notes'] = c['notes']
  471. if 'color' not in c:
  472. c['color'] = "#000000"
  473. if 'mood' not in c:
  474. c['mood'] = "offline"
  475. u.append((handle, PesterProfile(handle, color=QtGui.QColor(c['color']), mood=Mood(c['mood']), **options)))
  476. converted = dict(u)
  477. self.update(converted)
  478. def save(self):
  479. try:
  480. fp = open("%s/chums.js" % (self.logpath), 'w')
  481. chumdict = dict([p.plaindict() for p in self.itervalues()])
  482. json.dump(chumdict, fp)
  483. fp.close()
  484. except Exception, e:
  485. raise e
  486. def getColor(self, handle, default=None):
  487. if not self.has_key(handle):
  488. return default
  489. else:
  490. return self[handle].color
  491. def setColor(self, handle, color):
  492. if self.has_key(handle):
  493. self[handle].color = color
  494. else:
  495. self[handle] = PesterProfile(handle, color)
  496. def getGroup(self, handle, default="Chums"):
  497. if not self.has_key(handle):
  498. return default
  499. else:
  500. return self[handle].group
  501. def setGroup(self, handle, theGroup):
  502. if self.has_key(handle):
  503. self[handle].group = theGroup
  504. else:
  505. self[handle] = PesterProfile(handle, group=theGroup)
  506. self.save()
  507. def getNotes(self, handle, default=""):
  508. if not self.has_key(handle):
  509. return default
  510. else:
  511. return self[handle].notes
  512. def setNotes(self, handle, notes):
  513. if self.has_key(handle):
  514. self[handle].notes = notes
  515. else:
  516. self[handle] = PesterProfile(handle, notes=notes)
  517. self.save()
  518. def __setitem__(self, key, val):
  519. dict.__setitem__(self, key, val)
  520. self.save()
  521. class pesterTheme(dict):
  522. def __init__(self, name, default=False):
  523. possiblepaths = (_datadir+"themes/%s" % (name),
  524. "themes/%s" % (name),
  525. _datadir+"themes/pesterchum",
  526. "themes/pesterchum")
  527. self.path = "themes/pesterchum"
  528. for p in possiblepaths:
  529. if os.path.exists(p):
  530. self.path = p
  531. break
  532. self.name = name
  533. try:
  534. fp = open(self.path+"/style.js")
  535. theme = json.load(fp, object_hook=self.pathHook)
  536. fp.close()
  537. except IOError:
  538. theme = json.loads("{}")
  539. self.update(theme)
  540. if self.has_key("inherits"):
  541. self.inheritedTheme = pesterTheme(self["inherits"])
  542. if not default:
  543. self.defaultTheme = pesterTheme("pesterchum", default=True)
  544. def __getitem__(self, key):
  545. keys = key.split("/")
  546. try:
  547. v = dict.__getitem__(self, keys.pop(0))
  548. except KeyError, e:
  549. if hasattr(self, 'inheritedTheme'):
  550. return self.inheritedTheme[key]
  551. if hasattr(self, 'defaultTheme'):
  552. return self.defaultTheme[key]
  553. else:
  554. raise e
  555. for k in keys:
  556. try:
  557. v = v[k]
  558. except KeyError, e:
  559. if hasattr(self, 'inheritedTheme'):
  560. return self.inheritedTheme[key]
  561. if hasattr(self, 'defaultTheme'):
  562. return self.defaultTheme[key]
  563. else:
  564. raise e
  565. return v
  566. def pathHook(self, d):
  567. for (k, v) in d.iteritems():
  568. if type(v) is unicode:
  569. s = Template(v)
  570. d[k] = s.safe_substitute(path=self.path)
  571. return d
  572. def get(self, key, default):
  573. keys = key.split("/")
  574. try:
  575. v = dict.__getitem__(self, keys.pop(0))
  576. for k in keys:
  577. v = v[k]
  578. return default if v is None else v
  579. except KeyError:
  580. if hasattr(self, 'inheritedTheme'):
  581. return self.inheritedTheme.get(key, default)
  582. else:
  583. return default
  584. def has_key(self, key):
  585. keys = key.split("/")
  586. try:
  587. v = dict.__getitem__(self, keys.pop(0))
  588. for k in keys:
  589. v = v[k]
  590. return False if v is None else True
  591. except KeyError:
  592. if hasattr(self, 'inheritedTheme'):
  593. return self.inheritedTheme.has_key(key)
  594. else:
  595. return False