servicemp3.cpp: more simple/flexible streaming detection
[enigma2.git] / lib / python / Plugins / SystemPlugins / Hotplug / plugin.py
1 from Plugins.Plugin import PluginDescriptor
2 from Components.Harddisk import harddiskmanager
3 from Tools.Directories import fileExists
4
5 hotplugNotifier = [ ]
6 bdpoll = None
7
8 def processHotplugData(self, v):
9         print "hotplug:", v
10         action = v.get("ACTION")
11         device = v.get("DEVPATH")
12         physdevpath = v.get("PHYSDEVPATH")
13         media_state = v.get("X_E2_MEDIA_STATUS")
14
15         dev = device.split('/')[-1]
16
17         if action is not None and action == "add":
18                 error, blacklisted, removable, is_cdrom, partitions, medium_found = harddiskmanager.addHotplugPartition(dev, physdevpath)
19                 if bdpoll and removable or is_cdrom:
20                         bdpoll.addDevice(dev, is_cdrom, medium_found)
21         elif action is not None and action == "remove":
22                 if bdpoll:
23                         bdpoll.removeDevice(dev)
24                 harddiskmanager.removeHotplugPartition(dev)
25         elif media_state is not None:
26                 if media_state == '1':
27                         harddiskmanager.removeHotplugPartition(dev)
28                         harddiskmanager.addHotplugPartition(dev, physdevpath)
29                 elif media_state == '0':
30                         harddiskmanager.removeHotplugPartition(dev)
31
32         for callback in hotplugNotifier:
33                 try:
34                         callback(dev, action or media_state)
35                 except AttributeError:
36                         hotplugNotifier.remove(callback)
37
38 CDROM_DRIVE_STATUS = 0x5326
39 CDROM_MEDIA_CHANGED = 0x5325
40 CDSL_CURRENT = ((int)(~0>>1))
41 CDS_NO_INFO = 0
42 CDS_NO_DISC = 1
43 CDS_TRAY_OPEN = 2
44 CDS_DRIVE_NOT_READY = 3
45 CDS_DISC_OK = 4
46 ENOMEDIUM = 159
47 IOC_NRBITS = 8
48 IOC_NRSHIFT = 0
49 IOC_TYPESHIFT = (IOC_NRSHIFT+IOC_NRBITS)
50 BLKRRPART = ((0x12<<IOC_TYPESHIFT) | (95<<IOC_NRSHIFT))
51
52 def autostart(reason, **kwargs):
53         global bdpoll
54         if reason == 0:
55                 print "starting hotplug handler"
56
57                 if fileExists('/dev/.udev'):
58                         global netlink
59                         from enigma import eSocketNotifier, eTimer, ePythonMessagePump
60                         import socket
61                         from select import POLLIN, POLLPRI
62
63                         class Netlink:
64                                 def __init__(self):
65                                         self.netlink = socket.socket(socket.AF_NETLINK, socket.SOCK_DGRAM, 15)
66                                         self.netlink.setsockopt(socket.SOL_SOCKET, socket.SO_SNDBUF, 65536)
67                                         self.netlink.setsockopt(socket.SOL_SOCKET, socket.SO_RCVBUF, 65536)
68                                         self.netlink.bind((0, 1))
69                                         self.sn = eSocketNotifier(self.netlink.fileno(), POLLIN|POLLPRI)
70                                         self.sn.callback.append(self.dataAvail)
71
72                                 def dataAvail(self, what):
73                                         received = self.netlink.recvfrom(16384)
74 #                                       print "HOTPLUG(%d):" %(what), received
75
76                                         data = received[0].split('\0')[:-1]
77                                         v = {}
78
79                                         for x in data:
80                                                 i = x.find('=')
81                                                 var, val = x[:i], x[i+1:]
82                                                 v[var] = val
83
84                                         if v['SUBSYSTEM'] == 'block' and v['ACTION'] in ('add', 'remove'):
85                                                 processHotplugData(self, v)
86
87                         from threading import Thread, Semaphore, Lock
88
89                         class ThreadQueue:
90                                 def __init__(self):
91                                         self.__list = [ ]
92                                         self.__lock = Lock()
93
94                                 def push(self, val):
95                                         list = self.__list
96                                         lock = self.__lock
97                                         lock.acquire()
98                                         list.append(val)
99                                         lock.release()
100
101                                 def pop(self):
102                                         list = self.__list
103                                         lock = self.__lock
104                                         lock.acquire()
105                                         ret = list[0]
106                                         del list[0]
107                                         lock.release()
108                                         return ret
109
110                         import os
111                         import errno
112                         import fcntl
113
114                         class BDPoll(Thread):
115                                 CHECK_INTERVAL = 2000
116                                 MSG_MEDIUM_REMOVED = 1
117                                 MSG_MEDIUM_INSERTED = 2
118                                 MSG_POLL_FINISHED = 4
119                                 def __init__(self):
120                                         Thread.__init__(self)
121                                         self.__sema = Semaphore(0)
122                                         self.__lock = Lock()
123                                         self.running = False
124                                         self.devices_to_poll = { }
125                                         self.messages = ThreadQueue()
126                                         self.checkTimer = eTimer()
127                                         self.checkTimer.callback.append(self.timeout)
128                                         self.checkTimer.start(BDPoll.CHECK_INTERVAL, True)
129                                         self.mp = ePythonMessagePump()
130                                         self.mp.recv_msg.get().append(self.gotThreadMsg)
131                                         self.start()
132
133                                 def gotThreadMsg(self, msg):
134                                         msg = self.messages.pop()
135                                         if msg[0] == BDPoll.MSG_MEDIUM_REMOVED:
136                                                 print "MSG_MEDIUM_REMOVED"
137                                                 harddiskmanager.removeHotplugPartition(msg[1])
138                                         elif msg[0] == BDPoll.MSG_MEDIUM_INSERTED:
139                                                 print "MSG_MEDIUM_INSERTED"
140                                                 harddiskmanager.addHotplugPartition(msg[1])
141                                         elif msg[0] == BDPoll.MSG_POLL_FINISHED:
142                                                 self.checkTimer.start(BDPoll.CHECK_INTERVAL, True)
143
144                                 def timeout(self):
145                                         self.__sema.release() # start bdpoll loop in thread
146
147                                 def is_mounted(self, dev):
148                                         mounts = file('/proc/mounts').read()
149                                         return mounts.find(dev) != -1
150
151                                 def run(self):
152                                         sema = self.__sema
153                                         lock = self.__lock
154                                         messages = self.messages
155                                         mp = self.mp
156                                         self.running = True
157                                         while self.running:
158                                                 sema.acquire()
159                                                 self.__lock.acquire()
160                                                 devices_to_poll = self.devices_to_poll.items()
161                                                 self.__lock.release()
162                                                 devices_to_poll_processed = [ ]
163                                                 for device, state in devices_to_poll:
164                                                         got_media = False
165                                                         is_cdrom, prev_media_state = state
166                                                         if is_cdrom:
167                                                                 try:
168                                                                         fd = os.open("/dev/" + device, os.O_RDONLY | os.O_NONBLOCK | os.O_EXCL)
169                                                                 except OSError, err:
170                                                                         if err.errno == errno.EBUSY:
171                                                                                 print "open cdrom exclusive failed:",
172                                                                                 if not self.is_mounted(device):
173                                                                                         print "not mounted"
174                                                                                         continue
175                                                                                 try:
176                                                                                         print "mounted... try non exclusive"
177                                                                                         fd = os.open("/dev/" + device, os.O_RDONLY | os.O_NONBLOCK)
178                                                                                 except OSError, err:
179                                                                                         print "open cdrom not exclusive failed", os.strerror(err.errno)
180                                                                                         continue
181                                                                 #here the fs must be valid!
182                                                                 try:
183                                                                         ret = fcntl.ioctl(fd, CDROM_DRIVE_STATUS, CDSL_CURRENT)
184                                                                 except IOError, err:
185                                                                         print "ioctl CDROM_DRIVE_STATUS failed", os.strerror(err.errno)
186                                                                 else:
187                                                                         if ret in (CDS_NO_INFO, CDS_NO_DISC, CDS_TRAY_OPEN, CDS_DRIVE_NOT_READY):
188                                                                                 pass
189                                                                         elif ret == CDS_DISC_OK:
190                                                                                 #todo new kernels support events to userspace event on media change
191                                                                                 #but not 2.6.18.... see hotplug-ng bdpoll.c
192                                                                                 got_media = True
193                                                                 os.close(fd)
194                                                         else:
195                                                                 try:
196                                                                         fd = os.open("/dev/" + device, os.O_RDONLY)
197                                                                 except OSError, err:
198                                                                         if err.errno == ENOMEDIUM:
199                                                                                 pass
200                                                                         else:
201                                                                                 print "open non cdrom failed", os.strerror(err.errno)
202                                                                                 continue
203                                                                 else:
204                                                                         got_media = True
205                                                                         os.close(fd)
206                                                         if prev_media_state:
207                                                                 if not got_media:
208                                                                         print "media removal detected on", device
209                                                                         try:
210                                                                                 fd = os.open("/dev/" + device, os.O_RDONLY | os.O_NONBLOCK)
211                                                                         except OSError, err:
212                                                                                 print "open device for blkrrpart ioctl failed", os.strerror(err.errno)
213                                                                         else:
214                                                                                 try:
215                                                                                         fcntl.ioctl(fd, BLKRRPART)
216                                                                                 except IOError, err:
217                                                                                         print "ioctl BLKRRPART failed", os.strerror(err.errno)
218                                                                                 os.close(fd)
219                                                         else:
220                                                                 if got_media:
221                                                                         print "media insertion detected on", device
222                                                         devices_to_poll_processed.append((device, is_cdrom, got_media))
223                                                 self.__lock.acquire()
224                                                 for device, is_cdrom, state in devices_to_poll_processed:
225                                                         old_state = self.devices_to_poll.get(device)
226                                                         if old_state is not None and old_state[1] != state:
227                                                                 msg = state and BDPoll.MSG_MEDIUM_INSERTED or BDPoll.MSG_MEDIUM_REMOVED
228                                                                 self.devices_to_poll[device] = (is_cdrom, state)
229                                                                 messages.push((msg, device))
230                                                                 mp.send(0)
231
232                                                 self.__lock.release()
233                                                 messages.push((self.MSG_POLL_FINISHED,))
234                                                 mp.send(0)
235
236                                 def addDevice(self, device, is_cdrom, inserted):
237                                         self.__lock.acquire()
238                                         if device in self.devices_to_poll:
239                                                 print "device", device, "already in bdpoll"
240                                         else:
241                                                 print "add device", device, "to bdpoll current state:",
242                                                 if inserted:
243                                                         print "medium inserted"
244                                                 else:
245                                                         print "medium removed"
246                                                 self.devices_to_poll[device] = (is_cdrom, inserted)
247                                         self.__lock.release()
248
249                                 def removeDevice(self, device):
250                                         self.__lock.acquire()
251                                         if device in self.devices_to_poll:
252                                                 print "device", device, "removed from bdpoll"
253                                                 del self.devices_to_poll[device]
254                                         else:
255                                                 print "try to del not exist device", device, "from bdpoll"
256                                         self.__lock.release()
257
258                         netlink = Netlink()
259                         if bdpoll is not None:
260                                 bdpoll.running = False
261                         bdpoll = BDPoll()
262                         for blockdev, removable, is_cdrom, medium_found in harddiskmanager.devices_scanned_on_init:
263                                 if removable or is_cdrom:
264                                         bdpoll.addDevice(blockdev, is_cdrom, medium_found)
265                 else:
266                         from twisted.internet.protocol import Protocol, Factory
267                         from twisted.internet import reactor
268
269                         try:
270                                 import os
271                                 os.remove("/tmp/hotplug.socket")
272                         except OSError:
273                                 pass
274
275                         class Hotplug(Protocol):
276                                 def connectionMade(self):
277                                         print "HOTPLUG connection!"
278                                         self.received = ""
279
280                                 def dataReceived(self, data):
281                                         print "hotplug:", data
282                                         self.received += data
283                                         print "complete", self.received
284
285                                 def connectionLost(self, reason):
286                                         print "HOTPLUG connection lost!"
287                                         data = self.received.split('\0')[:-1]
288                                         v = {}
289
290                                         for x in data:
291                                                 i = x.find('=')
292                                                 var, val = x[:i], x[i+1:]
293                                                 v[var] = val
294
295                                         processHotplugData(self, v)
296
297                         factory = Factory()
298                         factory.protocol = Hotplug
299                         reactor.listenUNIX("/tmp/hotplug.socket", factory)
300         else:
301                 if bdpoll:
302                         bdpoll.running = False
303                         bdpoll.timeout() # XXX: I assume the timer is shut down before it executes again, so release the semaphore manually
304                         bdpoll.join()
305                 bdpoll = None
306
307 def Plugins(**kwargs):
308         return PluginDescriptor(name = "Hotplug", description = "listens to hotplug events", where = PluginDescriptor.WHERE_AUTOSTART, needsRestart = True, fnc = autostart)