PageRenderTime 70ms CodeModel.GetById 33ms RepoModel.GetById 0ms app.codeStats 0ms

/Desktop/flingo.py

https://github.com/vicboy1109/Flingo
Python | 344 lines | 297 code | 4 blank | 43 comment | 41 complexity | 9c471e6278a4515d6aede1c98af9ace2 MD5 | raw file
  1. #!/usr/bin/env python
  2. # Copyright(c) 2010. Free Stream Media Corp. Released under the terms
  3. # of the GNU General Public License version 2.0.
  4. #
  5. # author: Omar Zennadi, David Harrison, AMansfield
  6. import json
  7. import netifaces
  8. try:
  9. from netifaces import interfaces, ifaddresses
  10. found_netifaces = True
  11. except ImportError,e:
  12. print "WARNING! Failed to import netifaces. You can obtain netifaces on Windows "
  13. print "and OSX by running:"
  14. print " easy_install netifaces"
  15. print "Using the less reliable socket.gethostbyname to determine ip address."
  16. found_netifaces = False
  17. import os
  18. import qt4reactor
  19. import socket
  20. import sys
  21. import urllib, urllib2
  22. from PyQt4 import QtCore, QtGui
  23. from ConfigParser import RawConfigParser
  24. #initialize the qt application and load the reactor for qt and twisted
  25. app = QtGui.QApplication([])
  26. qt4reactor.install()
  27. from twisted.internet import reactor
  28. from twisted.web.server import Site
  29. from twisted.web.static import File
  30. #get the values from the configuration file
  31. def update_config(conf_file, config={}):
  32. c = config
  33. if conf_file and os.path.isfile(conf_file):
  34. rv = RawConfigParser()
  35. rv.read(conf_file)
  36. if rv.has_section('fling'):
  37. for k,v in rv.items('fling'):
  38. if (v == 'None'):
  39. c[k] = None
  40. else:
  41. c[k] = v
  42. return c
  43. #find the configuration file
  44. def find_config():
  45. config = update_config('flingo.conf')
  46. conf = os.getenv('FLING_CONF', default='')
  47. if conf:
  48. config = update_config(conf, config)
  49. return config
  50. def get_local_ips():
  51. """Returns the set of known local IP addresses excluding the loopback
  52. interface."""
  53. global found_netifaces
  54. ips = []
  55. if found_netifaces:
  56. ifs = [ifaddresses(i).get(netifaces.AF_INET,None) for i in interfaces()]
  57. ifs = [i for i in ifs if i]
  58. ips = []
  59. for i in ifs:
  60. ips.extend([j.get('addr',None) for j in i])
  61. ips = [i for i in ips if i and i != "127.0.0.1"]
  62. if not found_netifaces or not ips:
  63. ip = socket.gethostbyname(socket.gethostname())
  64. if ip != "127.0.0.1":
  65. ips.append(ip)
  66. return ips
  67. #store configuration file values, used at cleanup when exiting
  68. def store_cfg_value(key, value=None):
  69. cfg_file = 'flingo.conf'
  70. if cfg_file and os.path.isfile(cfg_file):
  71. cfgprsr = RawConfigParser()
  72. cfgprsr.read(cfg_file)
  73. if (cfgprsr.has_section('fling')):
  74. cfgprsr.set('fling', key, value)
  75. savecfg = open(cfg_file, 'wb')
  76. cfgprsr.write(savecfg)
  77. #actions that appear as options in the menu
  78. ACTFILE = 'Fling File'
  79. ACTDIR = 'Fling Directory'
  80. ACTSETDIR = 'Set Directory'
  81. ACTQUIT = 'Quit'
  82. #keys in the configuration file
  83. NAMEKEY = 'name'
  84. GUIDKEY = 'guid'
  85. DIRKEY = 'directory'
  86. DIRCHKKEY = 'flingdir'
  87. CACHEKEY= 'cache'
  88. #values for the Fling Directory configuration option
  89. CHECKED = 'Checked'
  90. UNCHECKED = 'Unchecked'
  91. #initialize the configuration file and get the values
  92. config = find_config()
  93. PORT = int(config.get('port', 8080))
  94. FLING_ADDR_BASE = config.get('host', 'http://flingo.tv')
  95. DEV_NAME = config.get(NAMEKEY, None)
  96. DIR_PATH = config.get(DIRKEY, None)
  97. FLNGDIRCHK = config.get(DIRCHKKEY, UNCHECKED)
  98. CACHE = config.get(CACHEKEY, None)
  99. #setup the URLs
  100. DEVICE_CHECK = FLING_ADDR_BASE + '/fling/has_services'
  101. print "DEVICE_CHECK=", DEVICE_CHECK
  102. DEVICE_LOOKUP = FLING_ADDR_BASE + '/fling/lookup'
  103. FLING_URL = FLING_ADDR_BASE + '/fling/fling'
  104. #timer delay for polling the flung directory
  105. TIMERDELAY = 5000
  106. class FlingIcon(QtGui.QSystemTrayIcon):
  107. def __init__(self, parent=None):
  108. QtGui.QSystemTrayIcon.__init__(self, parent)
  109. #setting self.flingdir to None so that flinging the directory doesn't happen until ready
  110. self.flingdir = None
  111. #menu widget
  112. self.menu = QtGui.QMenu(parent)
  113. #check if there's devices to fling to
  114. self.findAnyDevice()
  115. #add menu options
  116. self.menu.addAction(ACTFILE, self.setFlingFiles)
  117. self.togdir = QtGui.QAction(ACTDIR, self.menu, triggered=self.toggleFlingDir)
  118. self.togdir.setCheckable(True)
  119. self.menu.addAction(self.togdir)
  120. self.menu.addAction(ACTSETDIR, self.setFlingDir)
  121. #menu item with submenu for detected devices
  122. self.devs = QtGui.QAction('Set Devices', self.menu)
  123. self.submenu = QtGui.QMenu(self.menu)
  124. self.devs.setMenu(self.submenu)
  125. self.menu.addAction(self.devs)
  126. #setup an action group for detected devices so the options are exclusive selection
  127. self.actgrp = QtGui.QActionGroup(self.submenu)
  128. self.actgrp.setExclusive(True)
  129. #timer for flinging from directory (updates as files are added)
  130. self.flingtimer = QtCore.QTimer()
  131. QtCore.QObject.connect(self.flingtimer, QtCore.SIGNAL('timeout()'), self.servDir)
  132. #initialize the guid, used later for flinging to single devices
  133. self.guid = None
  134. #add detected devices to the list
  135. self.getDevices()
  136. #quit option in menu
  137. quit=self.menu.addAction(ACTQUIT)
  138. self.connect(quit,QtCore.SIGNAL('triggered()'),self.quitApp)
  139. #initialize what files have been flung
  140. self.cache = CACHE
  141. #initialize the fling directory for use with flung directory contents
  142. self.flingdir = DIR_PATH
  143. #start the timer if a directory was loaded from the configuration file
  144. if (self.flingdir != None and FLNGDIRCHK == CHECKED):
  145. self.togdir.trigger()
  146. #if the timer isn't started, make sure the Fling Directory option is unchecked
  147. else:
  148. self.togdir.setChecked(False)
  149. self.setContextMenu(self.menu)
  150. self.icon = QtGui.QIcon('flingo.png')
  151. self.setIcon(self.icon)
  152. #does some cleanup and exits the app
  153. def quitApp(self):
  154. #store fling directory checked state
  155. if (self.togdir.isChecked()):
  156. store_cfg_value(DIRCHKKEY, CHECKED)
  157. else:
  158. store_cfg_value(DIRCHKKEY, UNCHECKED)
  159. #remove any files from the cache that don't exist anymore
  160. allFiles = str(self.cache).split(',')
  161. self.cache = None
  162. for singleFile in allFiles:
  163. if (os.path.isfile(singleFile)):
  164. if (self.cache != None):
  165. self.cache = self.cache + ','
  166. else:
  167. self.cache = ''
  168. self.cache = self.cache + singleFile
  169. #store all the files that have been flung
  170. store_cfg_value(CACHEKEY, self.cache)
  171. #exit cleanly
  172. self.flingtimer.stop()
  173. app.quit()
  174. sys.exit(app.exec_())
  175. #determine if there are any devices that can be flung to
  176. def findAnyDevice(self):
  177. response = {}
  178. try:
  179. print "searching for devices..."
  180. req = urllib2.Request(DEVICE_CHECK)
  181. response = json.loads(urllib2.urlopen(req).read())
  182. except Exception,e:
  183. print str(e)
  184. #warn the user if no devices are detected
  185. if response!=True:
  186. self.noDeviceWarning()
  187. def noDeviceWarning(self):
  188. QtGui.QMessageBox.warning(self.menu,'Warning','No flingable devices were found.')
  189. #obtain a list of all devices that can be flung to
  190. def getDevices(self):
  191. response = {}
  192. try:
  193. req = urllib2.Request(DEVICE_LOOKUP)
  194. response = json.loads(urllib2.urlopen(req).read())
  195. if (response.get('services') != None):
  196. self.resps = response['services']
  197. #setup an action for each name, guid pair
  198. #add an option to select all devices, this is a dummy option to fling to all devices by default
  199. act = QtGui.QAction('All Devices', self.actgrp, triggered=self.selDevice)
  200. act.setCheckable(True)
  201. self.submenu.addAction(act)
  202. act.trigger()
  203. for resp in self.resps:
  204. if ((resp.get(NAMEKEY) != None) and (resp.get(GUIDKEY) != None)):
  205. #associate each action with the action group
  206. act = QtGui.QAction(str(resp[NAMEKEY]), self.actgrp, triggered=self.selDevice)
  207. #make each action checkable
  208. act.setCheckable(True)
  209. self.submenu.addAction(act)
  210. #if the device name is in the config file, set this one as checked
  211. if (str(resp[NAMEKEY]) == DEV_NAME):
  212. act.trigger()
  213. except Exception,e:
  214. print str(e)
  215. #the select device callback to switch and use the device's guid
  216. def selDevice(self):
  217. #reset the guid everytime so that default is All Devices
  218. self.guid = None
  219. sel = str(self.sender().text())
  220. for resp in self.resps:
  221. if ((resp.get(NAMEKEY) != None) and (resp.get(GUIDKEY) != None)):
  222. #check if a specific device is selected
  223. if (str(resp[NAMEKEY]) == sel):
  224. self.guid = str(resp[GUIDKEY])
  225. #store the selected value in the configuration file
  226. store_cfg_value(NAMEKEY, sel)
  227. if (self.flingdir != None):
  228. self.resetFlingDir()
  229. #toggles fling directory option on/off, if no directory is selected, automatically launches the selection dialog
  230. def toggleFlingDir(self):
  231. #if checked and no folder selected yet, launch the directory dialog selection
  232. if (self.togdir.isChecked() and self.flingdir == None):
  233. self.setFlingDir()
  234. #if checked and folder has been selected (serving up the directory), stop the timer
  235. elif (not self.togdir.isChecked()):
  236. self.flingtimer.stop()
  237. else:
  238. #if checked and folder is already selected, start up the timer again
  239. self.flingtimer.start(TIMERDELAY)
  240. #displays and sets the fling directory, also starts the
  241. def setFlingDir(self):
  242. try:
  243. fileNames = None
  244. self.menu.setDisabled(True)
  245. dir = QtGui.QFileDialog.getExistingDirectory(parent=None,
  246. caption='Select directory to Fling from...', directory=QtCore.QDir.currentPath())
  247. self.menu.setDisabled(False)
  248. if (dir):
  249. self.flingdir = dir
  250. #store the selected directory in the configuration file
  251. store_cfg_value(DIRKEY, self.flingdir)
  252. self.togdir.setChecked(True)
  253. self.resetFlingDir()
  254. #if the user clicks cancel on the dialog AND a directory wasn't already selected, uncheck the Fling Directory option
  255. elif(self.flingdir == None):
  256. self.togdir.setChecked(False)
  257. except Exception, e:
  258. print str(e)
  259. #reset the fling directory cache in the event devices or directory selection changes
  260. def resetFlingDir(self):
  261. #make sure the timer is stopped
  262. self.flingtimer.stop()
  263. #clear the cached flings
  264. self.cache = None
  265. #install timer to check for files and fling them
  266. if (self.togdir.isChecked()):
  267. self.flingtimer.start(TIMERDELAY)
  268. #recursively fling the files in a directory (e.g. find the files in the selected directory and subdirectories)
  269. def servDir(self, dir=None):
  270. if (dir == None):
  271. dir = str(self.flingdir)
  272. fileNames = os.listdir(dir)
  273. if fileNames:
  274. for fileName in fileNames:
  275. fullPath = os.path.join(dir, fileName)
  276. if (os.path.isfile(fullPath)):
  277. #if file name already flung, don't fling it
  278. if (str(self.cache).find(fullPath) == -1):
  279. #add the file to the cache file
  280. if (self.cache != None):
  281. self.cache = self.cache + ','
  282. else:
  283. self.cache = ''
  284. self.cache = self.cache + fullPath
  285. #fling unflung file
  286. self.fling(fullPath)
  287. elif (os.path.isdir(fullPath)):
  288. self.servDir(fullPath)
  289. #select and fling one or more files
  290. def setFlingFiles(self):
  291. try:
  292. fileNames = []
  293. self.menu.setDisabled(True)
  294. fileNames = QtGui.QFileDialog.getOpenFileNames(parent=None,
  295. caption='Select files to Fling...', directory=QtCore.QDir.currentPath())
  296. self.menu.setDisabled(False)
  297. if fileNames:
  298. for fileName in fileNames:
  299. self.fling(str(fileName))
  300. except Exception, e:
  301. print str(e)
  302. #makes the actual "API" call to fling the file to the selected device(s)
  303. def fling(self, fileName):
  304. try:
  305. if sys.platform=='win32':
  306. fileName = fileName.replace('C:','')
  307. fileName = fileName.replace('\\', '/')
  308. name = os.path.basename(fileName)
  309. #http://flingo.tv/fling/fling?[url=U | deofuscator=D&context=C][&guid=G&title=T&description=D&image=I&preempt=P]
  310. params = {}
  311. ip = get_local_ips()[0]
  312. params['url'] = 'http://' + ip +':' + str(PORT) + fileName
  313. params['description'] = 'Desktop Fling of %s from %s' % (name, socket.gethostname())
  314. params['title'] = '%s via Desktop Fling' % name
  315. if (self.guid != None):
  316. params['guid'] = '%s' % self.guid
  317. data = urllib.urlencode(params)
  318. newurl = "http://flingo.tv/fling/fling?" + data
  319. #req = urllib2.Request(FLING_URL, data)
  320. req = urllib2.Request(newurl)
  321. response = urllib2.urlopen(req).read()
  322. except Exception, e:
  323. print str(e)
  324. print "Creating FlingIcon"
  325. i = FlingIcon()
  326. i.show()
  327. root = '/'
  328. if sys.platform == 'win32':
  329. root = 'C:\\'
  330. doc_root = File(root)
  331. print "Starting reactor"
  332. site = Site(doc_root)
  333. reactor.listenTCP(PORT, site)
  334. if sys.platform == 'darwin':
  335. app.exec_()
  336. reactor.run()