1 /*
2  * This file is part of libbluray
3  *
4  * This library is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU Lesser General Public
6  * License as published by the Free Software Foundation; either
7  * version 2.1 of the License, or (at your option) any later version.
8  *
9  * This library is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12  * Lesser General Public License for more details.
13  *
14  * You should have received a copy of the GNU Lesser General Public
15  * License along with this library. If not, see
16  * <http://www.gnu.org/licenses/>.
17  */
18 
19 package org.videolan;
20 
21 import org.dvb.application.AppID;
22 import org.dvb.application.AppStateChangeEvent;
23 import org.dvb.application.AppStateChangeEventListener;
24 import org.dvb.application.DVBJProxy;
25 
26 import java.awt.EventQueue;
27 
28 import java.io.File;
29 import java.util.LinkedList;
30 import javax.tv.xlet.Xlet;
31 
32 class BDJAppProxy implements DVBJProxy, Runnable {
newInstance(BDJXletContext context)33     protected static BDJAppProxy newInstance(BDJXletContext context) {
34         BDJAppProxy proxy = new BDJAppProxy(context);
35         /* do not create and start thread in constructor.
36            if constructor fails (exception), thread is left running without BDJAppProxy ... */
37         proxy.startThread();
38         return proxy;
39     }
40 
startThread()41     private void startThread() {
42         thread = new Thread(context.getThreadGroup(), this, "BDJAppProxy");
43         thread.setDaemon(true);
44         thread.start();
45 
46         /* wait until thread has been started and event queue is initialized.
47          * We want event dispatcher thread to be inside xlet thread group
48          * -> event queue must be created from thread running inside applet thread group.
49          */
50         while (context.getEventQueue() == null) {
51             Thread.yield();
52         }
53     }
54 
BDJAppProxy(BDJXletContext context)55     private BDJAppProxy(BDJXletContext context) {
56         this.context = context;
57         state = NOT_LOADED;
58     }
59 
getState()60     public int getState() {
61         return state;
62     }
63 
load()64     public void load() {
65         AppCommand cmd = new AppCommand(AppCommand.CMD_LOAD, null);
66         synchronized (cmds) {
67             cmds.addLast(cmd);
68             cmds.notifyAll();
69         }
70     }
71 
init()72     public void init() {
73         AppCommand cmd = new AppCommand(AppCommand.CMD_INIT, null);
74         synchronized (cmds) {
75             cmds.addLast(cmd);
76             cmds.notifyAll();
77         }
78     }
79 
start()80     public void start() {
81         start(null);
82     }
83 
start(String[] args)84     public void start(String[] args) {
85         AppCommand cmd = new AppCommand(AppCommand.CMD_START, args);
86         synchronized (cmds) {
87             cmds.addLast(cmd);
88             cmds.notifyAll();
89         }
90     }
91 
stop(boolean force, int timeout)92     public void stop(boolean force, int timeout) {
93         AppCommand cmd = new AppCommand(AppCommand.CMD_STOP, new Boolean(force));
94         synchronized (cmds) {
95             cmds.addLast(cmd);
96             cmds.notifyAll();
97         }
98         if (timeout > 0) {
99             if (!cmd.waitDone(timeout)) {
100                 logger.error("stop() timeout: Xlet " + context.getThreadGroup().getName());
101             }
102         }
103     }
104 
stop(boolean force)105     public void stop(boolean force) {
106         stop(force, -1);
107     }
108 
pause()109     public void pause() {
110         AppCommand cmd = new AppCommand(AppCommand.CMD_PAUSE, null);
111         synchronized (cmds) {
112             cmds.addLast(cmd);
113             cmds.notifyAll();
114         }
115     }
116 
resume()117     public void resume() {
118         AppCommand cmd = new AppCommand(AppCommand.CMD_RESUME, null);
119         synchronized (cmds) {
120             cmds.addLast(cmd);
121             cmds.notifyAll();
122         }
123     }
124 
notifyDestroyed()125     protected void notifyDestroyed() {
126         AppCommand cmd = new AppCommand(AppCommand.CMD_NOTIFY_DESTROYED, null);
127         synchronized (cmds) {
128             cmds.addLast(cmd);
129             cmds.notifyAll();
130         }
131     }
132 
notifyPaused()133     protected void notifyPaused() {
134         AppCommand cmd = new AppCommand(AppCommand.CMD_NOTIFY_PAUSED, null);
135         synchronized (cmds) {
136             cmds.addLast(cmd);
137             cmds.notifyAll();
138         }
139     }
140 
release()141     protected void release() {
142         AppCommand cmd = new AppCommand(AppCommand.CMD_STOP, new Boolean(true));
143         synchronized (cmds) {
144             cmds.addLast(cmd);
145             cmds.addLast(null);
146             cmds.notifyAll();
147         }
148 
149         if (!cmd.waitDone(5000)) {
150             logger.error("release(): STOP timeout, killing Xlet " + context.getThreadGroup().getName());
151         }
152 
153         final String persistentOrg = System.getProperty("dvb.persistent.root") + File.separator +
154             (String)context.getXletProperty("dvb.org.id") + File.separator;
155         final String persistentApp = persistentOrg + (String)context.getXletProperty("dvb.app.id");
156 
157         context.release();
158 
159         if (new File(persistentApp).delete()) {
160             new File(persistentOrg).delete();
161         }
162     }
163 
addAppStateChangeEventListener(AppStateChangeEventListener listener)164     public void addAppStateChangeEventListener(AppStateChangeEventListener listener) {
165         synchronized (listeners) {
166             listeners.add(listener);
167         }
168     }
169 
removeAppStateChangeEventListener(AppStateChangeEventListener listener)170     public void removeAppStateChangeEventListener(AppStateChangeEventListener listener) {
171         synchronized (listeners) {
172             listeners.remove(listener);
173         }
174     }
175 
notifyListeners(int fromState, int toState, boolean hasFailed)176     private void notifyListeners(int fromState, int toState, boolean hasFailed) {
177         LinkedList list;
178         synchronized (listeners) {
179             list = (LinkedList)listeners.clone();
180         }
181 
182         AppStateChangeEvent event = new AppStateChangeEvent(
183                 (AppID)context.getXletProperty("org.dvb.application.appid"),
184                 fromState, toState, this, hasFailed);
185         for (int i = 0; i < list.size(); i++)
186             ((AppStateChangeEventListener)list.get(i)).stateChange(event);
187     }
188 
getXletContext()189     protected BDJXletContext getXletContext() {
190         return context;
191     }
192 
doLoad()193     private boolean doLoad() {
194         if (state == NOT_LOADED) {
195             try {
196                 xlet = ((BDJClassLoader)context.getClassLoader()).loadXlet();
197                 state = LOADED;
198                 return true;
199             } catch (Throwable e) {
200                 logger.error("doLoad() failed: " + e + "\n" + Logger.dumpStack(e));
201                 state = INVALID;
202             }
203         }
204         return false;
205     }
206 
doInit()207     private boolean doInit() {
208         if ((state == NOT_LOADED) && !doLoad())
209             return false;
210         if (state == LOADED) {
211             try {
212                 String persistent = System.getProperty("dvb.persistent.root") + File.separator +
213                     (String)context.getXletProperty("dvb.org.id") + File.separator +
214                     (String)context.getXletProperty("dvb.app.id");
215                 File f = new File(persistent);
216                 if (!f.isDirectory() && !f.mkdirs()) {
217                     logger.error("Error creating persistent storage " + persistent);
218                 }
219 
220                 String buda = System.getProperty("bluray.bindingunit.root") + File.separator +
221                     (String)context.getXletProperty("dvb.org.id") + File.separator +
222                     org.bluray.ti.DiscManager.getDiscManager().getCurrentDisc().getId();
223                 File fb = new File(buda);
224                 if (!fb.isDirectory() && !fb.mkdirs()) {
225                     logger.error("Error creating BUDA storage " + buda);
226                 }
227 
228                 xlet.initXlet(context);
229                 state = PAUSED;
230                 return true;
231             } catch (Throwable e) {
232                 logger.error("doInit() failed: " + e + "\n" + Logger.dumpStack(e));
233                 state = INVALID;
234             }
235         }
236         return false;
237     }
238 
doStart(String[] args)239     private boolean doStart(String[] args) {
240         if (((state == NOT_LOADED) || (state == LOADED)) && !doInit())
241             return false;
242         if (state == PAUSED) {
243             try {
244                 if (args != null)
245                     context.setArgs(args);
246                 xlet.startXlet();
247                 state = STARTED;
248                 return true;
249             } catch (Throwable e) {
250                 logger.error("doStart() failed: " + e + "\n" + Logger.dumpStack(e));
251                 state = INVALID;
252             }
253         }
254         return false;
255     }
256 
doStop(boolean force)257     private boolean doStop(boolean force) {
258         if (state == INVALID)
259             return false;
260         if ((state != NOT_LOADED) && (state != LOADED)) {
261             try {
262                 xlet.destroyXlet(force);
263 
264                 context.closeSockets();
265                 context.getThreadGroup().waitForShutdown(1000, 1 + context.numEventQueueThreads());
266 
267                 context.exitXlet();
268 
269             } catch (Throwable e) {
270                 logger.error("doStop() failed: " + e + "\n" + Logger.dumpStack(e));
271                 state = INVALID;
272                 return false;
273             }
274         }
275         xlet = null;
276         state = DESTROYED;
277         return true;
278     }
279 
doPause()280     private boolean doPause() {
281         if (state == STARTED) {
282             try {
283                 xlet.pauseXlet();
284                 state = PAUSED;
285                 return true;
286             } catch (Throwable e) {
287                 logger.error("doPause() failed: " + e + "\n" + Logger.dumpStack(e));
288                 state = INVALID;
289             }
290         }
291         return false;
292     }
293 
doResume()294     private boolean doResume() {
295         if (state == PAUSED) {
296             try {
297                 xlet.startXlet();
298                 state = STARTED;
299                 return true;
300             } catch (Throwable e) {
301                 logger.error("doResume() failed: " + e + "\n" + Logger.dumpStack(e));
302                 state = INVALID;
303             }
304         }
305         return false;
306     }
307 
run()308     public void run() {
309         if (context.getEventQueue() == null)
310             context.setEventQueue(new EventQueue());
311 
312         for (;;) {
313             AppCommand cmd;
314             synchronized (cmds) {
315                 while (cmds.isEmpty()) {
316                     try {
317                         cmds.wait();
318                     } catch (InterruptedException e) {
319 
320                     }
321                 }
322                 cmd = (AppCommand)cmds.removeFirst();
323             }
324             if (cmd == null)
325                 return;
326             int fromState = state;
327             int toState;
328             boolean ret;
329             switch (cmd.getCommand()) {
330             case AppCommand.CMD_LOAD:
331                 toState = LOADED;
332                 ret = doLoad();
333                 break;
334             case AppCommand.CMD_INIT:
335                 toState = PAUSED;
336                 ret = doInit();
337                 break;
338             case AppCommand.CMD_START:
339                 toState = STARTED;
340                 Object args = cmd.getArgument();
341                 ret = doStart(args == null ? null : (String[])args);
342                 break;
343             case AppCommand.CMD_STOP:
344                 toState = DESTROYED;
345                 ret = doStop(((Boolean)cmd.getArgument()).booleanValue());
346                 break;
347             case AppCommand.CMD_PAUSE:
348                 toState = PAUSED;
349                 ret = doPause();
350                 break;
351             case AppCommand.CMD_RESUME:
352                 toState = STARTED;
353                 ret = doResume();
354                 break;
355             case AppCommand.CMD_NOTIFY_DESTROYED:
356                 toState = DESTROYED;
357                 state = DESTROYED;
358                 ret = true;
359                 break;
360             case AppCommand.CMD_NOTIFY_PAUSED:
361                 toState = PAUSED;
362                 state = PAUSED;
363                 ret = true;
364                 break;
365             default:
366                 return;
367             }
368             notifyListeners(fromState, toState, !ret);
369             cmd.release();
370             if (state == DESTROYED)
371                 state = NOT_LOADED;
372         }
373     }
374 
375     private BDJXletContext context;
376     private Xlet xlet;
377     private int state;
378     private LinkedList listeners = new LinkedList();
379     private LinkedList cmds = new LinkedList();
380     private Thread thread;
381     private static final Logger logger = Logger.getLogger(BDJAppProxy.class.getName());
382 
383     private static class AppCommand {
AppCommand(int cmd, Object arg)384         public AppCommand(int cmd, Object arg) {
385             this.cmd = cmd;
386             this.arg = arg;
387         }
388 
getCommand()389         public int getCommand() {
390             return cmd;
391         }
392 
getArgument()393         public Object getArgument() {
394             return arg;
395         }
396 
waitDone(int timeoutMs)397         public boolean waitDone(int timeoutMs) {
398             synchronized (this) {
399                 while (!done) {
400                     try {
401                         if (timeoutMs < 1) {
402                             this.wait();
403                         } else {
404                             this.wait(timeoutMs);
405                             break;
406                         }
407                     } catch (InterruptedException e) {
408                     }
409                 }
410                 return done;
411             }
412         }
413 
release()414         public void release() {
415             synchronized (this) {
416                 done = true;
417                 this.notifyAll();
418             }
419         }
420 
421         public static final int CMD_LOAD = 0;
422         public static final int CMD_INIT = 1;
423         public static final int CMD_START = 2;
424         public static final int CMD_STOP = 3;
425         public static final int CMD_PAUSE = 4;
426         public static final int CMD_RESUME = 5;
427         public static final int CMD_NOTIFY_DESTROYED = 6;
428         public static final int CMD_NOTIFY_PAUSED = 7;
429 
430         private int cmd;
431         private Object arg;
432         private boolean done = false;
433     }
434 }
435