Add option to start pdb on exceptions
[pyrook:pyrook.git] / PyRook / PyRook.py
1 import os
2 from collections import defaultdict
3 try: import argparse
4 except ImportError: argparse = None
5
6 from PySide.QtCore import *
7 from PySide.QtGui import *
8 from PySide.QtNetwork import *
9 try: import setproctitle
10 except ImportError: pass
11 else: setproctitle.setproctitle("pyrook")
12
13 from Debug import debug
14 from ThemeEngine import theme
15 from ChatWindow import ChatWindow
16 from Externals import pygeoip
17 from SettingsConvenience import settings_get_bool
18
19 class PyRookApplication(QApplication):
20     """The QApplication for PyRook.
21
22     Contains a list of each ChatWindow and each Server in the application."""
23     settings_changed = Signal()
24     def __init__(self, *args, **kwargs):
25         QApplication.__init__(self, *args, **kwargs)
26
27         self.setOrganizationName("Sentynel")
28         self.setApplicationName("PyRook")
29
30         debug.enabled = settings_get_bool("config/debug", True)
31         debug.pdb = settings_get_bool("config/debug_pdb", False)
32         debug.debug("PyRook Application started")
33
34         if "_MEIPASS2" in os.environ:
35             # frozen executable
36             wd = os.environ["_MEIPASS2"]
37             self.addLibraryPath(wd)
38             os.chdir(wd)
39
40         self.server = None
41         self.window = None
42
43         self.datadir = QDesktopServices.storageLocation(QDesktopServices.DataLocation)
44         if not os.path.exists(self.datadir):
45             QDir().mkpath(self.datadir)
46
47         # install icons
48         files = os.listdir(".")
49         for copy in files:
50             if (copy.startswith("pyrook_logo")) | (copy == "GeoIP.dat"):
51                 iconpath = os.path.join(self.datadir, copy)
52                 # check if there is an existing identical file before copying
53                 if os.path.exists(iconpath):
54                     src_stat = os.stat(copy)
55                     dest_stat = os.stat(iconpath)
56                     if dest_stat.st_mtime >= src_stat.st_mtime:
57                         # existing file is more recent than candidate
58                         if dest_stat.st_size == src_stat.st_size:
59                             # and files are the same size, so don't copy
60                             continue
61                 try:
62                     logo = open(copy, "rb")
63                 except IOError:
64                     pass
65                 else:
66                     # copy to data location
67                     try:
68                         with open(iconpath, "wb") as f:
69                             f.write(logo.read())
70                     except IOError:
71                         pass
72                     logo.close()
73
74         # load icons
75         icon = QIcon()
76         files = os.listdir(self.datadir)
77         if ("svg" in QImageReader.supportedImageFormats()) & ("pyrook_logo.svg" in files):
78             ext = ".svg"
79         else:
80             ext = ".png"
81         for i in files:
82             if i.endswith(ext):
83                 icon.addFile(os.path.join(self.datadir, i))
84         self.setWindowIcon(icon)
85
86         self.cache_dir = QDir(QDesktopServices.storageLocation(QDesktopServices.CacheLocation))
87         debug.debug("Cache location: " + self.cache_dir.path())
88         self.cache_dir.mkpath(self.cache_dir.path())
89
90         self.n_exit = 0
91         self.quitting = False
92
93         if argparse:
94             self.handle_args()
95         else:
96             self.args = defaultdict(lambda: None)
97
98         self.geolocate_db = None
99         self.settings_changed.connect(self.handle_settings_changed)
100         self.handle_settings_changed()
101
102         self.setQuitOnLastWindowClosed(False)
103         self.lastWindowClosed.connect(self.exit_timeout)
104
105         self.window = ChatWindow(self)
106         self.window.show()
107
108     def close_all(self):
109         debug.debug("close_all called")
110         self.quitting = True
111         self.window.close()
112         self.check_exit_requests()
113
114     def exit_item_added(self):
115         self.n_exit += 1
116
117     def exit_item_done(self):
118         # this is used to count network operations which should be completed before the
119         # application exits
120         self.n_exit -= 1
121         debug.debug("Exit item done, remaining: " + str(self.n_exit))
122         if self.quitting:
123             self.check_exit_requests()
124
125     def check_exit_requests(self):
126         if self.n_exit == 0:
127             debug.debug("No remaining exit items, quitting.")
128             self.quit()
129
130     def commitData(self, sm):
131         self.closeAllWindows()
132
133     def exit_timeout(self):
134         # timeout for leave requests in case of network issues etc
135         QTimer.singleShot(20000, self.quit)
136
137     def handle_args(self):
138         parser = argparse.ArgumentParser(description="RookChat client")
139         parser.add_argument("-u", "--username", help="Your login username")
140         parser.add_argument("-p", "--password", help="Your login password")
141         parser.add_argument("-s", "--server", help="Full URL for the RookChat server to connect to, e.g. http://www.rinkworks.com/rinkchat/ (the default)")
142         parser.add_argument("-n", "--server-name", help="Name of the server to connect to")
143         parser.add_argument("other", help="Standard Qt arguments", nargs="*")
144         args, leftover = parser.parse_known_args(self.arguments())
145         self.args = vars(args)
146
147     def handle_settings_changed(self):
148         # install proxy if applicable
149         settings = QSettings()
150         if settings_get_bool("config/proxy", settings=settings):
151             debug.debug("Installing proxy")
152             if settings.value("config/proxy_type") == "HTTP":
153                 ptype = QNetworkProxy.HttpCachingProxy
154             else:
155                 ptype = QNetworkProxy.Socks5Proxy
156             try:
157                 port = int(settings.value("config/proxy_port"))
158             except ValueError:
159                 port = 0
160             proxy = QNetworkProxy(ptype,
161                                   settings.value("config/proxy_hostname",""),
162                                   port,
163                                   settings.value("config/proxy_username",""),
164                                   settings.value("config/proxy_password",""))
165         else:
166             proxy = QNetworkProxy(QNetworkProxy.NoProxy)
167         QNetworkProxy.setApplicationProxy(proxy)
168         # geolocation
169         self.geolocate = settings_get_bool("config/geolocate", settings=settings)
170         if (self.geolocate) & (self.geolocate_db is None) & (pygeoip is not None):
171             dbpath = os.path.join(self.datadir, "GeoIP.dat")
172             try:
173                 self.geolocate_db = pygeoip.GeoIP(dbpath, pygeoip.MEMORY_CACHE)
174             except (pygeoip.GeoIPError, IOError):
175                 debug.debug("Could not open GeoIP database.")
176         elif not self.geolocate:
177             self.geolocate_db = None
178
179     def lookup_ip(self, ip):
180         if not self.geolocate:
181             return None
182         if not self.geolocate_db:
183             return None
184         try:
185             country = self.geolocate_db.country_name_by_addr(ip)
186         except pygeoip.GeoIPError:
187             country = ""
188         try:
189             region = self.geolocate_db.region_by_addr(ip)["region_name"]
190         except (pygeoip.GeoIPError, KeyError):
191             region = ""
192         if all((country, region)):
193             return ", ".join((region, country))
194         else:
195             return "".join((region, country))