1 package com.nexuiz.demorecorder.application.jobs;
2 
3 import java.io.BufferedReader;
4 import java.io.File;
5 import java.io.IOException;
6 import java.io.InputStream;
7 import java.io.InputStreamReader;
8 import java.io.Serializable;
9 import java.util.ArrayList;
10 import java.util.HashMap;
11 import java.util.List;
12 import java.util.Map;
13 import java.util.Properties;
14 
15 import com.nexuiz.demorecorder.application.DemoRecorderApplication;
16 import com.nexuiz.demorecorder.application.DemoRecorderException;
17 import com.nexuiz.demorecorder.application.DemoRecorderUtils;
18 import com.nexuiz.demorecorder.application.NDRPreferences;
19 import com.nexuiz.demorecorder.application.DemoRecorderApplication.Preferences;
20 import com.nexuiz.demorecorder.application.democutter.DemoCutter;
21 import com.nexuiz.demorecorder.application.democutter.DemoCutterException;
22 import com.nexuiz.demorecorder.application.plugins.EncoderPlugin;
23 import com.nexuiz.demorecorder.application.plugins.EncoderPluginException;
24 
25 public class RecordJob implements Runnable, Serializable {
26 
27 	private static final long serialVersionUID = -4585637490345587912L;
28 
29 	public enum State {
30 		WAITING, PROCESSING, ERROR, ERROR_PLUGIN, DONE
31 	}
32 
33 	public static final String CUT_DEMO_FILE_SUFFIX = "_autocut";
34 	public static final String CUT_DEMO_CAPVIDEO_NAMEFORMAT_OVERRIDE = "autocap";
35 	public static final String CUT_DEMO_CAPVIDEO_NUMBER_OVERRIDE = "1234567";
36 	protected static final String[] VIDEO_FILE_ENDINGS = {"avi", "ogv"};
37 
38 	private DemoRecorderApplication appLayer;
39 	protected String jobName;
40 	private int jobIndex;
41 	protected File enginePath;
42 	protected String engineParameters;
43 	protected File demoFile;
44 	protected String relativeDemoPath;
45 	protected File dpVideoPath;
46 	protected File videoDestination;
47 	protected String executeBeforeCap;
48 	protected String executeAfterCap;
49 	protected float startSecond;
50 	protected float endSecond;
51 	protected State state = State.WAITING;
52 	protected DemoRecorderException lastException = null;
53 
54 	/**
55 	 * Points to the actual final file, including possible suffixes, e.g. _copy1, and the actualy ending
56 	 */
57 	protected File actualVideoDestination = null;
58 	/**
59 	 * Map that identifies the plug-in by its name (String) and maps to the plug-in's job-specific settings
60 	 */
61 	protected Map<String, Properties> encoderPluginSettings = new HashMap<String, Properties>();
62 
63 	private List<File> cleanUpFiles = null;
64 
RecordJob( DemoRecorderApplication appLayer, String jobName, int jobIndex, File enginePath, String engineParameters, File demoFile, String relativeDemoPath, File dpVideoPath, File videoDestination, String executeBeforeCap, String executeAfterCap, float startSecond, float endSecond )65 	public RecordJob(
66 		DemoRecorderApplication appLayer,
67 		String jobName,
68 		int jobIndex,
69 		File enginePath,
70 		String engineParameters,
71 		File demoFile,
72 		String relativeDemoPath,
73 		File dpVideoPath,
74 		File videoDestination,
75 		String executeBeforeCap,
76 		String executeAfterCap,
77 		float startSecond,
78 		float endSecond
79 	) {
80 		this.appLayer = appLayer;
81 		this.jobName = jobName;
82 		this.jobIndex = jobIndex;
83 
84 		this.setEnginePath(enginePath);
85 		this.setEngineParameters(engineParameters);
86 		this.setDemoFile(demoFile);
87 		this.setRelativeDemoPath(relativeDemoPath);
88 		this.setDpVideoPath(dpVideoPath);
89 		this.setVideoDestination(videoDestination);
90 		this.setExecuteBeforeCap(executeBeforeCap);
91 		this.setExecuteAfterCap(executeAfterCap);
92 		this.setStartSecond(startSecond);
93 		this.setEndSecond(endSecond);
94 	}
95 
RecordJob()96 	public RecordJob(){}
97 
98 	/**
99 	 * Constructor that can be used by other classes such as job templates. Won't throw exceptions
100 	 * as it won't check the input for validity.
101 	 */
RecordJob( File enginePath, String engineParameters, File demoFile, String relativeDemoPath, File dpVideoPath, File videoDestination, String executeBeforeCap, String executeAfterCap, float startSecond, float endSecond )102 	protected RecordJob(
103 		File enginePath,
104 		String engineParameters,
105 		File demoFile,
106 		String relativeDemoPath,
107 		File dpVideoPath,
108 		File videoDestination,
109 		String executeBeforeCap,
110 		String executeAfterCap,
111 		float startSecond,
112 		float endSecond
113 		) {
114 		this.jobIndex = -1;
115 		this.enginePath = enginePath;
116 		this.engineParameters = engineParameters;
117 		this.demoFile = demoFile;
118 		this.relativeDemoPath = relativeDemoPath;
119 		this.dpVideoPath = dpVideoPath;
120 		this.videoDestination = videoDestination;
121 		this.executeBeforeCap = executeBeforeCap;
122 		this.executeAfterCap = executeAfterCap;
123 		this.startSecond = startSecond;
124 		this.endSecond = endSecond;
125 	}
126 
execute()127 	public void execute() {
128 		if (this.state == State.PROCESSING) {
129 			return;
130 		}
131 		boolean errorOccurred = false;
132 		this.setState(State.PROCESSING);
133 		this.appLayer.fireUserInterfaceUpdate(this);
134 		cleanUpFiles = new ArrayList<File>();
135 
136 		File cutDemo = computeCutDemoFile();
137 		cutDemo.delete(); //delete possibly old cutDemoFile
138 
139 		EncoderPlugin recentEncoder = null;
140 
141 		try {
142 			this.cutDemo(cutDemo);
143 			this.removeOldAutocaps();
144 			this.recordClip(cutDemo);
145 			this.moveRecordedClip();
146 			for (EncoderPlugin plugin : this.appLayer.getEncoderPlugins()) {
147 				recentEncoder = plugin;
148 				plugin.executeEncoder(this);
149 			}
150 		} catch (DemoRecorderException e) {
151 			errorOccurred = true;
152 			this.lastException = e;
153 			this.setState(State.ERROR);
154 		} catch (EncoderPluginException e) {
155 			errorOccurred = true;
156 			this.lastException = new DemoRecorderException("Encoder plug-in " + recentEncoder.getName() + " failed: "
157 					+ e.getMessage(), e);
158 			this.setState(State.ERROR_PLUGIN);
159 		} catch (Exception e) {
160 			errorOccurred = true;
161 			this.lastException = new DemoRecorderException("Executing job failed, click on details for more info", e);
162 		} finally {
163 			NDRPreferences preferences = this.appLayer.getPreferences();
164 			if (!Boolean.valueOf(preferences.getProperty(NDRPreferences.MAIN_APPLICATION, Preferences.DO_NOT_DELETE_CUT_DEMOS))) {
165 				cleanUpFiles.add(cutDemo);
166 			}
167 			if (!errorOccurred) {
168 				this.setState(State.DONE);
169 			}
170 			this.cleanUpFiles();
171 			this.appLayer.fireUserInterfaceUpdate(this);
172 			this.appLayer.saveJobQueue();
173 		}
174 	}
175 
176 	/**
177 	 * Will execute just the specified encoder plug-in on an already "done" job.
178 	 * @param pluginName
179 	 */
executePlugin(EncoderPlugin plugin)180 	public void executePlugin(EncoderPlugin plugin) {
181 		if (this.getState() != State.DONE) {
182 			return;
183 		}
184 		this.setState(State.PROCESSING);
185 		this.appLayer.fireUserInterfaceUpdate(this);
186 
187 		try {
188 			plugin.executeEncoder(this);
189 			this.setState(State.DONE);
190 		} catch (EncoderPluginException e) {
191 			this.lastException = new DemoRecorderException("Encoder plug-in " + plugin.getName() + " failed: "
192 					+ e.getMessage(), e);
193 			this.setState(State.ERROR_PLUGIN);
194 		}
195 
196 		this.appLayer.fireUserInterfaceUpdate(this);
197 	}
198 
cleanUpFiles()199 	private void cleanUpFiles() {
200 		try {
201 			for (File f : this.cleanUpFiles) {
202 				f.delete();
203 			}
204 		} catch (Exception e) {}
205 
206 	}
207 
moveRecordedClip()208 	private void moveRecordedClip() {
209 		//1. Figure out whether the file is .avi or .ogv
210 		File sourceFile = null;
211 		for (String videoExtension : VIDEO_FILE_ENDINGS) {
212 			String fileString = this.dpVideoPath.getAbsolutePath() + File.separator + CUT_DEMO_CAPVIDEO_NAMEFORMAT_OVERRIDE
213 			+ CUT_DEMO_CAPVIDEO_NUMBER_OVERRIDE + "." + videoExtension;
214 			File videoFile = new File(fileString);
215 			if (videoFile.exists()) {
216 				sourceFile = videoFile;
217 				break;
218 			}
219 		}
220 
221 		if (sourceFile == null) {
222 			String p = this.dpVideoPath.getAbsolutePath() + File.separator + CUT_DEMO_CAPVIDEO_NAMEFORMAT_OVERRIDE
223 			+ CUT_DEMO_CAPVIDEO_NUMBER_OVERRIDE;
224 			throw new DemoRecorderException("Could not locate the expected video file being generated by Nexuiz (should have been at "
225 					+ p + ".avi/.ogv");
226 		}
227 		cleanUpFiles.add(sourceFile);
228 
229 		File destinationFile = null;
230 		NDRPreferences preferences = this.appLayer.getPreferences();
231 		String sourceFileExtension = DemoRecorderUtils.getFileExtension(sourceFile);
232 		String destinationFilePath = this.videoDestination + "." + sourceFileExtension;
233 		destinationFile = new File(destinationFilePath);
234 		if (destinationFile.exists()) {
235 			if (Boolean.valueOf(preferences.getProperty(NDRPreferences.MAIN_APPLICATION, Preferences.OVERWRITE_VIDEO_FILE))) {
236 				if (!destinationFile.delete()) {
237 					throw new DemoRecorderException("Could not delete the existing video destinatin file " + destinationFile.getAbsolutePath()
238 							+ " (application setting to overwrite existing video files is enabled!)");
239 				}
240 			} else {
241 				destinationFilePath = this.videoDestination + "_copy" + this.getVideoDestinationCopyNr(sourceFileExtension) + "." + sourceFileExtension;
242 				destinationFile = new File(destinationFilePath);
243 			}
244 		}
245 
246 		//finally move the file
247 		if (!sourceFile.renameTo(destinationFile)) {
248 			cleanUpFiles.add(destinationFile);
249 			throw new DemoRecorderException("Could not move the video file from " + sourceFile.getAbsolutePath()
250 					+ " to " + destinationFile.getAbsolutePath());
251 		}
252 
253 		this.actualVideoDestination = destinationFile;
254 	}
255 
256 	/**
257 	 * As destination video files, e.g. "test"[.avi] can already exist, we have to save the
258 	 * the video file to a file name such as test_copy1 or test_copy2.
259 	 * This function will figure out what the number (1, 2....) is.
260 	 * @return
261 	 */
getVideoDestinationCopyNr(String sourceFileExtension)262 	private int getVideoDestinationCopyNr(String sourceFileExtension) {
263 		int i = 1;
264 		File lastFile;
265 		while (true) {
266 			lastFile = new File(this.videoDestination + "_copy" + i + "." + sourceFileExtension);
267 			if (!lastFile.exists()) {
268 				break;
269 			}
270 
271 			i++;
272 		}
273 		return i;
274 	}
275 
computeCutDemoFile()276 	private File computeCutDemoFile() {
277 		String origFileString = this.demoFile.getAbsolutePath();
278 		int lastIndex = origFileString.lastIndexOf(File.separator);
279 		String autoDemoFileName = origFileString.substring(lastIndex+1, origFileString.length());
280 		//strip .dem ending
281 		autoDemoFileName = autoDemoFileName.substring(0, autoDemoFileName.length()-4);
282 		autoDemoFileName = autoDemoFileName + CUT_DEMO_FILE_SUFFIX + ".dem";
283 		String finalString = origFileString.substring(0, lastIndex) + File.separator + autoDemoFileName;
284 		File f = new File(finalString);
285 
286 		return f;
287 	}
288 
cutDemo(File cutDemo)289 	private void cutDemo(File cutDemo) {
290 		String injectAtStart = "";
291 		String injectBeforeCap = "";
292 		String injectAfterCap = "";
293 
294 		NDRPreferences preferences = this.appLayer.getPreferences();
295 		if (Boolean.valueOf(preferences.getProperty(NDRPreferences.MAIN_APPLICATION, Preferences.DISABLE_RENDERING))) {
296 			injectAtStart += "r_render 0;";
297 			injectBeforeCap += "r_render 1;";
298 		}
299 		if (Boolean.valueOf(preferences.getProperty(NDRPreferences.MAIN_APPLICATION, Preferences.DISABLE_SOUND))) {
300 			injectAtStart += "set _volume $volume;volume 0;";
301 			injectBeforeCap += "set volume $_volume;";
302 		}
303 		injectBeforeCap += this.executeBeforeCap + "\n";
304 		injectBeforeCap += "set _cl_capturevideo_nameformat $cl_capturevideo_nameformat;set _cl_capturevideo_number $cl_capturevideo_number;";
305 		injectBeforeCap += "cl_capturevideo_nameformat " + CUT_DEMO_CAPVIDEO_NAMEFORMAT_OVERRIDE + ";";
306 		injectBeforeCap += "cl_capturevideo_number " + CUT_DEMO_CAPVIDEO_NUMBER_OVERRIDE + ";";
307 
308 		injectAfterCap += this.executeAfterCap + "\n";
309 		injectAfterCap += "cl_capturevideo_nameformat $_cl_capturevideo_nameformat;cl_capturevideo_number $_cl_capturevideo_number;";
310 
311 
312 		DemoCutter cutter = new DemoCutter();
313 		int fwdSpeedFirstStage, fwdSpeedSecondStage;
314 		try {
315 			fwdSpeedFirstStage = Integer.parseInt(preferences.getProperty(NDRPreferences.MAIN_APPLICATION, Preferences.FFW_SPEED_FIRST_STAGE));
316 			fwdSpeedSecondStage = Integer.parseInt(preferences.getProperty(NDRPreferences.MAIN_APPLICATION, Preferences.FFW_SPEED_SECOND_STAGE));
317 		} catch (NumberFormatException e) {
318 			throw new DemoRecorderException("Make sure that you specified valid numbers for the settings "
319 					+ Preferences.FFW_SPEED_FIRST_STAGE + " and " + Preferences.FFW_SPEED_SECOND_STAGE, e);
320 		}
321 
322 		try {
323 			cutter.cutDemo(
324 				this.demoFile,
325 				cutDemo,
326 				this.startSecond,
327 				this.endSecond,
328 				injectAtStart,
329 				injectBeforeCap,
330 				injectAfterCap,
331 				fwdSpeedFirstStage,
332 				fwdSpeedSecondStage
333 			);
334 		} catch (DemoCutterException e) {
335 			throw new DemoRecorderException("Error occurred while trying to cut the demo: " + e.getMessage(), e);
336 		}
337 
338 	}
339 
removeOldAutocaps()340 	private void removeOldAutocaps() {
341 		for (String videoExtension : VIDEO_FILE_ENDINGS) {
342 			String fileString = this.dpVideoPath.getAbsolutePath() + File.separator + CUT_DEMO_CAPVIDEO_NAMEFORMAT_OVERRIDE
343 			+ CUT_DEMO_CAPVIDEO_NUMBER_OVERRIDE + "." + videoExtension;
344 			File videoFile = new File(fileString);
345 			cleanUpFiles.add(videoFile);
346 			if (videoFile.exists()) {
347 				if (!videoFile.delete()) {
348 					throw new DemoRecorderException("Could not delete old obsolete video file " + fileString);
349 				}
350 			}
351 		}
352 	}
353 
recordClip(File cutDemo)354 	private void recordClip(File cutDemo) {
355 		Process nexProc;
356 		String demoFileName = DemoRecorderUtils.getJustFileNameOfPath(cutDemo);
357 		String execPath = this.enginePath.getAbsolutePath() + " " + this.engineParameters + " -demo "
358 						+ this.relativeDemoPath + "/" + demoFileName;
359 		File engineDir = this.enginePath.getParentFile();
360 		try {
361 			nexProc = Runtime.getRuntime().exec(execPath, null, engineDir);
362 			nexProc.getErrorStream();
363 			nexProc.getOutputStream();
364 			InputStream is = nexProc.getInputStream();
365 			InputStreamReader isr = new InputStreamReader(is);
366 			BufferedReader br = new BufferedReader(isr);
367 			while (br.readLine() != null) {
368 				//System.out.println(line);
369 			}
370 		} catch (IOException e) {
371 			throw new DemoRecorderException("I/O Exception occurred when trying to execute the Nexuiz binary", e);
372 		}
373 	}
374 
run()375 	public void run() {
376 		this.execute();
377 	}
378 
setAppLayer(DemoRecorderApplication appLayer)379 	public void setAppLayer(DemoRecorderApplication appLayer) {
380 		this.appLayer = appLayer;
381 	}
382 
getJobIndex()383 	public int getJobIndex() {
384 		return jobIndex;
385 	}
386 
getEnginePath()387 	public File getEnginePath() {
388 		return enginePath;
389 	}
390 
setEnginePath(File enginePath)391 	public void setEnginePath(File enginePath) {
392 		this.checkForProcessingState();
393 		if (enginePath == null || !enginePath.exists()) {
394 			throw new DemoRecorderException("Could not locate engine binary!");
395 		}
396 		if (!enginePath.canExecute()) {
397 			throw new DemoRecorderException("The file you specified is not executable!");
398 		}
399 		this.enginePath = enginePath.getAbsoluteFile();
400 	}
401 
getEngineParameters()402 	public String getEngineParameters() {
403 		return engineParameters;
404 	}
405 
setEngineParameters(String engineParameters)406 	public void setEngineParameters(String engineParameters) {
407 		this.checkForProcessingState();
408 		if (engineParameters == null) {
409 			engineParameters = "";
410 		}
411 		this.engineParameters = engineParameters.trim();
412 	}
413 
getDemoFile()414 	public File getDemoFile() {
415 		return demoFile;
416 	}
417 
setDemoFile(File demoFile)418 	public void setDemoFile(File demoFile) {
419 		this.checkForProcessingState();
420 		if (demoFile == null) {
421 			throw new DemoRecorderException("Could not locate demo file!");
422 		}
423 		if (!demoFile.exists()) {
424 			throw new DemoRecorderException("Could not locate demo file!: " + demoFile.getAbsolutePath());
425 		}
426 		if (!doReadWriteTest(demoFile.getParentFile())) {
427 			throw new DemoRecorderException("The directory you specified for the demo to be recorded is not writable!");
428 		}
429 		if (!demoFile.getAbsolutePath().endsWith(".dem")) {
430 			throw new DemoRecorderException("The demo file you specified must have the ending .dem");
431 		}
432 
433 		this.demoFile = demoFile.getAbsoluteFile();
434 	}
435 
getRelativeDemoPath()436 	public String getRelativeDemoPath() {
437 		return relativeDemoPath;
438 	}
439 
setRelativeDemoPath(String relativeDemoPath)440 	public void setRelativeDemoPath(String relativeDemoPath) {
441 		this.checkForProcessingState();
442 		if (relativeDemoPath == null) {
443 			relativeDemoPath = "";
444 		}
445 
446 		//get rid of possible slashes
447 		while (relativeDemoPath.startsWith("/") || relativeDemoPath.startsWith("\\")) {
448 			relativeDemoPath = relativeDemoPath.substring(1, relativeDemoPath.length());
449 		}
450 		while (relativeDemoPath.endsWith("/") || relativeDemoPath.endsWith("\\")) {
451 			relativeDemoPath = relativeDemoPath.substring(0, relativeDemoPath.length() - 1);
452 		}
453 
454 		this.relativeDemoPath = relativeDemoPath.trim();
455 	}
456 
getDpVideoPath()457 	public File getDpVideoPath() {
458 		return dpVideoPath;
459 	}
460 
setDpVideoPath(File dpVideoPath)461 	public void setDpVideoPath(File dpVideoPath) {
462 		this.checkForProcessingState();
463 		if (dpVideoPath == null || !dpVideoPath.isDirectory()) {
464 			throw new DemoRecorderException("Could not locate the specified DPVideo directory!");
465 		}
466 
467 		if (!this.doReadWriteTest(dpVideoPath)) {
468 			throw new DemoRecorderException("The DPVideo directory is not writable! It needs to be writable so that the file can be moved to its new location");
469 		}
470 		this.dpVideoPath = dpVideoPath.getAbsoluteFile();
471 	}
472 
getVideoDestination()473 	public File getVideoDestination() {
474 		return videoDestination;
475 	}
476 
setVideoDestination(File videoDestination)477 	public void setVideoDestination(File videoDestination) {
478 		this.checkForProcessingState();
479 		//keep in mind, the parameter videoDestination points to the final avi/ogg file w/o extension!
480 		if (videoDestination == null || !videoDestination.getParentFile().isDirectory()) {
481 			throw new DemoRecorderException("Could not locate the specified video destination");
482 		}
483 
484 		if (!this.doReadWriteTest(videoDestination.getParentFile())) {
485 			throw new DemoRecorderException("The video destination directory is not writable! It needs to be writable so that the file can be moved to its new location");
486 		}
487 
488 		this.videoDestination = videoDestination.getAbsoluteFile();
489 	}
490 
getExecuteBeforeCap()491 	public String getExecuteBeforeCap() {
492 		return executeBeforeCap;
493 	}
494 
setExecuteBeforeCap(String executeBeforeCap)495 	public void setExecuteBeforeCap(String executeBeforeCap) {
496 		this.checkForProcessingState();
497 		if (executeBeforeCap == null) {
498 			executeBeforeCap = "";
499 		}
500 		executeBeforeCap = executeBeforeCap.trim();
501 		while (executeBeforeCap.endsWith(";")) {
502 			executeBeforeCap = executeBeforeCap.substring(0, executeBeforeCap.length()-1);
503 		}
504 		this.executeBeforeCap = executeBeforeCap;
505 	}
506 
getExecuteAfterCap()507 	public String getExecuteAfterCap() {
508 		return executeAfterCap;
509 	}
510 
setExecuteAfterCap(String executeAfterCap)511 	public void setExecuteAfterCap(String executeAfterCap) {
512 		this.checkForProcessingState();
513 		if (executeAfterCap == null) {
514 			executeAfterCap = "";
515 		}
516 		executeAfterCap = executeAfterCap.trim();
517 		while (executeAfterCap.endsWith(";")) {
518 			executeAfterCap = executeAfterCap.substring(0, executeAfterCap.length()-1);
519 		}
520 		if (executeAfterCap.contains("cl_capturevideo_number") || executeAfterCap.contains("cl_capturevideo_nameformat")) {
521 			throw new DemoRecorderException("Execute after String cannot contain cl_capturevideo_number or _nameformat changes!");
522 		}
523 		this.executeAfterCap = executeAfterCap;
524 	}
525 
getStartSecond()526 	public float getStartSecond() {
527 		return startSecond;
528 	}
529 
setStartSecond(float startSecond)530 	public void setStartSecond(float startSecond) {
531 		this.checkForProcessingState();
532 		if (startSecond < 0) {
533 			throw new DemoRecorderException("Start second cannot be < 0");
534 		}
535 		this.startSecond = startSecond;
536 	}
537 
getEndSecond()538 	public float getEndSecond() {
539 		return endSecond;
540 	}
541 
setEndSecond(float endSecond)542 	public void setEndSecond(float endSecond) {
543 		this.checkForProcessingState();
544 		if (endSecond < this.startSecond) {
545 			throw new DemoRecorderException("End second cannot be < start second");
546 		}
547 		this.endSecond = endSecond;
548 	}
549 
getState()550 	public State getState() {
551 		return state;
552 	}
553 
setState(State state)554 	public void setState(State state) {
555 		this.state = state;
556 		this.appLayer.fireUserInterfaceUpdate(this);
557 	}
558 
getJobName()559 	public String getJobName() {
560 		if (this.jobName == null || this.jobName.equals("")) {
561 			return "Job " + this.jobIndex;
562 		}
563 		return this.jobName;
564 	}
565 
setJobName(String jobName)566 	public void setJobName(String jobName) {
567 		if (jobName == null || jobName.equals("")) {
568 			this.jobIndex = appLayer.getNewJobIndex();
569 			this.jobName = "Job " + this.jobIndex;
570 		} else {
571 			this.jobName = jobName;
572 		}
573 	}
574 
getLastException()575 	public DemoRecorderException getLastException() {
576 		return lastException;
577 	}
578 
579 	/**
580 	 * Tests whether the given directory is writable by creating a file in there and deleting
581 	 * it again.
582 	 * @param directory
583 	 * @return true if directory is writable
584 	 */
doReadWriteTest(File directory)585 	protected boolean doReadWriteTest(File directory) {
586 		boolean writable = false;
587 		String fileName = "tmp." + Math.random()*10000 + ".dat";
588 		File tempFile = new File(directory, fileName);
589 		try {
590 			writable = tempFile.createNewFile();
591 			if (writable) {
592 				tempFile.delete();
593 			}
594 		} catch (IOException e) {
595 			writable = false;
596 		}
597 		return writable;
598 	}
599 
checkForProcessingState()600 	private void checkForProcessingState() {
601 		if (this.state == State.PROCESSING) {
602 			throw new DemoRecorderException("Cannot modify this job while it is processing!");
603 		}
604 	}
605 
getEncoderPluginSettings(EncoderPlugin plugin)606 	public Properties getEncoderPluginSettings(EncoderPlugin plugin) {
607 		if (this.encoderPluginSettings.containsKey(plugin.getName())) {
608 			return this.encoderPluginSettings.get(plugin.getName());
609 		} else {
610 			return new Properties();
611 		}
612 	}
613 
setEncoderPluginSetting(String pluginName, String pluginSettingKey, String value)614 	public void setEncoderPluginSetting(String pluginName, String pluginSettingKey, String value) {
615 		Properties p = this.encoderPluginSettings.get(pluginName);
616 		if (p == null) {
617 			p = new Properties();
618 			this.encoderPluginSettings.put(pluginName, p);
619 		}
620 
621 		p.put(pluginSettingKey, value);
622 	}
623 
getEncoderPluginSettings()624 	public Map<String, Properties> getEncoderPluginSettings() {
625 		return encoderPluginSettings;
626 	}
627 
setEncoderPluginSettings(Map<String, Properties> encoderPluginSettings)628 	public void setEncoderPluginSettings(Map<String, Properties> encoderPluginSettings) {
629 		this.encoderPluginSettings = encoderPluginSettings;
630 	}
631 
getActualVideoDestination()632 	public File getActualVideoDestination() {
633 		return actualVideoDestination;
634 	}
635 
setActualVideoDestination(File actualVideoDestination)636 	public void setActualVideoDestination(File actualVideoDestination) {
637 		this.actualVideoDestination = actualVideoDestination;
638 	}
639 }
640