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