PageRenderTime 32ms CodeModel.GetById 25ms RepoModel.GetById 1ms app.codeStats 0ms

/Campus-Warder.app/Contents/Resources/cw.py

https://github.com/sinzim/Campus-Warder
Python | 392 lines | 363 code | 2 blank | 27 comment | 5 complexity | 3aa1da30a3359ed2d9c5be78ce03d22a MD5 | raw file
  1. #!/usr/bin/python
  2. #coding=UTF-8
  3. '''
  4. Created on 1. jan. 2010
  5. This script is created with the purpose of handling bandwidth issues
  6. with certain programs. The main problem being that the landlord of a
  7. certain university campus village disconnect their user's Internet
  8. connection if they exceed the 10gb/24h traffic limit.
  9. @author: Harald Hauknes <harald (att) hauknes (dot) org>
  10. Written for Python 2.6 with PIL
  11. '''
  12. import time
  13. import os
  14. import ConfigParser
  15. from Tkinter import Tk, Frame, Label
  16. from Tkconstants import FALSE, LEFT, RIGHT, TOP, X, W, N
  17. import tkFont
  18. import urllib2
  19. import base64
  20. #TODO: Test in Ubuntu, Windows and OSX
  21. #TODO: GUI for configuring cw.cfg
  22. #TODO: Write README
  23. #TODO: Add pokemon error handling?
  24. class CWGUI:
  25. '''
  26. Draws the GUI and initiates the core components of the program.
  27. '''
  28. def __init__(self):
  29. self.root = Tk()
  30. self.root.title("CW")
  31. self.root.resizable(width=FALSE, height=FALSE)
  32. self.mainframe = Frame(self.root, bg='white', border=0)
  33. self.values = CONFIG()
  34. self.labels_pnames = []
  35. self.labels_status = []
  36. self.main_window(self.values)
  37. def main_window(self, values):
  38. '''
  39. Initiates the main components of the program, and draws the GUI.
  40. '''
  41. #HEADER
  42. font_header = tkFont.Font(family="Helvetica", size=16, weight="bold")
  43. label_header = Label(self.mainframe, text="Campus-Warder",
  44. #fg="LightCyan3",
  45. fg='LightCyan4',
  46. bg='white', font=font_header)
  47. label_header.pack(anchor=N, pady=5)
  48. #BANDWIDTHUSAGE
  49. frame_bandwidth = Frame(self.mainframe, border=2, relief="groove",
  50. bg='white')
  51. label_bandwidth_decorator = Label(frame_bandwidth, text="Usage:\n",
  52. bg='white')
  53. label_bandwidth_decorator.pack(side=LEFT, padx=5)
  54. # Colors and font
  55. upcolor = color_indicator(values.parser.up, values.uplimit)
  56. downcolor = color_indicator(values.parser.down, values.downlimit)
  57. font_numbers = tkFont.Font(family="Helvetica", size=12, weight="bold")
  58. # Values
  59. label_up_decorator = Label(frame_bandwidth, bg='white', text="Up: ")
  60. label_up_decorator.pack(side=LEFT)
  61. self.label_up = Label(frame_bandwidth, bg='white', fg=upcolor,
  62. font=font_numbers, text=str(values.parser.up) + " MB")
  63. self.label_up.pack(side=LEFT)
  64. label_down_decorator = Label(frame_bandwidth, bg='white',
  65. text=" / Down: ")
  66. label_down_decorator.pack(side=LEFT)
  67. self.label_down = Label(frame_bandwidth, bg='white', fg=downcolor,
  68. text= str(values.parser.down) + " MB", font=font_numbers)
  69. self.label_down.pack(side=LEFT, padx= 5)
  70. frame_bandwidth.pack(side=TOP)
  71. #PROGRAMS AND STATUS
  72. frame_status = Frame(self.mainframe, bg='white', border=2,
  73. relief="groove")
  74. frames_status = []
  75. for i in range(0, len(values.programs)):
  76. frames_status.append(Frame(frame_status, bg='white', border=0,
  77. relief='groove'))
  78. #Program display names
  79. self.labels_pnames.append(Label(frames_status[i], bg='white',
  80. text=values.programs[i].display_name + ": "))
  81. self.labels_pnames[i].pack(side=LEFT, padx=6)
  82. #Status
  83. self.labels_status.append(Label(frames_status[i], bg='white',
  84. text=values.programs[i].status))
  85. self.labels_status[i].pack(side=RIGHT, padx=6)
  86. frames_status[i].pack(side=TOP, fill=X)
  87. #PACK
  88. frame_status.pack(side=TOP, fill=X, anchor=W)
  89. self.mainframe.pack()
  90. #ENDLESS LOOP
  91. #On the first run, wait 3 seconds so that the GUI
  92. #has a chance to draw
  93. self.root.after(3000, self.update)
  94. def update(self):
  95. '''
  96. Updates the status of the programs, depending on the output of the OS
  97. tasklist command
  98. '''
  99. #Update every 2 minutes
  100. self.root.after(60*1000*2, self.update)
  101. #BANDWIDTH STATUS
  102. # Colors
  103. bandwidth = self.values.get_bandwidth()
  104. upcolor = color_indicator(bandwidth[0], self.values.uplimit)
  105. downcolor = color_indicator(bandwidth[1], self.values.downlimit)
  106. # Values
  107. self.label_up["fg"] = upcolor,
  108. self.label_up["text"] = str(self.values.parser.up) + " MB"
  109. self.label_up.update()
  110. self.label_down["fg"] = downcolor,
  111. self.label_down["text"] = str(self.values.parser.down) + " MB"
  112. self.label_down.update()
  113. #PROGRAM STATUS
  114. if self.values.os == "Windows":
  115. ps_output = os.popen("tasklist").read()
  116. else:
  117. ps_output = os.popen("ps aux").read()
  118. #Loop through all programs
  119. for i in range(0, len(self.values.programs)):
  120. #If currently active
  121. if ps_output.find(self.values.programs[i].process_name) > 5:
  122. self.values.programs[i].status = "Active"
  123. self.values.programs[i].update()
  124. #If not currently active
  125. elif ps_output.find(self.values.programs[i].process_name) == -1:
  126. if self.values.programs[i].status == "Active":
  127. self.values.programs[i].status = "KBU"
  128. self.labels_status[i]["text"] = self.values.programs[i].status
  129. self.labels_status[i].update()
  130. #UPDATE GUI
  131. self.mainframe.update()
  132. class CWPROCESS:
  133. '''
  134. Class to handle interaction with the various programs we need to
  135. monitor and control.
  136. '''
  137. def __init__(self, process_name, up_limit, down_limit, display_name,
  138. values, full_path=None, status="MIA"):
  139. '''
  140. Arguments:
  141. process_name -- The name of the process as the OS sees it
  142. #TODO:write proper docstrings
  143. '''
  144. self.process_name = process_name
  145. self.up_limit = up_limit
  146. self.down_limit = down_limit
  147. self.display_name = display_name
  148. self.full_path = full_path
  149. #MIA=it never ran, KIA=it ran but we killed it, Active = running now, KBU =
  150. #killed by user
  151. self.status = status
  152. self.values = values
  153. def update(self):
  154. if (self.status == 'MIA') or (self.status == 'KBU'):
  155. #This status is updated from the update status loop
  156. return
  157. elif self.status == 'KIA':
  158. #TODO: Test this
  159. if (self.up_limit > self.values.parser.up) or (self.down_limit >
  160. self.values.parser.down):
  161. self.revive()
  162. elif self.status == 'Active':
  163. if (self.up_limit < self.values.parser.up) or (self.down_limit <
  164. self.values.parser.down):
  165. self.kill()
  166. def kill(self):
  167. '''
  168. Kills the process using the OS own kill command
  169. '''
  170. os.system(self.values.kill_command + " " + self.process_name)
  171. self.status = "KIA"
  172. def revive(self):
  173. '''
  174. Starts processes that has earlier been killed by this script and
  175. is eligble to be started again.
  176. '''
  177. if self.values.os == "OSX":
  178. os.system("/Applications/" + self.process_name + ".app" +
  179. "/Contents/MacOS/" + self.process_name + " &")
  180. #TODO: Investigate revive hang on OSX
  181. #TODO: Test on all OSes
  182. #TODO: Switch to use of subprocess module
  183. if self.values.os == "linux":
  184. os.system(self.process_name + " &")
  185. if self.values.os == "Windows":
  186. os.system("start " + self.full_path)
  187. self.status = "Active"
  188. class CONFIG:
  189. def __init__(self):
  190. #OS
  191. if os.path.isdir("/Applications"):
  192. self.os = "OSX"
  193. elif os.path.isdir("C:/Windows"):
  194. self.os = "Windows"
  195. elif os.path.isdir("/usr/bin"):
  196. self.os = "linux"
  197. config = ConfigParser.RawConfigParser()
  198. config.read('cw.cfg')
  199. #LIMITS
  200. self.uplimit = int(config.get('CW', 'uplimit'))
  201. self.downlimit = int(config.get('CW', 'downlimit'))
  202. #PROGRAMS
  203. all_programs_processed = False
  204. program_no = 1
  205. self.programs = []
  206. while not all_programs_processed:
  207. try:
  208. process_name = config.get('Program' + str(program_no),
  209. 'process_name')
  210. up_limit = int(config.get('Program' + str(program_no),
  211. 'up_limit'))
  212. down_limit = int(config.get('Program' + str(program_no),
  213. 'down_limit'))
  214. display_name = config.get('Program' + str(program_no),
  215. 'display_name')
  216. if self.os == "Windows":
  217. full_path = config.get('Program' + str(program_no),
  218. 'full_path')
  219. self.programs.append(CWPROCESS(process_name, up_limit,
  220. down_limit, display_name, self))
  221. program_no += 1
  222. except ConfigParser.NoSectionError:
  223. break
  224. #KILL
  225. self.kill_command = "killall"
  226. if self.os == "Windows":
  227. self.kill_command = "taskkill /IM"
  228. #ROUTER INFO
  229. self.router_url = config.get('CW', 'router_url')
  230. self.router_user = config.get('CW', 'router_user')
  231. self.router_pass = config.get('CW', 'router_pass')
  232. #BANDWIDTH
  233. self.parser = CWPARSER(self)
  234. # Initiate values for the parser
  235. self.get_bandwidth()
  236. self.revive_list = []
  237. self.kill_count = 0
  238. def get_bandwidth(self):
  239. self.parser.parse()
  240. while ((self.parser.up == -1) or (self.parser.down == -1)):
  241. # If we for some reason cannot parse the values
  242. # we wait a second and see if we can
  243. self.parser.parse()
  244. time.sleep(1)
  245. #TODO: Inform user if this goes on too long
  246. return (self.parser.up, self.parser.down)
  247. def color_indicator(value, limit):
  248. '''
  249. Compares two values and returns a coloring based
  250. on the percentage the first value is of the second.
  251. Argument:
  252. value -- The first value
  253. limit -- The second value which the first is measured against
  254. '''
  255. if value < (limit * 0.33):
  256. color = 'darkgreen'
  257. elif value < (limit * 0.66):
  258. color = 'orange'
  259. else:
  260. color = 'red'
  261. return color
  262. class CWPARSER:
  263. '''
  264. This class handles the parsing of the html
  265. from the router, it gets it's values from the
  266. configuration object.
  267. '''
  268. def __init__(self, config):
  269. self.config = config
  270. self.up = 0
  271. self.down = 0
  272. def parse(self):
  273. '''
  274. Authenticates to the router, and parses the html of the
  275. 24/h view to see how much data has been transmitted
  276. '''
  277. request = urllib2.Request(self.config.router_url)
  278. base64string = base64.encodestring(
  279. '%s:%s' % (self.config.router_user,
  280. self.config.router_pass))[:-1]
  281. authheader = "Basic %s" % base64string
  282. request.add_header("Authorization", authheader)
  283. try:
  284. handle = urllib2.urlopen(request)
  285. except IOError:
  286. # here we shouldn't fail if the username/password is right
  287. print "It looks like the username or password is wrong."
  288. self.up = -2
  289. self.down = -2
  290. return
  291. html = handle.read()
  292. # Learned from trial and error the correct export value is extracted from the
  293. # 4th occurrence of the tx_total value.
  294. unsearched_tx = html
  295. unsearched_rx = html
  296. for i in range (0, 4):
  297. #This code is very old, and I cant bother to make it look better ;)
  298. #TODO: This is horrible, switch to regex
  299. index_tx = unsearched_tx.find("tx_total")
  300. index_rx = unsearched_rx.find("rx_total")
  301. if (i != 3):
  302. # if we aren't on the fourth occurrence we just keep searching
  303. unsearched_tx = (
  304. unsearched_tx[index_tx+25:len(unsearched_tx)])
  305. unsearched_rx = unsearched_rx[index_rx+25:len(unsearched_rx)]
  306. else:
  307. # extracting substring containing desired value
  308. tx_value = unsearched_tx[index_tx:index_tx+30]
  309. rx_value = unsearched_rx[index_rx:index_rx+30]
  310. whitepace_tx = tx_value.find(" ")
  311. whitepaceindexrx = rx_value.find(" ")
  312. # cutting away the identifying variable
  313. tx_value = tx_value[whitepace_tx+1:len(tx_value)]
  314. rx_value = rx_value[whitepaceindexrx+1:len(rx_value)]
  315. # the value may end in a whitespace or bracket
  316. # the rx value seems to be able to end in a comma
  317. whitepace_tx = tx_value.find(" ")
  318. bracket_tx = tx_value.find("}")
  319. whitepaceindexrx = rx_value.find(" ")
  320. bracketindexrx = rx_value.find("}")
  321. commaindex_rx = rx_value.find(",")
  322. # take either the bracket or whitespace that comes first
  323. if (whitepace_tx>bracket_tx and
  324. bracket_tx>0 and whitepace_tx>0):
  325. bracket_tx = whitepace_tx
  326. tx_value = tx_value[0:bracket_tx]
  327. if (whitepaceindexrx>bracketindexrx and
  328. bracketindexrx>0 and whitepaceindexrx>0):
  329. bracketindexrx = whitepaceindexrx
  330. # Seems that rx is always separated by comma
  331. if (commaindex_rx>bracketindexrx
  332. and commaindex_rx > 0):
  333. bracketindexrx = commaindex_rx
  334. rx_value = rx_value[0:bracketindexrx]
  335. i += 1
  336. # Display the value in megabytes
  337. try:
  338. self.up = int(tx_value)/(1024*1024)
  339. self.down = int(rx_value)/(1024*1024)
  340. # Sometimes the value is parsed incorrectly,
  341. # if so - ignore it - this script is meant to be looped
  342. except ValueError:
  343. self.up = -1
  344. self.down = -1
  345. def main():
  346. '''
  347. Initiates CampusWarder, takes no arguments.
  348. '''
  349. app = CWGUI()
  350. app.mainframe.mainloop()
  351. if __name__ == '__main__':
  352. main()