1 /*
2  * Created on 28.11.2003
3  * Copyright (C) Azureus Software, Inc, All Rights Reserved.
4  *
5  * This program is free software; you can redistribute it and/or
6  * modify it under the terms of the GNU General Public License
7  * as published by the Free Software Foundation; either version 2
8  * of the License, or (at your option) any later version.
9  * This program 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
12  * GNU General Public License for more details.
13  * You should have received a copy of the GNU General Public License
14  * along with this program; if not, write to the Free Software
15  * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
16  *
17  */
18 package org.gudy.azureus2.ui.swt;
19 
20 import java.applet.Applet;
21 import java.applet.AudioClip;
22 import java.io.File;
23 import java.net.URL;
24 import java.util.Map;
25 
26 import org.gudy.azureus2.core3.config.COConfigurationManager;
27 import org.gudy.azureus2.core3.disk.*;
28 import org.gudy.azureus2.core3.download.DownloadManager;
29 import org.gudy.azureus2.core3.download.DownloadManagerDiskListener;
30 import org.gudy.azureus2.core3.download.DownloadManagerState;
31 import org.gudy.azureus2.core3.download.impl.DownloadManagerAdapter;
32 import org.gudy.azureus2.core3.global.GlobalManager;
33 import org.gudy.azureus2.core3.global.GlobalManagerAdapter;
34 import org.gudy.azureus2.core3.internat.MessageText;
35 import org.gudy.azureus2.core3.logging.LogAlert;
36 import org.gudy.azureus2.core3.logging.Logger;
37 import org.gudy.azureus2.core3.util.*;
38 import org.gudy.azureus2.platform.PlatformManager;
39 import org.gudy.azureus2.platform.PlatformManagerCapabilities;
40 import org.gudy.azureus2.platform.PlatformManagerFactory;
41 import org.gudy.azureus2.plugins.platform.PlatformManagerException;
42 import org.gudy.azureus2.ui.swt.minibar.DownloadBar;
43 
44 import com.aelitis.azureus.ui.UIFunctions;
45 import com.aelitis.azureus.ui.UIFunctionsManager;
46 import com.aelitis.azureus.ui.mdi.MultipleDocumentInterface;
47 
48 /**
49  * Contains methods to alert the user of certain events.
50  * @author Rene Leonhardt
51  */
52 
53 public class
54 UserAlerts
55 {
56   	private AudioClip 	audio_clip 		= null;
57   	private String		audio_resource	= "";
58 
59     private AEMonitor	this_mon 	= new AEMonitor( "UserAlerts" );
60 
61     private boolean startup = true;
62 
63 	public
UserAlerts( GlobalManager global_manager )64 	UserAlerts(
65 		GlobalManager	global_manager )
66  	{
67 		final DownloadManagerAdapter download_manager_listener =
68 			new DownloadManagerAdapter()
69 			{
70 			public void downloadComplete(DownloadManager manager) {
71 				activityFinished( manager, null );
72 			}
73 
74 			// @see org.gudy.azureus2.core3.download.impl.DownloadManagerAdapter#stateChanged(org.gudy.azureus2.core3.download.DownloadManager, int)
75 			public void stateChanged(final DownloadManager manager, int state) {
76 
77 				boolean lowNoise = manager.getDownloadState().getFlag(
78 						DownloadManagerState.FLAG_LOW_NOISE);
79 				if (lowNoise) {
80 					return;
81 				}
82 
83 				// if state == STARTED, then open the details window (according to config)
84 				if (state == DownloadManager.STATE_DOWNLOADING
85 						|| state == DownloadManager.STATE_SEEDING) {
86 					Utils.execSWTThread(new AERunnable() {
87 						public void runSupport() {
88 							boolean complete = manager.isDownloadComplete(false);
89 
90 							if ((!complete && COConfigurationManager.getBooleanParameter("Open Details"))
91 									|| (complete && COConfigurationManager.getBooleanParameter("Open Seeding Details"))) {
92 								UIFunctionsManager.getUIFunctions().getMDI().loadEntryByID(
93 										MultipleDocumentInterface.SIDEBAR_SECTION_TORRENT_DETAILS,
94 										false, false, manager);
95 							}
96 
97 							if (((!complete) && COConfigurationManager.getBooleanParameter("Open Bar Incomplete"))
98 									|| (complete && COConfigurationManager.getBooleanParameter("Open Bar Complete"))) {
99 
100 								DownloadBar.open(manager, Utils.findAnyShell());
101 							}
102 						}
103 					});
104 				}
105 
106 				boolean error_reported = manager.getDownloadState().getFlag( DownloadManagerState.FLAG_ERROR_REPORTED );
107 
108 				if ( state == DownloadManager.STATE_ERROR ){
109 
110 					if ( !error_reported ){
111 
112 						manager.getDownloadState().setFlag( DownloadManagerState.FLAG_ERROR_REPORTED, true );
113 
114 						reportError( manager );
115 					}
116 				}else if ( state == DownloadManager.STATE_DOWNLOADING || state == DownloadManager.STATE_SEEDING ){
117 
118 					if ( error_reported ){
119 
120 						manager.getDownloadState().setFlag( DownloadManagerState.FLAG_ERROR_REPORTED, false );
121 					}
122 				}
123 			}
124 		};
125 
126 		final DiskManagerListener	disk_listener =
127 			new DiskManagerListener()
128 			{
129 				public void
130 				stateChanged(
131 					int oldState,
132 					int	newState )
133 				{
134 				}
135 
136 				public void
137 				filePriorityChanged(
138 					DiskManagerFileInfo		file )
139 				{
140 				}
141 
142 				public void
143 				pieceDoneChanged(
144 					DiskManagerPiece		piece )
145 				{
146 				}
147 
148 				public void
149 				fileAccessModeChanged(
150 					DiskManagerFileInfo		file,
151 					int						old_mode,
152 					int						new_mode )
153 				{
154 					DownloadManager dm = file.getDownloadManager();
155 
156 					if ( dm != null ){
157 
158 						if ( 	old_mode == DiskManagerFileInfo.WRITE &&
159 								new_mode == DiskManagerFileInfo.READ &&
160 								file.getDownloaded() == file.getLength()){
161 
162 							activityFinished( dm, file );
163 						}
164 					}
165 
166 					/*
167 					System.out.println(
168 						"amc:" +
169 						file.getDownloadManager().getDisplayName() + "/" +
170 						file.getName() + ":" + old_mode + " -> " + new_mode );
171 					*/
172 				}
173 			};
174 
175 		final DownloadManagerDiskListener dm_disk_listener =
176 			new DownloadManagerDiskListener()
177 			{
178 				public void
179 				diskManagerAdded(
180 					DiskManager	dm )
181 				{
182 					dm.addListener( disk_listener );
183 				}
184 
185 				public void
186 				diskManagerRemoved(
187 					DiskManager	dm )
188 				{
189 					dm.removeListener( disk_listener );
190 				}
191 
192 			};
193 
194     	global_manager.addListener(
195     		new GlobalManagerAdapter()
196     		{
197 				public void
198 				downloadManagerAdded(DownloadManager manager)
199 				{
200 				// don't pop up for non-persistent as these get added late in the day every time
201 				// so we'll notify for each download every startup
202 
203 				if (!startup && manager.isPersistent()) {
204 
205 					boolean bPopup = COConfigurationManager.getBooleanParameter("Popup Download Added");
206 
207 					if (bPopup) {
208 
209 							if( !manager.getDownloadState().getFlag( DownloadManagerState.FLAG_LOW_NOISE )){
210 
211 							String popup_text = MessageText.getString("popup.download.added",
212 										new String[] { manager.getDisplayName()
213 									});
214 							UIFunctionsManager.getUIFunctions().forceNotify(
215 									UIFunctions.STATUSICON_NONE, null, popup_text, null,
216 									new Object[] {
217 										manager
218 									}, -1);
219 						}
220 					}
221 				}
222 
223 				manager.addListener(download_manager_listener);
224 
225 				manager.addDiskListener(dm_disk_listener);
226 			}
227 
228 				public void
229 				downloadManagerRemoved(DownloadManager manager)
230 				{
231 					manager.removeListener(download_manager_listener);
232 
233 					manager.removeDiskListener( dm_disk_listener );
234 				}
235 
236 				public void
237 				destroyed()
238 				{
239 					tidyUp();
240 				}
241 			});
242     	startup = false;
243      }
244 
245   	private void
activityFinished( DownloadManager manager, DiskManagerFileInfo dm_file )246   	activityFinished(
247   		DownloadManager			manager,
248   		DiskManagerFileInfo		dm_file )
249   	{
250   		DownloadManagerState dm_state = manager.getDownloadState();
251 
252 		if ( dm_state.getFlag( DownloadManagerState.FLAG_LOW_NOISE)) {
253 
254 			return;
255 		}
256 
257 		boolean	download = dm_file == null;
258 
259 		Object 	relatedObject;
260 		String	item_name;
261 
262 		if ( download ){
263 
264 			relatedObject 	= manager;
265 			item_name		= manager.getDisplayName();
266 
267 		}else{
268 
269 			relatedObject	= dm_file.getDiskManager();
270 			item_name		= dm_file.getFile( true ).getName();
271 		}
272 
273   		final String sound_enabler;
274   		final String sound_file;
275   		final String default_sound 	= "org/gudy/azureus2/ui/icons/downloadFinished.wav";
276 
277   		final String speech_enabler;
278   		final String speech_text;
279 
280   		final String popup_enabler;
281   		final String popup_def_text;
282 
283   		if ( download ){
284 	 		sound_enabler 	= "Play Download Finished";
285 	  		sound_file		= "Play Download Finished File";
286 
287 	  		speech_enabler 	= "Play Download Finished Announcement";
288 	  		speech_text		= "Play Download Finished Announcement Text";
289 
290 	  		popup_enabler   = "Popup Download Finished";
291 	  		popup_def_text  = "popup.download.finished";
292 
293   		}else{
294 	 		sound_enabler 	= "Play File Finished";
295 	  		sound_file		= "Play File Finished File";
296 
297 	  		speech_enabler 	= "Play File Finished Announcement";
298 	  		speech_text		= "Play File Finished Announcement Text";
299 
300 	  		popup_enabler   = "Popup File Finished";
301 	  		popup_def_text  = "popup.file.finished";
302   		}
303 
304   		Map 	dl_file_alerts = dm_state.getMapAttribute( DownloadManagerState.AT_DL_FILE_ALERTS );
305   		String 	dlf_prefix = download?"":(String.valueOf(dm_file.getIndex()) + "." );
306 
307   		try{
308   			this_mon.enter();
309 
310   			if ( COConfigurationManager.getBooleanParameter(popup_enabler) || isDLFEnabled( dl_file_alerts, dlf_prefix, popup_enabler )) {
311   				String popup_text = MessageText.getString(popup_def_text, new String[]{item_name});
312 					UIFunctionsManager.getUIFunctions().forceNotify(
313 							UIFunctions.STATUSICON_NONE, null, popup_text, null,
314 							new Object[] {
315 								relatedObject
316 							}, -1);
317   			}
318 
319 			if (Constants.isOSX
320 					&&  ( COConfigurationManager.getBooleanParameter(speech_enabler) || isDLFEnabled( dl_file_alerts, dlf_prefix, speech_enabler ))) {
321 				new AEThread2("SaySound") {
322 					public void run() {
323 						try {
324 							Runtime.getRuntime().exec(new String[] {
325 								"say",
326 								COConfigurationManager.getStringParameter(speech_text)
327 							}); // Speech Synthesis services
328 
329 							Thread.sleep(2500);
330 						} catch (Throwable e) {
331 						}
332 					}
333 				}.start();
334 			}
335 
336         if ( COConfigurationManager.getBooleanParameter( sound_enabler, false) || isDLFEnabled( dl_file_alerts, dlf_prefix, sound_enabler )){
337 
338 	    		String	file = COConfigurationManager.getStringParameter( sound_file );
339 
340     			file = file.trim();
341 
342 	    			// turn "<default>" into blank
343 
344 	    		if ( file.startsWith( "<" )){
345 
346 	    			file	= "";
347 	    		}
348 
349 	    		if ( audio_clip == null || !file.equals( audio_resource )){
350 
351 	    			audio_clip	= null;
352 
353 	    				// try explicit file
354 
355 	    			if ( file.length() != 0 ){
356 
357 	    				File	f = new File( file );
358 
359 	    				try{
360 
361 			    			if ( f.exists()){
362 
363 		    					URL	file_url = f.toURI().toURL();
364 
365 		    					audio_clip = Applet.newAudioClip( file_url );
366 			    			}
367 
368 	    				}catch( Throwable  e ){
369 
370 	    					Debug.printStackTrace(e);
371 
372 	    				}finally{
373 
374 	    					if ( audio_clip == null ){
375 	    						Logger.log(new LogAlert(relatedObject, LogAlert.UNREPEATABLE,
376 										LogAlert.AT_ERROR, "Failed to load audio file '" + file
377 												+ "'"));
378 	    					}
379 	    				}
380 	    			}
381 
382 	    				// either non-explicit or explicit missing
383 
384 	    			if ( audio_clip == null ){
385 
386 	    				audio_clip = Applet.newAudioClip(UserAlerts.class.getClassLoader().getResource( default_sound ));
387 
388 	    			}
389 
390 	    			audio_resource	= file;
391 	    		}
392 
393 	    		if ( audio_clip != null ){
394 
395 	            	new AEThread2("DownloadSound")
396 					{
397 		        		public void
398 		        		run()
399 		        		{
400 		        			try{
401 		        				audio_clip.play();
402 
403 		        				Thread.sleep(2500);
404 
405 		        			}catch( Throwable e ){
406 
407 		        			}
408 		        		}
409 		        	}.start();
410 		        }
411 	    	}
412   		}catch( Throwable e ){
413 
414   			Debug.printStackTrace( e );
415 
416   		}finally{
417 
418   			this_mon.exit();
419   		}
420   	}
421 
422   	private long	last_error_speech;
423   	private long	last_error_sound;
424 
425   	private void
reportError( DownloadManager manager )426   	reportError(
427   		DownloadManager			manager )
428   	{
429 		final Object relatedObject 	= manager;
430 		final String item_name		= manager.getDisplayName();
431 
432   		final String default_sound 	= "org/gudy/azureus2/ui/icons/downloadFinished.wav";
433 
434 
435   		final String sound_enabler 	= "Play Download Error";
436   		final String sound_file		= "Play Download Error File";
437 
438   		final String speech_enabler 	= "Play Download Error Announcement";
439   		final String speech_text		= "Play Download Error Announcement Text";
440 
441   		final String popup_enabler   = "Popup Download Error";
442   		final String popup_def_text  = "popup.download.error";
443 
444 
445   		try{
446   			this_mon.enter();
447 
448   			if ( COConfigurationManager.getBooleanParameter(popup_enabler)) {
449    					UIFunctionsManager.execWithUIFunctions(
450   							new UIFunctionsManager.UIFCallback() {
451 
452 								public void run(UIFunctions functions) {
453 
454 					 				String popup_text = MessageText.getString(popup_def_text, new String[]{item_name});
455 
456 									functions.forceNotify(
457 											UIFunctions.STATUSICON_ERROR, null, popup_text, null,
458 											new Object[] {
459 												relatedObject
460 											}, -1);
461 								}
462 							});
463   			}
464 
465   			long now = SystemTime.getMonotonousTime();
466 
467 			if (	Constants.isOSX
468 					&&  COConfigurationManager.getBooleanParameter(speech_enabler)){
469 
470 				if ( last_error_speech == 0 || now - last_error_speech > 5000 ){
471 
472 					last_error_speech = now;
473 
474 					new AEThread2("SaySound") {
475 						public void run() {
476 							try {
477 								Runtime.getRuntime().exec(new String[] {
478 									"say",
479 									COConfigurationManager.getStringParameter(speech_text)
480 								}); // Speech Synthesis services
481 
482 								Thread.sleep(2500);
483 							} catch (Throwable e) {
484 							}
485 						}
486 					}.start();
487 				}
488 			}
489 
490 	        if ( COConfigurationManager.getBooleanParameter( sound_enabler, false)){
491 
492 				if ( last_error_sound == 0 || now - last_error_sound > 5000 ){
493 
494 					last_error_sound = now;
495 
496 		    		String	file = COConfigurationManager.getStringParameter( sound_file );
497 
498 	    			file = file.trim();
499 
500 		    			// turn "<default>" into blank
501 
502 		    		if ( file.startsWith( "<" )){
503 
504 		    			file	= "";
505 		    		}
506 
507 		    		if ( audio_clip == null || !file.equals( audio_resource )){
508 
509 		    			audio_clip	= null;
510 
511 		    				// try explicit file
512 
513 		    			if ( file.length() != 0 ){
514 
515 		    				File	f = new File( file );
516 
517 		    				try{
518 
519 				    			if ( f.exists()){
520 
521 			    					URL	file_url = f.toURI().toURL();
522 
523 			    					audio_clip = Applet.newAudioClip( file_url );
524 				    			}
525 
526 		    				}catch( Throwable  e ){
527 
528 		    					Debug.printStackTrace(e);
529 
530 		    				}finally{
531 
532 		    					if ( audio_clip == null ){
533 		    						Logger.log(new LogAlert(relatedObject, LogAlert.UNREPEATABLE,
534 											LogAlert.AT_ERROR, "Failed to load audio file '" + file
535 													+ "'"));
536 		    					}
537 		    				}
538 		    			}
539 
540 		    				// either non-explicit or explicit missing
541 
542 		    			if ( audio_clip == null ){
543 
544 		    				audio_clip = Applet.newAudioClip(UserAlerts.class.getClassLoader().getResource( default_sound ));
545 
546 		    			}
547 
548 		    			audio_resource	= file;
549 		    		}
550 
551 		    		if ( audio_clip != null ){
552 
553 		            	new AEThread2("DownloadSound")
554 						{
555 			        		public void
556 							run()
557 			        		{
558 			        			try{
559 			        				audio_clip.play();
560 
561 			        				Thread.sleep(2500);
562 
563 			        			}catch( Throwable e ){
564 
565 			        			}
566 			        		}
567 			        	}.start();
568 			        }
569 				}
570 	    	}
571   		}catch( Throwable e ){
572 
573   			Debug.printStackTrace( e );
574 
575   		}finally{
576 
577   			this_mon.exit();
578   		}
579   	}
580 
581   	private boolean
isDLFEnabled( Map map, String prefix, String key )582   	isDLFEnabled(
583   		Map		map,
584   		String	prefix,
585   		String	key )
586   	{
587   		if ( map == null ){
588 
589   			return( false );
590   		}
591 
592   		key = prefix + key;
593 
594   		return( map.containsKey( key ));
595   	}
596 
597   	protected void
tidyUp()598   	tidyUp()
599   	{
600 		/*
601 		The Java audio system keeps some threads running even after playback is finished.
602 		One of them, named "Java Sound event dispatcher", is *not* a daemon
603 		thread and keeps the VM alive.
604 		We have to locate and interrupt it explicitely.
605 		*/
606 
607 		try{
608 
609 			ThreadGroup threadGroup = Thread.currentThread().getThreadGroup();
610 
611 			Thread[] threadList = new Thread[threadGroup.activeCount()];
612 
613 			threadGroup.enumerate(threadList);
614 
615 			for (int i = 0;	i < threadList.length;	i++){
616 
617 				if(threadList[i] != null && "Java Sound event dispatcher".equals(threadList[i].getName())){
618 
619 					threadList[i].interrupt();
620 				}
621 			}
622 		}catch( Throwable e ){
623 
624 			Debug.printStackTrace( e );
625 		}
626   	}
627 
628 
629   	/**
630   	 * Grab the user's attention in a platform dependent way
631   	 * @param type one of <code>PlatformManager.USER_REQUEST_INFO</code>,
632   	 * 										<code>PlatformManager.USER_REQUEST_WARNING</code>, OR
633   	 * 										<code>PlatformManager.USER_REQUEST_QUESTION</code>
634   	 * @param data user-defined data object;
635   	 * 				see the platform-specific <code>PlatformManager</code> for what may be supported
636   	 */
requestUserAttention(int type, Object data)637   	public static void requestUserAttention(int type, Object data) {
638 
639   		PlatformManager pm = PlatformManagerFactory.getPlatformManager();
640   		if (true == pm.hasCapability(PlatformManagerCapabilities.RequestUserAttention)) {
641   			try {
642   				pm.requestUserAttention(type, data);
643   			} catch (PlatformManagerException e) {
644   				Debug.printStackTrace(e);
645   			}
646   		}
647   	}
648 }