From 36c85690aad35ac4737d5f621b335a63a47b045a Mon Sep 17 00:00:00 2001 From: Sean-M Date: Wed, 20 Mar 2013 10:18:55 -0400 Subject: [PATCH 1/9] Minor Test Additions and fixes --- Testing/script/regression.py | 11 +++++++++++ .../org/sleuthkit/autopsy/testing/RegressionTest.java | 3 ++- 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/Testing/script/regression.py b/Testing/script/regression.py index 2edecc8423..0f6fca14fe 100644 --- a/Testing/script/regression.py +++ b/Testing/script/regression.py @@ -309,6 +309,12 @@ class Database: for type_id in range(1, length): autopsy_cur.execute("SELECT COUNT(*) FROM blackboard_artifacts WHERE artifact_type_id=%d" % type_id) self.autopsy_artifacts.append(autopsy_cur.fetchone()[0]) + autopsy_cur.execute("SELECT * FROM blackboard_artifacts") + self.autopsy_artifacts_list = [] + for row in autopsy_cur.fetchall(): + for item in row: + self.autopsy_artifacts_list.append(item) + def generate_autopsy_attributes(self): if self.autopsy_attributes == 0: @@ -340,6 +346,11 @@ class Database: for type_id in range(1, length): gold_cur.execute("SELECT COUNT(*) FROM blackboard_artifacts WHERE artifact_type_id=%d" % type_id) self.gold_artifacts.append(gold_cur.fetchone()[0]) + gold_cur.execute("SELECT * FROM blackboard_artifacts") + self.gold_artifacts_list = [] + for row in gold_cur.fetchall(): + for item in row: + self.gold_artifacts_list.append(item) def generate_gold_attributes(self): if self.gold_attributes == 0: diff --git a/Testing/test/qa-functional/src/org/sleuthkit/autopsy/testing/RegressionTest.java b/Testing/test/qa-functional/src/org/sleuthkit/autopsy/testing/RegressionTest.java index c3df9370a1..46b3abb8bb 100644 --- a/Testing/test/qa-functional/src/org/sleuthkit/autopsy/testing/RegressionTest.java +++ b/Testing/test/qa-functional/src/org/sleuthkit/autopsy/testing/RegressionTest.java @@ -296,7 +296,7 @@ public class RegressionTest extends TestCase{ new Timeout("pausing", 1000).sleep(); JButtonOperator jbo1 = new JButtonOperator(reportDialogOperator, "Finish"); jbo1.pushNoBlock(); - new Timeout("pausing", 8000).sleep(); // Give it a few seconds to generate + new Timeout("pausing", 20000).sleep(); // Give it a few seconds to generate screenshot("Progress"); JDialog previewDialog = JDialogOperator.waitJDialog("Progress", false, false); JDialogOperator previewDialogOperator = new JDialogOperator(previewDialog); @@ -305,6 +305,7 @@ public class RegressionTest extends TestCase{ new Timeout("pausing", 3000).sleep(); // Give the program a second to idle to be safe System.setProperty("ReportStr", datenotime); screenshot("Done Testing"); + System.exit(0); } public void screenshot(String name) { From fb887c91d7f31403b49a1300539a25696123e96f Mon Sep 17 00:00:00 2001 From: Sean-M Date: Wed, 20 Mar 2013 12:34:46 -0400 Subject: [PATCH 2/9] Updated regression.py and documentation --- Testing/script/Setup.txt | 11 +++++++++++ Testing/script/regression.py | 9 +++++++-- 2 files changed, 18 insertions(+), 2 deletions(-) create mode 100644 Testing/script/Setup.txt diff --git a/Testing/script/Setup.txt b/Testing/script/Setup.txt new file mode 100644 index 0000000000..5b745453fe --- /dev/null +++ b/Testing/script/Setup.txt @@ -0,0 +1,11 @@ +This file explains how to setup regression and continuous testing for Autopsy. + +1)Create a folder called 'gold', this is where the test standards will go. +2)Create a folder called 'input', this is where the image, hash, and keyword search files. +3)Put an idx file called NSRL, that contains known bad hashes, in the input folder. +4)Put an idx file called notablehashes, that contains notable hashes, in the input folder. +5)Put an xml file called notablekeywords, that contains keywords, in the input folder. +6)The xml file should be of the format of the notablekeywords-example.xml file. +7)Create a folder called 'output', this is where the output from tests go. +8)The config.xml file is an example of a configuration file and contains all of the available elements. +9)Use the -g diff --git a/Testing/script/regression.py b/Testing/script/regression.py index 0f6fca14fe..07b528c932 100644 --- a/Testing/script/regression.py +++ b/Testing/script/regression.py @@ -66,6 +66,7 @@ class Args: self.exception = False self.exception_string = "" self.contin = False + self.gold_creation = False def parse(self): global nxtproc @@ -128,6 +129,9 @@ class Args: elif arg == "-c" or arg == "--continuous": printout("Running until interrupted") self.contin = True + elif arg == "-g" or arg == "--gold": + printout("Creating gold standards") + self.gold_creation = True else: printout(usage()) return False @@ -523,8 +527,9 @@ def run_test(image_file, count): print_report(exceptions, "EXCEPTION", okay) # Now test in comparison to the gold standards - compare_to_gold_db() - compare_to_gold_html() + if !args.gold_creation: + compare_to_gold_db() + compare_to_gold_html() # Make the CSV log and the html log viewer generate_csv(case.csv) From 1b08f9c92a14c88489a335c2114bd598a32364b0 Mon Sep 17 00:00:00 2001 From: Sean-M Date: Wed, 20 Mar 2013 12:48:30 -0400 Subject: [PATCH 3/9] updated regression test and documentation --- Testing/script/Setup.txt | 4 +++- Testing/script/regression.py | 5 +++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/Testing/script/Setup.txt b/Testing/script/Setup.txt index 5b745453fe..e70ab3f39b 100644 --- a/Testing/script/Setup.txt +++ b/Testing/script/Setup.txt @@ -8,4 +8,6 @@ This file explains how to setup regression and continuous testing for Autopsy. 6)The xml file should be of the format of the notablekeywords-example.xml file. 7)Create a folder called 'output', this is where the output from tests go. 8)The config.xml file is an example of a configuration file and contains all of the available elements. -9)Use the -g +9)Use the -g command to create a gold standard without testing. +10)Use the -r command to rebuild gold standards with testing. +11)Use the -c command to run testing continuously until interrupted, this will also update all of the sleuthkit and autopsy code from github and will rebuild the code so that it is running the latest version after each test. \ No newline at end of file diff --git a/Testing/script/regression.py b/Testing/script/regression.py index 07b528c932..4f47187f50 100644 --- a/Testing/script/regression.py +++ b/Testing/script/regression.py @@ -527,7 +527,7 @@ def run_test(image_file, count): print_report(exceptions, "EXCEPTION", okay) # Now test in comparison to the gold standards - if !args.gold_creation: + if not args.gold_creation: compare_to_gold_db() compare_to_gold_html() @@ -1736,7 +1736,8 @@ def execute_test(): errorem = "" errorem += "There were no Errors.\n" attachl = [] - send_email() + if not args.gold_creation: + send_email() def send_email(): From fd40cde19b5dbffd90109a8a2a9ffc81e75f9c5e Mon Sep 17 00:00:00 2001 From: Sean-M Date: Wed, 20 Mar 2013 17:14:57 -0400 Subject: [PATCH 4/9] Regression test improvements, now uses zip format --- Testing/script/regression.py | 37 ++++++++++++++++--- .../autopsy/testing/RegressionTest.java | 13 +++++++ 2 files changed, 44 insertions(+), 6 deletions(-) diff --git a/Testing/script/regression.py b/Testing/script/regression.py index 4f47187f50..eb7c442caf 100644 --- a/Testing/script/regression.py +++ b/Testing/script/regression.py @@ -24,6 +24,7 @@ from email.MIMEBase import MIMEBase from email import Encoders import urllib2 import re +import zipfile # # Please read me... @@ -528,16 +529,26 @@ def run_test(image_file, count): # Now test in comparison to the gold standards if not args.gold_creation: - compare_to_gold_db() - compare_to_gold_html() - + try: + gold_path = os.path.join(case.gold) + img_gold = os.path.join(case.gold, case.image_name) + img_archive = os.path.join(case.gold, case.image_name+"-archive.zip") + extrctr = zipfile.ZipFile(img_archive, 'r') + extrctr.extractall(gold_path) + extrctr.close + compare_to_gold_db() + compare_to_gold_html() + compare_errors() + del_dir(img_gold) + except: + print("Tests failed due to an error, try rebuilding or creating gold standards.\n") # Make the CSV log and the html log viewer generate_csv(case.csv) if case.global_csv: generate_csv(case.global_csv) generate_html() # If running in rebuild mode (-r) - if args.rebuild: + if args.rebuild or args.gold_creation: rebuild() # Reset the case and return the tests sucessfully finished clear_dir(make_local_path(case.output_dir, case.image_name, "AutopsyTestCase", "ModuleOutput", "keywordsearch")) @@ -639,9 +650,23 @@ def rebuild(): except Exception as e: errors.append("Error: Unknown fatal error when rebuilding the gold html report.") errors.append(str(e) + "\n") + oldcwd = os.getcwd() + os.chdir(case.gold) + img_archive = make_local_path(case.image_name+"-archive.zip") + img_gold = os.path.join(case.image_name) + comprssr = zipfile.ZipFile(img_archive, 'w') + zipdir(img_gold, comprssr) + comprssr.close() + del_dir(gold_dir) + os.chdir(oldcwd) okay = "Sucessfully rebuilt all gold standards." print_report(errors, "REBUILDING", okay) +def zipdir(path, zip): + for root, dirs, files in os.walk(path): + for file in files: + zip.write(os.path.join(root, file)) + # Using the global case's variables, compare the database file made by the # regression test to the gold standard database file # Initializes the global database, which stores the information retrieved @@ -856,7 +881,6 @@ def generate_common_log(): case.sorted_log = make_local_path(case.output_dir, case.image_name, case.image_name + "SortedErrors.txt") srtcmdlst = ["sort", case.common_log_path, "-o", case.sorted_log] subprocess.call(srtcmdlst) - compare_errors() except Exception as e: printerror("Error: Unable to generate the common log.") printerror(str(e) + "\n") @@ -906,9 +930,10 @@ def fill_case_data(): start = datetime.datetime.strptime(case.start_date, "%b %d, %Y %I:%M:%S %p") end = datetime.datetime.strptime(case.end_date, "%a %b %d %H:%M:%S %Y") case.total_test_time = str(end - start) - + try: # Set Autopsy version, heap space, ingest time, and service times + version_line = search_logs("INFO: Application name: Autopsy, version:")[0] case.autopsy_version = get_word_at(version_line, 5).rstrip(",") diff --git a/Testing/test/qa-functional/src/org/sleuthkit/autopsy/testing/RegressionTest.java b/Testing/test/qa-functional/src/org/sleuthkit/autopsy/testing/RegressionTest.java index 46b3abb8bb..5eca3fae14 100644 --- a/Testing/test/qa-functional/src/org/sleuthkit/autopsy/testing/RegressionTest.java +++ b/Testing/test/qa-functional/src/org/sleuthkit/autopsy/testing/RegressionTest.java @@ -296,6 +296,19 @@ public class RegressionTest extends TestCase{ new Timeout("pausing", 1000).sleep(); JButtonOperator jbo1 = new JButtonOperator(reportDialogOperator, "Finish"); jbo1.pushNoBlock(); + boolean waiting = true; + long size = 0; + java.io.File rprtfldr = new java.io.File(System.getProperty("out_path")+java.io.File.separator+"AutopsyTestCase"+java.io.File.separator+"Reports"+java.io.File.separator+"AutopsyTestCase "+datenotime+java.io.File.separator+"HTML Report"); + while(waiting) + { + long currsize = size; + size = 0; + for(java.io.File elem: rprtfldr.listFiles()) + { + size+=elem.getTotalSpace(); + } + waiting = size>currsize; + } new Timeout("pausing", 20000).sleep(); // Give it a few seconds to generate screenshot("Progress"); JDialog previewDialog = JDialogOperator.waitJDialog("Progress", false, false); From 6c8199d62c3f6bcbcadc58e5a9ddda7ab85c780b Mon Sep 17 00:00:00 2001 From: Sean-M Date: Thu, 21 Mar 2013 15:26:28 -0400 Subject: [PATCH 5/9] updates to regression testing --- .gitignore | 1 + Testing/script/regression.py | 10 ++++++---- .../org/sleuthkit/autopsy/testing/RegressionTest.java | 6 +++++- build.xml | 7 ++++++- 4 files changed, 18 insertions(+), 6 deletions(-) diff --git a/.gitignore b/.gitignore index ed00b53eb2..f207b62847 100644 --- a/.gitignore +++ b/.gitignore @@ -47,6 +47,7 @@ genfiles.properties !/branding/nbproject/project.xml !/branding/nbproject/project.properties /Testing/script/input/ +/Testing/script/input/* /Testing/script/output/ /Testing/script/gold/ /Testing/script/ScriptLog.txt diff --git a/Testing/script/regression.py b/Testing/script/regression.py index eb7c442caf..c8e04a9dfc 100644 --- a/Testing/script/regression.py +++ b/Testing/script/regression.py @@ -25,6 +25,7 @@ from email import Encoders import urllib2 import re import zipfile +import zlib # # Please read me... @@ -533,7 +534,7 @@ def run_test(image_file, count): gold_path = os.path.join(case.gold) img_gold = os.path.join(case.gold, case.image_name) img_archive = os.path.join(case.gold, case.image_name+"-archive.zip") - extrctr = zipfile.ZipFile(img_archive, 'r') + extrctr = zipfile.ZipFile(img_archive, 'r', zipfile.ZIP_DEFLATED) extrctr.extractall(gold_path) extrctr.close compare_to_gold_db() @@ -590,7 +591,7 @@ def run_ant(): if SYS is OS.CYGWIN: subprocess.call(case.ant, stdout=antout) elif SYS is OS.WIN: - theproc = subprocess.Popen(case.ant, shell = True) + theproc = subprocess.Popen(case.ant, shell = True, stdout=subprocess.PIPE) theproc.communicate() antout.close() @@ -612,7 +613,8 @@ def image_type(image_file): return IMGTYPE.UNKNOWN - +#http://digitalcorpora.org/corp/nps/drives/nps-2008-jean/nps-2008-jean.E01 +#http://digitalcorpora.org/corp/nps/drives/nps-2008-jean/nps-2008-jean.E02 #-----------------------------------------------------------# # Functions relating to rebuilding and comparison # # of gold standards # @@ -654,7 +656,7 @@ def rebuild(): os.chdir(case.gold) img_archive = make_local_path(case.image_name+"-archive.zip") img_gold = os.path.join(case.image_name) - comprssr = zipfile.ZipFile(img_archive, 'w') + comprssr = zipfile.ZipFile(img_archive, 'w',zipfile.ZIP_DEFLATED) zipdir(img_gold, comprssr) comprssr.close() del_dir(gold_dir) diff --git a/Testing/test/qa-functional/src/org/sleuthkit/autopsy/testing/RegressionTest.java b/Testing/test/qa-functional/src/org/sleuthkit/autopsy/testing/RegressionTest.java index 5eca3fae14..c906be081e 100644 --- a/Testing/test/qa-functional/src/org/sleuthkit/autopsy/testing/RegressionTest.java +++ b/Testing/test/qa-functional/src/org/sleuthkit/autopsy/testing/RegressionTest.java @@ -51,6 +51,7 @@ import org.netbeans.jemmy.operators.JButtonOperator; import org.netbeans.jemmy.operators.JCheckBoxOperator; import org.netbeans.jemmy.operators.JDialogOperator; import org.netbeans.jemmy.operators.JFileChooserOperator; +import org.netbeans.jemmy.operators.JLabelOperator; import org.netbeans.jemmy.operators.JTabbedPaneOperator; import org.netbeans.jemmy.operators.JTableOperator; import org.netbeans.jemmy.operators.JTextFieldOperator; @@ -297,19 +298,22 @@ public class RegressionTest extends TestCase{ JButtonOperator jbo1 = new JButtonOperator(reportDialogOperator, "Finish"); jbo1.pushNoBlock(); boolean waiting = true; + new Timeout("pausing", 500).sleep(); long size = 0; java.io.File rprtfldr = new java.io.File(System.getProperty("out_path")+java.io.File.separator+"AutopsyTestCase"+java.io.File.separator+"Reports"+java.io.File.separator+"AutopsyTestCase "+datenotime+java.io.File.separator+"HTML Report"); while(waiting) { + new Timeout("pausing", 500).sleep(); long currsize = size; size = 0; for(java.io.File elem: rprtfldr.listFiles()) { size+=elem.getTotalSpace(); } + System.out.println("\nJAVA:"+size); + System.out.println("JAVA:"+currsize); waiting = size>currsize; } - new Timeout("pausing", 20000).sleep(); // Give it a few seconds to generate screenshot("Progress"); JDialog previewDialog = JDialogOperator.waitJDialog("Progress", false, false); JDialogOperator previewDialogOperator = new JDialogOperator(previewDialog); diff --git a/build.xml b/build.xml index 9542a3f27b..6986f82af9 100644 --- a/build.xml +++ b/build.xml @@ -17,7 +17,7 @@ - + @@ -239,4 +239,9 @@ + + + + + From e4d5165914796ffcc578734ad9109ca243562001 Mon Sep 17 00:00:00 2001 From: Sean-M Date: Thu, 21 Mar 2013 16:50:37 -0400 Subject: [PATCH 6/9] changed regression test file structure --- Testing/script/config.xml | 1 + Testing/script/regression.py | 38 ++++++++++++++++++++++++------------ build.xml | 2 +- 3 files changed, 28 insertions(+), 13 deletions(-) diff --git a/Testing/script/config.xml b/Testing/script/config.xml index c5482529ae..0d11515406 100644 --- a/Testing/script/config.xml +++ b/Testing/script/config.xml @@ -35,6 +35,7 @@ It is up to the user to distinguish between the paths when adding to this file. + \ No newline at end of file diff --git a/Testing/script/regression.py b/Testing/script/regression.py index c8e04a9dfc..549bf07a6f 100644 --- a/Testing/script/regression.py +++ b/Testing/script/regression.py @@ -106,7 +106,7 @@ class Args: printout("Ignoring unallocated space.\n") self.unallocated = True elif(arg == "-i" or arg == "--ignore"): - printout("Ignoring the ./input directory.\n") + printout("Ignoring the ../input directory.\n") self.ignore = True elif(arg == "-k" or arg == "--keep"): printout("Keeping the Solr index.\n") @@ -148,9 +148,9 @@ class Args: class TestAutopsy: def __init__(self): # Paths: - self.input_dir = make_local_path("input") + self.input_dir = make_local_path("../","input") self.output_dir = "" - self.gold = "gold" + self.gold = make_local_path("../", "output", "gold") # Logs: self.antlog_dir = "" self.common_log = "" @@ -171,6 +171,7 @@ class TestAutopsy: self.known_bad_path = "" self.keyword_path = "" self.nsrl_path = "" + self.build_path = "" # Case info self.start_date = "" self.end_date = "" @@ -479,6 +480,7 @@ def compile(): # Runs the test on the single given file. # The path must be guarenteed to be a correct path. def run_test(image_file, count): + global parsed if image_type(image_file) == IMGTYPE.UNKNOWN: printerror("Error: Image type is unrecognized:") printerror(image_file + "\n") @@ -491,6 +493,18 @@ def run_test(image_file, count): case.common_log_path = make_local_path(case.output_dir, case.image_name, case.image_name+case.common_log) case.warning_log = make_local_path(case.output_dir, case.image_name, "AutopsyLogs.txt") case.antlog_dir = make_local_path(case.output_dir, case.image_name, "antlog.txt") + if(args.list): + element = parsed.getElementsByTagName("build") + if(len(element)<=0): + toval = make_path("..", "build.xml") + else: + element = element[0] + toval = element.getAttribute("value").encode().decode("utf_8") + if(toval==None): + toval = make_path("..", "build.xml") + else: + toval = make_path("..", "build.xml") + case.build_path = toval case.known_bad_path = make_path(case.input_dir, "notablehashes.txt-md5.idx") case.keyword_path = make_path(case.input_dir, "notablekeywords.xml") case.nsrl_path = make_path(case.input_dir, "nsrl.txt-md5.idx") @@ -569,6 +583,7 @@ def run_ant(): case.ant = ["ant"] case.ant.append("-v") case.ant.append("-f") +# case.ant.append(case.build_path) case.ant.append(os.path.join("..","build.xml")) case.ant.append("regression-test") case.ant.append("-l") @@ -613,8 +628,7 @@ def image_type(image_file): return IMGTYPE.UNKNOWN -#http://digitalcorpora.org/corp/nps/drives/nps-2008-jean/nps-2008-jean.E01 -#http://digitalcorpora.org/corp/nps/drives/nps-2008-jean/nps-2008-jean.E02 + #-----------------------------------------------------------# # Functions relating to rebuilding and comparison # # of gold standards # @@ -1647,19 +1661,19 @@ def usage(): Usage: ./regression.py [-f FILE] [OPTIONS] Run RegressionTest.java, and compare the result with a gold standard. - By default, the script tests every image in ./input + By default, the script tests every image in ../input When the -f flag is set, this script only tests a single given image. When the -l flag is set, the script looks for a configuration file, which may outsource to a new input directory and to individual images. Expected files: - An NSRL database at: ./input/nsrl.txt-md5.idx - A notable hash database at: ./input/notablehashes.txt-md5.idx - A notable keyword file at: ./input/notablekeywords.xml + An NSRL database at: ../input/nsrl.txt-md5.idx + A notable hash database at: ../input/notablehashes.txt-md5.idx + A notable keyword file at: ../input/notablekeywords.xml Options: -r Rebuild the gold standards for the image(s) tested. - -i Ignores the ./input directory and all files within it. + -i Ignores the ../input directory and all files within it. -u Tells Autopsy not to ingest unallocated space. -k Keeps each image's Solr index instead of deleting it. -v Verbose mode; prints all errors to the screen. @@ -1713,7 +1727,7 @@ def execute_test(): global failedbool global html global attachl - case.output_dir = make_path("output", time.strftime("%Y.%m.%d-%H.%M.%S")) + case.output_dir = make_path("../", "output", "results", time.strftime("%Y.%m.%d-%H.%M.%S")) os.makedirs(case.output_dir) case.common_log = "AutopsyErrors.txt" case.csv = make_local_path(case.output_dir, "CSV.txt") @@ -1739,7 +1753,7 @@ def execute_test(): return run_test(args.single_file, 0) # If user has not selected a single file, and does not want to ignore - # the input directory, continue on to parsing ./input + # the input directory, continue on to parsing ../input if (not args.single) and (not args.ignore): for file in os.listdir(case.input_dir): # Make sure it's not a required hash/keyword file or dir diff --git a/build.xml b/build.xml index 6986f82af9..9eb5734ae1 100644 --- a/build.xml +++ b/build.xml @@ -239,7 +239,7 @@ - + From 88138e0c03397e037c4f65dca30ac03fee66cc54 Mon Sep 17 00:00:00 2001 From: Sean Moss Date: Fri, 22 Mar 2013 14:45:42 -0400 Subject: [PATCH 7/9] fixed regression.py --- Testing/script/regression.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Testing/script/regression.py b/Testing/script/regression.py index eb7c442caf..798c81a18a 100644 --- a/Testing/script/regression.py +++ b/Testing/script/regression.py @@ -533,7 +533,7 @@ def run_test(image_file, count): gold_path = os.path.join(case.gold) img_gold = os.path.join(case.gold, case.image_name) img_archive = os.path.join(case.gold, case.image_name+"-archive.zip") - extrctr = zipfile.ZipFile(img_archive, 'r') + extrctr = zipfile.ZipFile(img_archive, 'r', compression=zipfile.ZIP_DEFLATED) extrctr.extractall(gold_path) extrctr.close compare_to_gold_db() @@ -654,7 +654,7 @@ def rebuild(): os.chdir(case.gold) img_archive = make_local_path(case.image_name+"-archive.zip") img_gold = os.path.join(case.image_name) - comprssr = zipfile.ZipFile(img_archive, 'w') + comprssr = zipfile.ZipFile(img_archive, 'w',compression=zipfile.ZIP_DEFLATED) zipdir(img_gold, comprssr) comprssr.close() del_dir(gold_dir) From 7463c37e2f746256159448947918d9c9b65949cb Mon Sep 17 00:00:00 2001 From: Sean-M Date: Mon, 25 Mar 2013 10:59:33 -0400 Subject: [PATCH 8/9] Merge branch 'master' of https://github.com/Smoss/autopsy Conflicts: Testing/script/regression.py --- .gitignore | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/.gitignore b/.gitignore index f207b62847..3aff8b82a1 100644 --- a/.gitignore +++ b/.gitignore @@ -46,10 +46,9 @@ genfiles.properties /branding/nbproject/* !/branding/nbproject/project.xml !/branding/nbproject/project.properties -/Testing/script/input/ -/Testing/script/input/* -/Testing/script/output/ -/Testing/script/gold/ +/Testing/input/ +/Testing/input/* +/Testing/output/* /Testing/script/ScriptLog.txt /Testing/build/ /Testing/dist/ From cead01fa9d54e2b101338d9482eff3044982d8d5 Mon Sep 17 00:00:00 2001 From: Sean-M Date: Tue, 26 Mar 2013 10:54:21 -0400 Subject: [PATCH 9/9] Had to roll back a lot of commits, mostly related to fixing the file hierarchy. --- .gitignore | 18 +- BUILDING.txt | 9 +- .../sleuthkit/autopsy/casemodule/Case.java | 16 +- .../autopsy/casemodule/CaseOpenAction.java | 43 +- .../autopsy/casemodule/GeneralFilter.java | 5 +- .../casemodule/NewCaseVisualPanel1.java | 2 +- .../org/sleuthkit/autopsy/core/Installer.java | 10 +- .../autopsy/corecomponents/Bundle.properties | 6 +- .../DataContentTopComponent.form | 21 +- .../DataContentTopComponent.java | 20 +- .../DataContentViewerMedia.form | 82 +- .../DataContentViewerMedia.java | 765 ++---------------- .../corecomponents/MediaViewImagePanel.form | 25 + .../corecomponents/MediaViewImagePanel.java | 209 +++++ .../corecomponents/MediaViewVideoPanel.form | 113 +++ .../corecomponents/MediaViewVideoPanel.java | 646 +++++++++++++++ .../datamodel/ExtractedContentChildren.java | 4 +- .../autopsy/directorytree/TagFileAction.java | 1 + .../ExifParserFileIngestModule.java | 13 +- NEWS.txt | 4 +- {Testing => Test}/script/Setup.txt | 0 {Testing => Test}/script/config.xml | 0 {Testing => Test}/script/getcounts.py | 0 {Testing => Test}/script/regression.py | 77 +- .../autopsy/testing/RegressionTest.java | 11 +- .../sleuthkit/autopsy/timeline/Timeline.java | 20 +- .../netbeans/core/startup/Bundle.properties | 4 +- .../core/windows/view/ui/Bundle.properties | 6 +- 28 files changed, 1237 insertions(+), 893 deletions(-) create mode 100644 Core/src/org/sleuthkit/autopsy/corecomponents/MediaViewImagePanel.form create mode 100644 Core/src/org/sleuthkit/autopsy/corecomponents/MediaViewImagePanel.java create mode 100644 Core/src/org/sleuthkit/autopsy/corecomponents/MediaViewVideoPanel.form create mode 100644 Core/src/org/sleuthkit/autopsy/corecomponents/MediaViewVideoPanel.java rename {Testing => Test}/script/Setup.txt (100%) rename {Testing => Test}/script/config.xml (100%) rename {Testing => Test}/script/getcounts.py (100%) rename {Testing => Test}/script/regression.py (95%) diff --git a/.gitignore b/.gitignore index 3aff8b82a1..fcb302ecc4 100644 --- a/.gitignore +++ b/.gitignore @@ -46,13 +46,13 @@ genfiles.properties /branding/nbproject/* !/branding/nbproject/project.xml !/branding/nbproject/project.properties -/Testing/input/ -/Testing/input/* -/Testing/output/* -/Testing/script/ScriptLog.txt -/Testing/build/ -/Testing/dist/ -/Testing/nbproject/* +/Test/input/ +/Test/input/* +/Test/output/* +/Test/script/ScriptLog.txt +/Test/build/ +/Test/dist/ +/Test/nbproject/* !/Testing/nbproject/project.xml !/Testing/nbproject/project.properties *~ @@ -64,5 +64,5 @@ genfiles.properties hs_err_pid*.log Core/src/org/sleuthkit/autopsy/casemodule/docs/QuickStart.html Core/src/org/sleuthkit/autopsy/casemodule/docs/screenshot.png -/Testing/script/myconfig.xml -/Testing/script/*/*.xml +/Test/script/myconfig.xml +/Test/script/*/*.xml diff --git a/BUILDING.txt b/BUILDING.txt index b8295269e9..7cba4cdc1e 100644 --- a/BUILDING.txt +++ b/BUILDING.txt @@ -1,4 +1,4 @@ - Last Updated: Sept 28, 2012 + Last Updated: March 23, 2013 This file outlines what it takes to build Autopsy from source. @@ -14,6 +14,11 @@ STEPS: 1a) Download and install 32-bit version of JDK version 1.7 (32-bit is currently needed even if you have a 64-bit system). +Autopsy has been used and tested with Oracle JavaSE and the included JavaFX support +(http://www.oracle.com/technetwork/java/javase/downloads/index.html). + +OpenJDK and OpenJFX might work, but they are not fully tested with Autopsy. + 1b) Ensure that JDK_HOME is set to the root JDK directory. 1c) (optional) Download and install Netbeans IDE (http://netbeans.org/) @@ -28,7 +33,7 @@ need to set JRE_HOME to the root JRE directory. 2) Get Sleuth Kit Setup -2a) Download and build the release version of Libewf2 (20120304 or +2a) Download and build the release version of Libewf2 (20130119 or later). All you need is the dll file. Note that you will get a launching error if you use libewf 1. - http://sourceforge.net/projects/libewf/ diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/Case.java b/Core/src/org/sleuthkit/autopsy/casemodule/Case.java index d1615f6c48..c39f10d476 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/Case.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/Case.java @@ -116,6 +116,8 @@ public class Case { private static Case currentCase = null; private Services services; private static final Logger logger = Logger.getLogger(Case.class.getName()); + static final String CASE_EXTENSION = "aut"; + static final String CASE_DOT_EXTENSION = "." + CASE_EXTENSION; /** * Constructor for the Case class @@ -202,7 +204,7 @@ public class Case { static void create(String caseDir, String caseName, String caseNumber, String examiner) throws CaseActionException { logger.log(Level.INFO, "Creating new case.\ncaseDir: {0}\ncaseName: {1}", new Object[]{caseDir, caseName}); - String configFilePath = caseDir + File.separator + caseName + ".aut"; + String configFilePath = caseDir + File.separator + caseName + CASE_DOT_EXTENSION; XMLCaseManagement xmlcm = new XMLCaseManagement(); xmlcm.create(caseDir, caseName, examiner, caseNumber); // create a new XML config file @@ -260,7 +262,12 @@ public class Case { // close the previous case if there's any CaseCloseAction closeCase = SystemAction.get(CaseCloseAction.class); closeCase.actionPerformed(null); - throw new CaseActionException("Error opening the case", ex); + if (!configFilePath.endsWith(CASE_DOT_EXTENSION)) { + throw new CaseActionException("Check that you selected the correct case file (usually with " + + CASE_DOT_EXTENSION + " extension)", ex); + } else { + throw new CaseActionException("Error opening the case", ex); + } } } @@ -562,7 +569,6 @@ public class Case { } } - /** * Get absolute module output directory path where modules should save their * permanent data The directory is a subdirectory of this case dir. @@ -916,7 +922,7 @@ public class Case { Frame f = WindowManager.getDefault().getMainWindow(); f.setTitle(Case.getAppName()); // set the window name to just application name - + //try to force gc to happen System.gc(); System.gc(); @@ -924,7 +930,7 @@ public class Case { //log memory usage after case changed logger.log(Level.INFO, PlatformUtil.getAllMemUsageInfo()); - + } diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/CaseOpenAction.java b/Core/src/org/sleuthkit/autopsy/casemodule/CaseOpenAction.java index 6f5406dbbf..41b1d8eb24 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/CaseOpenAction.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/CaseOpenAction.java @@ -16,7 +16,6 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - package org.sleuthkit.autopsy.casemodule; import java.awt.Component; @@ -27,6 +26,8 @@ import java.util.Collections; import java.util.logging.Level; import javax.swing.JFileChooser; import javax.swing.JOptionPane; +import javax.swing.filechooser.FileFilter; +import javax.swing.filechooser.FileNameExtensionFilter; import org.openide.util.lookup.ServiceProvider; import org.sleuthkit.autopsy.coreutils.ModuleSettings; import org.sleuthkit.autopsy.coreutils.Logger; @@ -36,33 +37,35 @@ import org.sleuthkit.autopsy.coreutils.Logger; */ @ServiceProvider(service = CaseOpenAction.class) public final class CaseOpenAction implements ActionListener { + private static final Logger logger = Logger.getLogger(CaseOpenAction.class.getName()); private static final String PROP_BASECASE = "LBL_BaseCase_PATH"; + private final JFileChooser fc = new JFileChooser(); + private FileFilter autFilter; - JFileChooser fc = new JFileChooser(); - GeneralFilter autFilter = new GeneralFilter(Collections.singletonList(".aut"), "AUTOPSY File (*.aut)"); - - /** The constructor */ + /** + * The constructor + */ public CaseOpenAction() { + autFilter = new FileNameExtensionFilter(org.sleuthkit.autopsy.coreutils.Version.getName() + + " Case File ( " + Case.CASE_DOT_EXTENSION + ")", + Case.CASE_EXTENSION); fc.setDragEnabled(false); fc.setFileSelectionMode(JFileChooser.FILES_ONLY); fc.setMultiSelectionEnabled(false); - //fc.setAcceptAllFileFilterUsed(false); - fc.addChoosableFileFilter(autFilter); - //fc.addChoosableFileFilter(fc.getAcceptAllFileFilter()); - try{ - if(ModuleSettings.getConfigSetting(ModuleSettings.MAIN_SETTINGS, PROP_BASECASE) != null) - fc.setCurrentDirectory(new File(ModuleSettings.getConfigSetting("Case", PROP_BASECASE))); - } - catch(Exception e){ - + fc.setFileFilter(autFilter); + try { + if (ModuleSettings.getConfigSetting(ModuleSettings.MAIN_SETTINGS, PROP_BASECASE) != null) { + fc.setCurrentDirectory(new File(ModuleSettings.getConfigSetting("Case", PROP_BASECASE))); + } + } catch (Exception e) { } } /** * Pop-up the File Chooser to open the existing case (.aut file) - * - * @param e the action event + * + * @param e the action event */ @Override public void actionPerformed(ActionEvent e) { @@ -70,6 +73,7 @@ public final class CaseOpenAction implements ActionListener { int retval = fc.showOpenDialog((Component) e.getSource()); + if (retval == JFileChooser.APPROVE_OPTION) { String path = fc.getSelectedFile().getPath(); String dirPath = fc.getSelectedFile().getParent(); @@ -89,11 +93,10 @@ public final class CaseOpenAction implements ActionListener { try { Case.open(path); // open the case } catch (CaseActionException ex) { - JOptionPane.showMessageDialog(null, "Error: could not open the case in folder " + path - + " due to unexpected error" - , "Error", JOptionPane.ERROR_MESSAGE); + JOptionPane.showMessageDialog(null, "Error: could not open the case in folder " + path + + ": " + ex.getMessage(), "Error", JOptionPane.ERROR_MESSAGE); logger.log(Level.WARNING, "Error opening case in folder " + path, ex); - + StartupWindow.getInstance().open(); } } diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/GeneralFilter.java b/Core/src/org/sleuthkit/autopsy/casemodule/GeneralFilter.java index 16620d9a07..b550a0a275 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/GeneralFilter.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/GeneralFilter.java @@ -28,10 +28,11 @@ import javax.swing.filechooser.FileFilter; */ public class GeneralFilter extends FileFilter{ - List extensions; - String desc; + private List extensions; + private String desc; public GeneralFilter(List ext, String desc){ + super(); this.extensions = ext; this.desc = desc; } diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/NewCaseVisualPanel1.java b/Core/src/org/sleuthkit/autopsy/casemodule/NewCaseVisualPanel1.java index 0ce1e09ddb..fd399a59ce 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/NewCaseVisualPanel1.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/NewCaseVisualPanel1.java @@ -179,7 +179,7 @@ final class NewCaseVisualPanel1 extends JPanel implements DocumentListener{ //fc.setSelectedFile(new File("C:\\Program Files\\")); //disableTextField(fc); // disable all the text field on the file chooser - int returnValue = fc.showSaveDialog((Component)evt.getSource()); + int returnValue = fc.showDialog((Component)evt.getSource(), "Select"); if(returnValue == JFileChooser.APPROVE_OPTION){ String path = fc.getSelectedFile().getPath(); caseParentDirTextField.setText(path); // put the path to the textfield diff --git a/Core/src/org/sleuthkit/autopsy/core/Installer.java b/Core/src/org/sleuthkit/autopsy/core/Installer.java index 584c1700e4..fc791743fc 100644 --- a/Core/src/org/sleuthkit/autopsy/core/Installer.java +++ b/Core/src/org/sleuthkit/autopsy/core/Installer.java @@ -101,6 +101,14 @@ public class Installer extends ModuleInstall { } + /** + * Check if JavaFx initialized + * @return false if java fx not initialized (classes coult not load), true if initialized + */ + public boolean isJavaFxInited() { + return this.javaFxInit; + } + private void initJavaFx() { //initialize java fx if exists try { @@ -118,7 +126,7 @@ public class Installer extends ModuleInstall { final String details = " Some features will not be available. " + " Check that you have the right JRE installed (Sun JRE > 1.7.10). "; logger.log(Level.SEVERE, msg - + details); + + details, e); WindowManager.getDefault().invokeWhenUIReady(new Runnable() { @Override diff --git a/Core/src/org/sleuthkit/autopsy/corecomponents/Bundle.properties b/Core/src/org/sleuthkit/autopsy/corecomponents/Bundle.properties index 7429c69b37..82b35ed90b 100644 --- a/Core/src/org/sleuthkit/autopsy/corecomponents/Bundle.properties +++ b/Core/src/org/sleuthkit/autopsy/corecomponents/Bundle.properties @@ -76,8 +76,6 @@ DataContentViewerArtifact.selectAllMenuItem.text=Select All DataContentViewerArtifact.pageLabel.text=Result: AdvancedConfigurationDialog.applyButton.text=OK -DataContentViewerMedia.pauseButton.text=\u25ba -DataContentViewerMedia.progressLabel.text= DataContentViewerString.goToPageLabel.text=Go to Page: DataContentViewerString.goToPageTextField.text= DataContentViewerHex.goToPageTextField.text= @@ -104,3 +102,7 @@ AdvancedConfigurationDialog.cancelButton.text=Cancel DataResultPanel.directoryTablePath.text=directoryPath DataResultPanel.numberMatchLabel.text=0 DataResultPanel.matchLabel.text=Results +MediaViewVideoPanel.pauseButton.text=\u25ba +MediaViewVideoPanel.progressLabel.text=00:00 +DataContentViewerMedia.AccessibleContext.accessibleDescription= +MediaViewVideoPanel.infoLabel.text=info diff --git a/Core/src/org/sleuthkit/autopsy/corecomponents/DataContentTopComponent.form b/Core/src/org/sleuthkit/autopsy/corecomponents/DataContentTopComponent.form index 1bad719e67..cef977f0c2 100644 --- a/Core/src/org/sleuthkit/autopsy/corecomponents/DataContentTopComponent.form +++ b/Core/src/org/sleuthkit/autopsy/corecomponents/DataContentTopComponent.form @@ -11,25 +11,10 @@ + - - - - - - - - - - - + + - - - - - - - diff --git a/Core/src/org/sleuthkit/autopsy/corecomponents/DataContentTopComponent.java b/Core/src/org/sleuthkit/autopsy/corecomponents/DataContentTopComponent.java index fde97d8b7a..4091980ab2 100644 --- a/Core/src/org/sleuthkit/autopsy/corecomponents/DataContentTopComponent.java +++ b/Core/src/org/sleuthkit/autopsy/corecomponents/DataContentTopComponent.java @@ -47,6 +47,8 @@ public final class DataContentTopComponent extends TopComponent implements DataC private static DataContentTopComponent defaultInstance; // set to true if this is the TC that always stays open and is the default place to display content private boolean isDefault; + // the content panel holding tabs with content viewers + private final DataContentPanel dataContentPanel; // contains a list of the undocked TCs private static ArrayList newWindowList = new ArrayList(); @@ -60,6 +62,10 @@ public final class DataContentTopComponent extends TopComponent implements DataC setToolTipText(TOOLTIP_TEXT); this.isDefault = isDefault; + + dataContentPanel = new DataContentPanel(isDefault); + add(dataContentPanel); + putClientProperty(TopComponent.PROP_CLOSING_DISABLED, Boolean.valueOf(isDefault)); // prevent option to close compoment in GUI logger.log(Level.INFO, "Created DataContentTopComponent instance: " + this); } @@ -90,21 +96,9 @@ public final class DataContentTopComponent extends TopComponent implements DataC // //GEN-BEGIN:initComponents private void initComponents() { - dataContentPanel = new DataContentPanel(true); - - javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this); - this.setLayout(layout); - layout.setHorizontalGroup( - layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addComponent(dataContentPanel, javax.swing.GroupLayout.DEFAULT_SIZE, 585, Short.MAX_VALUE) - ); - layout.setVerticalGroup( - layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addComponent(dataContentPanel, javax.swing.GroupLayout.DEFAULT_SIZE, 435, Short.MAX_VALUE) - ); + setLayout(new javax.swing.BoxLayout(this, javax.swing.BoxLayout.Y_AXIS)); }// //GEN-END:initComponents // Variables declaration - do not modify//GEN-BEGIN:variables - private org.sleuthkit.autopsy.corecomponents.DataContentPanel dataContentPanel; // End of variables declaration//GEN-END:variables /** diff --git a/Core/src/org/sleuthkit/autopsy/corecomponents/DataContentViewerMedia.form b/Core/src/org/sleuthkit/autopsy/corecomponents/DataContentViewerMedia.form index 2dc5d049de..20985cd415 100644 --- a/Core/src/org/sleuthkit/autopsy/corecomponents/DataContentViewerMedia.form +++ b/Core/src/org/sleuthkit/autopsy/corecomponents/DataContentViewerMedia.form @@ -1,6 +1,11 @@
+ + + + + @@ -13,80 +18,5 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + diff --git a/Core/src/org/sleuthkit/autopsy/corecomponents/DataContentViewerMedia.java b/Core/src/org/sleuthkit/autopsy/corecomponents/DataContentViewerMedia.java index e49dd6da87..5da28141a3 100644 --- a/Core/src/org/sleuthkit/autopsy/corecomponents/DataContentViewerMedia.java +++ b/Core/src/org/sleuthkit/autopsy/corecomponents/DataContentViewerMedia.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2011 Basis Technology Corp. + * Copyright 2011-2013 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -18,95 +18,62 @@ */ package org.sleuthkit.autopsy.corecomponents; -import com.sun.javafx.application.PlatformImpl; +import java.awt.CardLayout; import java.awt.Component; import java.awt.Dimension; -import java.awt.EventQueue; -import java.awt.image.BufferedImage; -import java.io.ByteArrayInputStream; -import java.io.IOException; -import java.io.InputStream; -import java.nio.IntBuffer; -import java.util.ArrayList; import java.util.Arrays; -import java.util.List; -import java.util.concurrent.CancellationException; -import java.util.concurrent.TimeUnit; import java.util.logging.Level; -import javafx.application.Platform; -import javafx.embed.swing.JFXPanel; -import javafx.embed.swing.SwingFXUtils; -import javafx.scene.Group; -import javafx.scene.Scene; -import javafx.scene.image.Image; -import javafx.scene.image.ImageView; import javax.imageio.ImageIO; import org.sleuthkit.autopsy.coreutils.Logger; -import javax.swing.BoxLayout; import javax.swing.SwingUtilities; -import javax.swing.SwingWorker; -import javax.swing.event.ChangeEvent; -import javax.swing.event.ChangeListener; -import org.gstreamer.*; -import org.gstreamer.elements.PlayBin2; -import org.gstreamer.elements.RGBDataSink; -import org.gstreamer.swing.VideoComponent; -import org.netbeans.api.progress.ProgressHandle; -import org.netbeans.api.progress.ProgressHandleFactory; import org.openide.nodes.Node; -import org.openide.util.Cancellable; -import org.openide.util.Exceptions; import org.openide.util.lookup.ServiceProvider; import org.openide.util.lookup.ServiceProviders; -import org.sleuthkit.autopsy.casemodule.Case; import org.sleuthkit.autopsy.corecomponentinterfaces.DataContentViewer; -import org.sleuthkit.autopsy.corelibs.ScalrWrapper; -import org.sleuthkit.autopsy.coreutils.MessageNotifyUtil; -import org.sleuthkit.autopsy.datamodel.ContentUtils; import org.sleuthkit.datamodel.AbstractFile; -import org.sleuthkit.datamodel.ReadContentInputStream; import org.sleuthkit.datamodel.TskData.TSK_FS_NAME_FLAG_ENUM; /** - * Media content viewer for videos, sounds and images. Using gstreamer. + * Media content viewer for videos, sounds and images. */ @ServiceProviders(value = { - @ServiceProvider(service = DataContentViewer.class, position = 5), - @ServiceProvider(service = FrameCapture.class) + @ServiceProvider(service = DataContentViewer.class, position = 5) }) -public class DataContentViewerMedia extends javax.swing.JPanel implements DataContentViewer, FrameCapture { +public class DataContentViewerMedia extends javax.swing.JPanel implements DataContentViewer { private String[] IMAGES; // use javafx supported - private static final String[] VIDEOS = new String[]{".mov", ".m4v", ".flv", ".mp4", ".3gp", ".avi", ".mpg", ".mpeg"}; + private static final String[] VIDEOS = new String[]{".mov", ".m4v", ".flv", ".mp4", ".3gp", ".avi", ".mpg", ".mpeg", ".wmv"}; private static final String[] AUDIOS = new String[]{".mp3", ".wav", ".wma"}; - private static final int NUM_FRAMES = 12; - private static final long MIN_FRAME_INTERVAL_MILLIS = 500; + private static final Logger logger = Logger.getLogger(DataContentViewerMedia.class.getName()); - private VideoComponent videoComponent; - private PlayBin2 playbin2; - private AbstractFile currentFile; - private long durationMillis = 0; - private boolean autoTracking = false; // true if the slider is moving automatically - private final Object playbinLock = new Object(); // lock for synchronization of playbin2 player - private VideoProgressWorker videoProgressWorker; - private int totalHours, totalMinutes, totalSeconds; - private BufferedImage currentImage = null; - private boolean gstInited = false; + private AbstractFile lastFile; - private boolean inImageMode; //keeps track if already in image mode to minimize UI setup + //UI + private final MediaViewVideoPanel videoPanel; + private final MediaViewImagePanel imagePanel; + private boolean videoPanelInited; + private boolean imagePanelInited; + + private static final String IMAGE_VIEWER_LAYER = "IMAGE"; + private static final String VIDEO_VIEWER_LAYER = "VIDEO"; /** * Creates new form DataContentViewerVideo */ public DataContentViewerMedia() { + initComponents(); + + videoPanel = new MediaViewVideoPanel(); + imagePanel = new MediaViewImagePanel(); + videoPanelInited = videoPanel.isInited(); + imagePanelInited = imagePanel.isInited(); + customizeComponents(); logger.log(Level.INFO, "Created MediaView instance: " + this); } private void customizeComponents() { - inImageMode = false; - logger.log(Level.INFO, "Supported image formats by javafx image viewer: "); //initialize supported image types //TODO use mime-types instead once we have support @@ -118,47 +85,11 @@ public class DataContentViewerMedia extends javax.swing.JPanel implements DataCo IMAGES[i] = "." + suffix; } - try { - logger.log(Level.INFO, "Initializing gstreamer for video/audio viewing"); - Gst.init(); - gstInited = true; - } catch (GstException e) { - gstInited = false; - logger.log(Level.SEVERE, "Error initializing gstreamer for audio/video viewing and frame extraction capabilities", e); - MessageNotifyUtil.Notify.error("Error initializing gstreamer for audio/video viewing and frame extraction capabilities. " - + " Video and audio viewing will be disabled. ", - e.getMessage()); - return; - } catch (UnsatisfiedLinkError | NoClassDefFoundError | Exception e) { - gstInited = false; - logger.log(Level.SEVERE, "Error initializing gstreamer for audio/video viewing and extraction capabilities", e); - MessageNotifyUtil.Notify.error("Error initializing gstreamer for audio/video viewing frame extraction capabilities. " - + " Video and audio viewing will be disabled. ", - e.getMessage()); - return; - } + add(imagePanel, IMAGE_VIEWER_LAYER); + add(videoPanel, VIDEO_VIEWER_LAYER); + + switchPanels(false); - - progressSlider.setEnabled(false); // disable slider; enable after user plays vid - progressSlider.addChangeListener(new ChangeListener() { - /** - * Should always try to synchronize any call to - * progressSlider.setValue() to avoid a different thread changing - * playbin while stateChanged() is processing - */ - @Override - public void stateChanged(ChangeEvent e) { - int time = progressSlider.getValue(); - synchronized (playbinLock) { - if (playbin2 != null && !autoTracking) { - State orig = playbin2.getState(); - playbin2.pause(); - playbin2.seek(ClockTime.fromMillis(time)); - playbin2.setState(orig); - } - } - } - }); } /** @@ -170,90 +101,17 @@ public class DataContentViewerMedia extends javax.swing.JPanel implements DataCo // //GEN-BEGIN:initComponents private void initComponents() { - pauseButton = new javax.swing.JButton(); - videoPanel = new javax.swing.JPanel(); - progressSlider = new javax.swing.JSlider(); - progressLabel = new javax.swing.JLabel(); - - pauseButton.setText(org.openide.util.NbBundle.getMessage(DataContentViewerMedia.class, "DataContentViewerMedia.pauseButton.text")); // NOI18N - pauseButton.setMaximumSize(new java.awt.Dimension(45, 23)); - pauseButton.setMinimumSize(new java.awt.Dimension(45, 23)); - pauseButton.setPreferredSize(new java.awt.Dimension(45, 23)); - pauseButton.addActionListener(new java.awt.event.ActionListener() { - public void actionPerformed(java.awt.event.ActionEvent evt) { - pauseButtonActionPerformed(evt); - } - }); - - javax.swing.GroupLayout videoPanelLayout = new javax.swing.GroupLayout(videoPanel); - videoPanel.setLayout(videoPanelLayout); - videoPanelLayout.setHorizontalGroup( - videoPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addGap(0, 0, Short.MAX_VALUE) - ); - videoPanelLayout.setVerticalGroup( - videoPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addGap(0, 242, Short.MAX_VALUE) - ); - - progressSlider.setValue(0); - - progressLabel.setHorizontalAlignment(javax.swing.SwingConstants.RIGHT); - progressLabel.setText(org.openide.util.NbBundle.getMessage(DataContentViewerMedia.class, "DataContentViewerMedia.progressLabel.text")); // NOI18N - - javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this); - this.setLayout(layout); - layout.setHorizontalGroup( - layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addComponent(videoPanel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) - .addGroup(layout.createSequentialGroup() - .addComponent(pauseButton, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) - .addComponent(progressSlider, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) - .addComponent(progressLabel, javax.swing.GroupLayout.PREFERRED_SIZE, 104, javax.swing.GroupLayout.PREFERRED_SIZE)) - ); - layout.setVerticalGroup( - layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, layout.createSequentialGroup() - .addComponent(videoPanel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) - .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.TRAILING, false) - .addComponent(pauseButton, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) - .addComponent(progressSlider, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) - .addComponent(progressLabel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))) - ); + setLayout(new java.awt.CardLayout()); + getAccessibleContext().setAccessibleDescription(org.openide.util.NbBundle.getMessage(DataContentViewerMedia.class, "DataContentViewerMedia.AccessibleContext.accessibleDescription")); // NOI18N }// //GEN-END:initComponents - - private void pauseButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_pauseButtonActionPerformed - synchronized (playbinLock) { - State state = playbin2.getState(); - if (state.equals(State.PLAYING)) { - playbin2.pause(); - pauseButton.setText("►"); - playbin2.setState(State.PAUSED); - } else if (state.equals(State.PAUSED)) { - playbin2.play(); - pauseButton.setText("||"); - playbin2.setState(State.PLAYING); - } else if (state.equals(State.READY)) { - ExtractMedia em = new ExtractMedia(currentFile, getJFile(currentFile)); - em.execute(); - em.getExtractedBytes(); - } - } - }//GEN-LAST:event_pauseButtonActionPerformed // Variables declaration - do not modify//GEN-BEGIN:variables - private javax.swing.JButton pauseButton; - private javax.swing.JLabel progressLabel; - private javax.swing.JSlider progressSlider; - private javax.swing.JPanel videoPanel; // End of variables declaration//GEN-END:variables @Override public void setNode(Node selectedNode) { - + if (selectedNode == null) { + videoPanel.reset(); return; } @@ -267,214 +125,34 @@ public class DataContentViewerMedia extends javax.swing.JPanel implements DataCo } else { lastFile = file; } - - reset(); - setComponentsVisibility(false); - - // get rid of any existing videoProgressWorker thread - if (videoProgressWorker != null) { - videoProgressWorker.cancel(true); - videoProgressWorker = null; - } - - - currentFile = file; - if (containsExt(file.getName(), IMAGES)) { - showImageFx(file); - } else if (gstInited + + videoPanel.reset(); + + final Dimension dims = DataContentViewerMedia.this.getSize(); + + if (imagePanelInited && containsExt(file.getName(), IMAGES)) { + imagePanel.showImageFx(file, dims); + this.switchPanels(false); + } else if (videoPanelInited && (containsExt(file.getName(), VIDEOS) || containsExt(file.getName(), AUDIOS))) { - inImageMode = false; - setupVideo(file); + + videoPanel.setupVideo(file, dims); + this.switchPanels(true); } } /** - * Initialize vars and display the image on the panel. + * switch to visible video or image panel * - * @param file + * @param showVideo true if video panel, false if image panel */ - private void showImageFx(final AbstractFile file) { - final String fileName = file.getName(); - - // load the image - PlatformImpl.runLater(new Runnable() { - @Override - public void run() { - if (!Case.isCaseOpen()) { - //handle in-between condition when case is being closed - //and an image was previously selected - return; - } - - Dimension dims = DataContentViewerMedia.this.getSize(); - - final InputStream inputStream = new ReadContentInputStream(file); - - final Image fxImage; - try { - //original input stream - BufferedImage bi = ImageIO.read(inputStream); - //scale image using Scalr - BufferedImage biScaled = ScalrWrapper.resizeHighQuality(bi, (int) dims.getWidth(), (int) dims.getHeight()); - //convert from awt imageto fx image - fxImage = SwingFXUtils.toFXImage(biScaled, null); - } catch (IOException ex) { - logger.log(Level.WARNING, "Could not load image file into media view: " + fileName, ex); - return; - } catch (OutOfMemoryError ex) { - logger.log(Level.WARNING, "Could not load image file into media view (too large): " + fileName, ex); - MessageNotifyUtil.Notify.warn("Could not load image file (too large): " + file.getName(), ex.getMessage()); - return; - } finally { - try { - inputStream.close(); - } catch (IOException ex) { - logger.log(Level.WARNING, "Could not close input stream after loading image in media view: " + fileName, ex); - } - } - - if (fxImage == null) { - logger.log(Level.WARNING, "Could not load image file into media view: " + fileName); - return; - } - - // simple displays ImageView the image as is - ImageView fxImageView = new ImageView(); - fxImageView.setImage(fxImage); - // resizes the image to have width of 100 while preserving the ratio and using - // higher quality filtering method; this ImageView is also cached to - // improve performance - fxImageView.setPreserveRatio(true); - fxImageView.setSmooth(true); - fxImageView.setCache(true); - fxImageView.setFitWidth(dims.getWidth()); - fxImageView.setFitHeight(dims.getHeight()); - - Group fxRoot = new Group(); - - //Scene fxScene = new Scene(fxRoot, dims.getWidth(), dims.getHeight(), javafx.scene.paint.Color.BLACK); - Scene fxScene = new Scene(fxRoot, javafx.scene.paint.Color.BLACK); - fxRoot.getChildren().add(fxImageView); - - if (inImageMode) { - final JFXPanel fxPanel = (JFXPanel) videoPanel.getComponent(0); - fxPanel.setScene(fxScene); - videoPanel.setVisible(true); - } else { - inImageMode = true; - final JFXPanel fxPanel = new JFXPanel(); - fxPanel.setScene(fxScene); - - //when done, join with the swing panel - EventQueue.invokeLater(new Runnable() { - @Override - public void run() { - //remove video panels and recreate image view panel - //TODO use swing layered pane to switch between different modes - videoPanel.removeAll(); - videoPanel.setLayout(new BoxLayout(videoPanel, BoxLayout.Y_AXIS)); - videoPanel.add(fxPanel); - videoPanel.setVisible(true); - - if (fxImage.isError()) { - logger.log(Level.WARNING, "Could not load image file into media view: " + fileName); - //MessageNotifyUtil.Message.warn("Could not load image file: " + file.getName()); - } - } - }); - } - } - }); - - - - } - - /** - * Initialize vars and display the image on the panel. - * - * @param file - * @deprecated using javafx for image display - */ - @Deprecated - private void showImageGst(AbstractFile file) { - if (!gstInited) { - return; + private void switchPanels(boolean showVideo) { + CardLayout layout = (CardLayout)this.getLayout(); + if (showVideo) { + layout.show(this, VIDEO_VIEWER_LAYER); + } else { + layout.show(this, IMAGE_VIEWER_LAYER); } - java.io.File ioFile = getJFile(file); - if (!ioFile.exists()) { - try { - ContentUtils.writeToFile(file, ioFile); - } catch (IOException ex) { - logger.log(Level.WARNING, "Error buffering file", ex); - } - } - - videoComponent = new VideoComponent(); - synchronized (playbinLock) { - if (playbin2 != null) { - playbin2.dispose(); - } - playbin2 = new PlayBin2("ImageViewer"); - playbin2.setVideoSink(videoComponent.getElement()); - } - - videoPanel.removeAll(); - videoPanel.setLayout(new BoxLayout(videoPanel, BoxLayout.Y_AXIS)); - videoPanel.add(videoComponent); - videoPanel.revalidate(); - videoPanel.repaint(); - - synchronized (playbinLock) { - playbin2.setInputFile(ioFile); - playbin2.play(); - } - videoPanel.setVisible(true); - } - - /** - * Initialize all the necessary vars to play a video/audio file. - * - * @param file the file to play - */ - private void setupVideo(AbstractFile file) { - java.io.File ioFile = getJFile(file); - - pauseButton.setText("►"); - progressSlider.setValue(0); - - videoComponent = new VideoComponent(); - synchronized (playbinLock) { - if (playbin2 != null) { - playbin2.dispose(); - } - playbin2 = new PlayBin2("VideoPlayer"); - playbin2.setVideoSink(videoComponent.getElement()); - } - - videoPanel.removeAll(); - videoPanel.setLayout(new BoxLayout(videoPanel, BoxLayout.Y_AXIS)); - videoPanel.add(videoComponent); - videoPanel.revalidate(); - videoPanel.repaint(); - - synchronized (playbinLock) { - playbin2.setInputFile(ioFile); - playbin2.setState(State.READY); - } - setComponentsVisibility(true); - } - - /** - * To set the visibility of specific components in this class. - * - * @param isVisible whether to show or hide the specific components - */ - private void setComponentsVisibility(boolean isVisible) { - pauseButton.setVisible(isVisible); - progressLabel.setVisible(isVisible); - progressSlider.setVisible(isVisible); - videoPanel.setVisible(isVisible); } @Override @@ -484,7 +162,7 @@ public class DataContentViewerMedia extends javax.swing.JPanel implements DataCo @Override public String getToolTip() { - return "Displays supported multimedia files"; + return "Displays supported multimedia files (images, videos, audio)"; } @Override @@ -503,50 +181,20 @@ public class DataContentViewerMedia extends javax.swing.JPanel implements DataCo // because we already reset on each selected node } - private void reset() { - // reset the progress label text on the event dispatch thread - SwingUtilities.invokeLater(new Runnable() { - @Override - public void run() { - progressLabel.setText(""); - } - }); - - - if (!gstInited) { - return; - } - - synchronized (playbinLock) { - if (playbin2 != null) { - if (playbin2.isPlaying()) { - playbin2.stop(); - } - playbin2.setState(State.NULL); - if (playbin2.getState().equals(State.NULL)) { - playbin2.dispose(); - } - playbin2 = null; - } - videoComponent = null; - } - } + @Override public boolean isSupported(Node node) { + if (node == null) { return false; } - + AbstractFile file = node.getLookup().lookup(AbstractFile.class); if (file == null) { return false; } - //try displaying deleted files if we can read them - //if (file.isDirNameFlagSet(TSK_FS_NAME_FLAG_ENUM.UNALLOC)) { - // return false; - //} if (file.getSize() == 0) { return false; @@ -554,15 +202,13 @@ public class DataContentViewerMedia extends javax.swing.JPanel implements DataCo String name = file.getName().toLowerCase(); - boolean deleted = file.isDirNameFlagSet(TSK_FS_NAME_FLAG_ENUM.UNALLOC); - - if (containsExt(name, IMAGES)) { + if (imagePanelInited && containsExt(name, IMAGES)) { return true; } //for gstreamer formats, check if initialized first, then - //support audio formats, and video formats if undeleted file - else if (gstInited + //support audio formats, and video formats + else if (videoPanelInited && videoPanel.isInited() && (containsExt(name, AUDIOS) - || (!deleted && containsExt(name, VIDEOS)))) { + || (containsExt(name, VIDEOS)))) { return true; } @@ -572,7 +218,19 @@ public class DataContentViewerMedia extends javax.swing.JPanel implements DataCo @Override public int isPreferred(Node node, boolean isSupported) { if (isSupported) { - return 7; + //special case, check if deleted video, then do not make it preferred + AbstractFile file = node.getLookup().lookup(AbstractFile.class); + if (file == null) { + return 0; + } + String name = file.getName().toLowerCase(); + + boolean deleted = file.isDirNameFlagSet(TSK_FS_NAME_FLAG_ENUM.UNALLOC); + if (containsExt(name, VIDEOS) && deleted) { + return 0; + } else { + return 7; + } } else { return 0; } @@ -586,274 +244,13 @@ public class DataContentViewerMedia extends javax.swing.JPanel implements DataCo } return Arrays.asList(exts).contains(ext); } - - private java.io.File getJFile(AbstractFile file) { - // Get the temp folder path of the case - String tempPath = Case.getCurrentCase().getTempDirectory(); - String name = file.getName(); - int extStart = name.lastIndexOf("."); - String ext = ""; - if (extStart != -1) { - ext = name.substring(extStart, name.length()).toLowerCase(); - } - tempPath = tempPath + java.io.File.separator + file.getId() + ext; - - java.io.File tempFile = new java.io.File(tempPath); - return tempFile; - } - - @Override - public List captureFrames(java.io.File file, int numFrames) { - - List frames = new ArrayList<>(); - - if (!gstInited) { - return frames; - } - - RGBDataSink.Listener listener1 = new RGBDataSink.Listener() { - @Override - public void rgbFrame(boolean bln, int w, int h, IntBuffer rgbPixels) { - BufferedImage curImage = new BufferedImage(w, h, - BufferedImage.TYPE_INT_ARGB); - curImage.setRGB(0, 0, w, h, rgbPixels.array(), 0, w); - currentImage = curImage; - } - }; - - // set up a PlayBin2 object - RGBDataSink videoSink = new RGBDataSink("rgb", listener1); - PlayBin2 playbin = new PlayBin2("VideoFrameCapture"); - playbin.setInputFile(file); - playbin.setVideoSink(videoSink); - - // this is necessary to get a valid duration value - playbin.play(); - playbin.pause(); - playbin.getState(); - - // get the duration of the video - TimeUnit unit = TimeUnit.MILLISECONDS; - long myDurationMillis = playbin.queryDuration(unit); - if (myDurationMillis <= 0) { - return frames; - } - - // create a list of timestamps at which to get frames - int numFramesToGet = numFrames; - long frameInterval = myDurationMillis / numFrames; - if (frameInterval < MIN_FRAME_INTERVAL_MILLIS) { - numFramesToGet = 1; - } - - // for each timeStamp, grap a frame - for (int i = 0; i < numFramesToGet; ++i) { - long timeStamp = i * frameInterval; - - playbin.pause(); - playbin.getState(); - - currentImage = null; - if (!playbin.seek(timeStamp, unit)) { - logger.log(Level.INFO, "There was a problem seeking to " + timeStamp + " " + unit.name().toLowerCase()); - } - playbin.play(); - - while (currentImage == null) { - System.out.flush(); // not sure why this is needed - } - - playbin.stop(); - - frames.add(new VideoFrame(currentImage, timeStamp)); - } - - return frames; - } - - /* Thread that extracts and plays a file */ - private class ExtractMedia extends SwingWorker { - - private ProgressHandle progress; - boolean success = false; - private AbstractFile sFile; - private java.io.File jFile; - private String duration; - private String position; - private long extractedBytes; - - ExtractMedia(org.sleuthkit.datamodel.AbstractFile sFile, java.io.File jFile) { - this.sFile = sFile; - this.jFile = jFile; - } - - public long getExtractedBytes() { - return extractedBytes; - } - - @Override - protected Object doInBackground() throws Exception { - success = false; - progress = ProgressHandleFactory.createHandle("Buffering " + sFile.getName(), new Cancellable() { - @Override - public boolean cancel() { - return ExtractMedia.this.cancel(true); - } - }); - progressLabel.setText("Buffering... "); - progress.start(); - progress.switchToDeterminate(100); - try { - extractedBytes = ContentUtils.writeToFile(sFile, jFile, progress, this, true); - } catch (IOException ex) { - logger.log(Level.WARNING, "Error buffering file", ex); - } - success = true; - return null; - } - - /* clean up or start the worker threads */ - @Override - protected void done() { - try { - super.get(); //block and get all exceptions thrown while doInBackground() - } catch (CancellationException ex) { - logger.log(Level.INFO, "Media buffering was canceled."); - } catch (InterruptedException ex) { - logger.log(Level.INFO, "Media buffering was interrupted."); - } catch (Exception ex) { - logger.log(Level.SEVERE, "Fatal error during media buffering.", ex); - } finally { - progress.finish(); - if (!this.isCancelled()) { - play(); - } - } - } - - private void play() { - if (jFile == null || !jFile.exists()) { - progressLabel.setText("Error buffering file"); - return; - } - ClockTime dur = null; - synchronized (playbinLock) { - playbin2.play(); // must play, then pause and get state to get duration. - playbin2.pause(); - playbin2.getState(); - dur = playbin2.queryDuration(); - } - duration = dur.toString(); - durationMillis = dur.toMillis(); - - // pick out the total hours, minutes, seconds - long durationSeconds = (int) durationMillis / 1000; - totalHours = (int) durationSeconds / 3600; - durationSeconds -= totalHours * 3600; - totalMinutes = (int) durationSeconds / 60; - durationSeconds -= totalMinutes * 60; - totalSeconds = (int) durationSeconds; - - SwingUtilities.invokeLater(new Runnable() { - @Override - public void run() { - progressSlider.setMaximum((int) durationMillis); - progressSlider.setMinimum(0); - - synchronized (playbinLock) { - playbin2.play(); - } - pauseButton.setText("||"); - videoProgressWorker = new VideoProgressWorker(); - videoProgressWorker.execute(); - } - }); - } - } - - private class VideoProgressWorker extends SwingWorker { - - private String durationFormat = "%02d:%02d:%02d/%02d:%02d:%02d "; - private long millisElapsed = 0; - private final long INTER_FRAME_PERIOD_MS = 20; - private final long END_TIME_MARGIN_MS = 50; - - private boolean isPlayBinReady() { - synchronized (playbinLock) { - return playbin2 != null && !playbin2.getState().equals(State.NULL); - } - } - - public void resetVideo() { - synchronized (playbinLock) { - if (playbin2 != null) { - playbin2.stop(); - playbin2.setState(State.READY); // ready to be played again - } - } - pauseButton.setText("►"); - progressSlider.setValue(0); - - String durationStr = String.format(durationFormat, 0, 0, 0, - totalHours, totalMinutes, totalSeconds); - progressLabel.setText(durationStr); - } - - /** - * @return true while millisElapsed is greater than END_TIME_MARGIN_MS - * from durationMillis. This is used to indicate when the video has - * ended because for some videos the time elapsed never becomes equal to - * the reported duration of the video. - */ - private boolean hasNotEnded() { - return (durationMillis - millisElapsed) > END_TIME_MARGIN_MS; - } - - @Override - protected Object doInBackground() throws Exception { - - // enable the slider - progressSlider.setEnabled(true); - - int elapsedHours = -1, elapsedMinutes = -1, elapsedSeconds = -1; - ClockTime pos = null; - while (hasNotEnded() && isPlayBinReady() && !isCancelled()) { - - synchronized (playbinLock) { - pos = playbin2.queryPosition(); - } - millisElapsed = pos.toMillis(); - - // pick out the elapsed hours, minutes, seconds - long secondsElapsed = millisElapsed / 1000; - elapsedHours = (int) secondsElapsed / 3600; - secondsElapsed -= elapsedHours * 3600; - elapsedMinutes = (int) secondsElapsed / 60; - secondsElapsed -= elapsedMinutes * 60; - elapsedSeconds = (int) secondsElapsed; - - String durationStr = String.format(durationFormat, - elapsedHours, elapsedMinutes, elapsedSeconds, - totalHours, totalMinutes, totalSeconds); - - progressLabel.setText(durationStr); - autoTracking = true; - progressSlider.setValue((int) millisElapsed); - autoTracking = false; - - try { - Thread.sleep(INTER_FRAME_PERIOD_MS); - } catch (InterruptedException ex) { - break; - } - } - - // disable the slider - progressSlider.setEnabled(false); - - resetVideo(); - - return null; - } - } } + + + + + + + + + diff --git a/Core/src/org/sleuthkit/autopsy/corecomponents/MediaViewImagePanel.form b/Core/src/org/sleuthkit/autopsy/corecomponents/MediaViewImagePanel.form new file mode 100644 index 0000000000..1a3af391a6 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/corecomponents/MediaViewImagePanel.form @@ -0,0 +1,25 @@ + + +
+ + + + + + + + + + + + + + + + + + + + + +
diff --git a/Core/src/org/sleuthkit/autopsy/corecomponents/MediaViewImagePanel.java b/Core/src/org/sleuthkit/autopsy/corecomponents/MediaViewImagePanel.java new file mode 100644 index 0000000000..831cdb652e --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/corecomponents/MediaViewImagePanel.java @@ -0,0 +1,209 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2013 Basis Technology Corp. + * Contact: carrier sleuthkit org + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.sleuthkit.autopsy.corecomponents; + +import com.sun.javafx.application.PlatformImpl; +import java.awt.Dimension; +import java.awt.EventQueue; +import java.awt.image.BufferedImage; +import java.io.IOException; +import java.io.InputStream; +import java.util.Collection; +import java.util.logging.Level; +import javafx.embed.swing.JFXPanel; +import javafx.embed.swing.SwingFXUtils; +import javafx.scene.Scene; +import javafx.scene.image.Image; +import javafx.scene.image.ImageView; +import javafx.scene.layout.BorderPane; +import javax.imageio.ImageIO; +import org.openide.modules.ModuleInstall; +import org.openide.util.Lookup; +import org.sleuthkit.autopsy.casemodule.Case; +import org.sleuthkit.autopsy.corelibs.ScalrWrapper; +import org.sleuthkit.autopsy.coreutils.Logger; +import org.sleuthkit.autopsy.coreutils.MessageNotifyUtil; +import org.sleuthkit.datamodel.AbstractFile; +import org.sleuthkit.datamodel.ReadContentInputStream; + +/** + * Container for the image viewer part of media view, on a layered pane. To be + * used with JavaFx image viewer only. + */ +public class MediaViewImagePanel extends javax.swing.JPanel { + + private JFXPanel fxPanel; + private ImageView fxImageView; + private static final Logger logger = Logger.getLogger(MediaViewImagePanel.class.getName()); + private boolean fxInited = false; + + /** + * Creates new form MediaViewImagePanel + */ + public MediaViewImagePanel() { + initComponents(); + + + + org.sleuthkit.autopsy.core.Installer coreInstaller = + ModuleInstall.findObject(org.sleuthkit.autopsy.core.Installer.class, false); + if (coreInstaller != null) { + fxInited = coreInstaller.isJavaFxInited(); + } + + + if (fxInited) { + setupFx(); + } + } + + public boolean isInited() { + return fxInited; + } + + /** + * Setup FX components + */ + private void setupFx() { + // load the image + PlatformImpl.runLater(new Runnable() { + @Override + public void run() { + fxPanel = new JFXPanel(); + fxImageView = new ImageView(); + // resizes the image to have width of 100 while preserving the ratio and using + // higher quality filtering method; this ImageView is also cached to + // improve performance + fxImageView.setPreserveRatio(true); + fxImageView.setSmooth(true); + fxImageView.setCache(true); + + EventQueue.invokeLater(new Runnable() { + @Override + public void run() { + add(fxPanel); + + //TODO + // setVisible(true); + } + }); + + + + } + }); + + + } + + /** + * Show image + * + * @param file image file to show + * @param dims dimension of the parent window + */ + void showImageFx(final AbstractFile file, final Dimension dims) { + if (!fxInited) { + return; + } + + final String fileName = file.getName(); + + //hide the panel during loading/transformations + fxPanel.setVisible(false); + + // load the image + PlatformImpl.runLater(new Runnable() { + @Override + public void run() { + if (!Case.isCaseOpen()) { + //handle in-between condition when case is being closed + //and an image was previously selected + return; + } + + final InputStream inputStream = new ReadContentInputStream(file); + + final Image fxImage; + try { + //original input stream + BufferedImage bi = ImageIO.read(inputStream); + //scale image using Scalr + BufferedImage biScaled = ScalrWrapper.resizeHighQuality(bi, (int) dims.getWidth(), (int) dims.getHeight()); + //convert from awt imageto fx image + fxImage = SwingFXUtils.toFXImage(biScaled, null); + } catch (IOException ex) { + logger.log(Level.WARNING, "Could not load image file into media view: " + fileName, ex); + return; + } catch (OutOfMemoryError ex) { + logger.log(Level.WARNING, "Could not load image file into media view (too large): " + fileName, ex); + MessageNotifyUtil.Notify.warn("Could not load image file (too large): " + file.getName(), ex.getMessage()); + return; + } finally { + try { + inputStream.close(); + } catch (IOException ex) { + logger.log(Level.WARNING, "Could not close input stream after loading image in media view: " + fileName, ex); + } + } + + if (fxImage == null || fxImage.isError()) { + logger.log(Level.WARNING, "Could not load image file into media view: " + fileName); + return; + } + + //use border pane to center the image in the scene + BorderPane borderpane = new BorderPane(); + borderpane.setCenter(fxImageView); + + fxImageView.setImage(fxImage); + fxImageView.setFitWidth(dims.getWidth()); + fxImageView.setFitHeight(dims.getHeight()); + + //Group fxRoot = new Group(); + + //Scene fxScene = new Scene(fxRoot, dims.getWidth(), dims.getHeight(), javafx.scene.paint.Color.BLACK); + Scene fxScene = new Scene(borderpane, javafx.scene.paint.Color.BLACK); + // borderpane.getChildren().add(fxImageView); + + fxPanel.setScene(fxScene); + + //show the panel after fully loaded + fxPanel.setVisible(true); + + } + }); + + } + + /** + * This method is called from within the constructor to initialize the form. + * WARNING: Do NOT modify this code. The content of this method is always + * regenerated by the Form Editor. + */ + @SuppressWarnings("unchecked") + // //GEN-BEGIN:initComponents + private void initComponents() { + + setBackground(new java.awt.Color(0, 0, 0)); + setLayout(new javax.swing.BoxLayout(this, javax.swing.BoxLayout.Y_AXIS)); + }// //GEN-END:initComponents + // Variables declaration - do not modify//GEN-BEGIN:variables + // End of variables declaration//GEN-END:variables +} diff --git a/Core/src/org/sleuthkit/autopsy/corecomponents/MediaViewVideoPanel.form b/Core/src/org/sleuthkit/autopsy/corecomponents/MediaViewVideoPanel.form new file mode 100644 index 0000000000..ceab29b1e8 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/corecomponents/MediaViewVideoPanel.form @@ -0,0 +1,113 @@ + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
diff --git a/Core/src/org/sleuthkit/autopsy/corecomponents/MediaViewVideoPanel.java b/Core/src/org/sleuthkit/autopsy/corecomponents/MediaViewVideoPanel.java new file mode 100644 index 0000000000..1e0b890810 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/corecomponents/MediaViewVideoPanel.java @@ -0,0 +1,646 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2013 Basis Technology Corp. + * Contact: carrier sleuthkit org + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.sleuthkit.autopsy.corecomponents; + +import java.awt.Color; +import java.awt.Dimension; +import java.awt.image.BufferedImage; +import java.io.IOException; +import java.nio.IntBuffer; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.CancellationException; +import java.util.concurrent.TimeUnit; +import java.util.logging.Level; +import javax.swing.BoxLayout; +import javax.swing.JButton; +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.JSlider; +import javax.swing.SwingUtilities; +import javax.swing.SwingWorker; +import javax.swing.event.ChangeEvent; +import javax.swing.event.ChangeListener; +import org.gstreamer.ClockTime; +import org.gstreamer.Gst; +import org.gstreamer.GstException; +import org.gstreamer.State; +import org.gstreamer.elements.PlayBin2; +import org.gstreamer.elements.RGBDataSink; +import org.gstreamer.swing.VideoComponent; +import org.netbeans.api.progress.ProgressHandle; +import org.netbeans.api.progress.ProgressHandleFactory; +import org.openide.util.Cancellable; +import org.openide.util.Exceptions; +import org.openide.util.lookup.ServiceProvider; +import org.openide.util.lookup.ServiceProviders; +import org.sleuthkit.autopsy.casemodule.Case; +import org.sleuthkit.autopsy.corecomponentinterfaces.DataContentViewer; +import org.sleuthkit.autopsy.coreutils.Logger; +import org.sleuthkit.autopsy.coreutils.MessageNotifyUtil; +import org.sleuthkit.autopsy.datamodel.ContentUtils; +import org.sleuthkit.datamodel.AbstractFile; +import org.sleuthkit.datamodel.TskCoreException; +import org.sleuthkit.datamodel.TskData; + +/** + * Video viewer part of the Media View layered pane + */ +@ServiceProviders(value = { + @ServiceProvider(service = FrameCapture.class) +}) +public class MediaViewVideoPanel extends javax.swing.JPanel implements FrameCapture { + + private static final Logger logger = Logger.getLogger(MediaViewVideoPanel.class.getName()); + private boolean gstInited; + //frame capture + private BufferedImage currentImage = null; + private static final long MIN_FRAME_INTERVAL_MILLIS = 500; + //playback + private long durationMillis = 0; + private VideoProgressWorker videoProgressWorker; + private int totalHours, totalMinutes, totalSeconds; + private volatile PlayBin2 gstPlaybin2; + private VideoComponent gstVideoComponent; + private boolean autoTracking = false; // true if the slider is moving automatically + private final Object playbinLock = new Object(); // lock for synchronization of gstPlaybin2 player + private AbstractFile currentFile; + + /** + * Creates new form MediaViewVideoPanel + */ + public MediaViewVideoPanel() { + initComponents(); + customizeComponents(); + } + + public JButton getPauseButton() { + return pauseButton; + } + + public JLabel getProgressLabel() { + return progressLabel; + } + + public JSlider getProgressSlider() { + return progressSlider; + } + + public JPanel getVideoPanel() { + return videoPanel; + } + + public VideoComponent getVideoComponent() { + return gstVideoComponent; + } + + public boolean isInited() { + return gstInited; + } + + private void customizeComponents() { + initGst(); + + progressSlider.setEnabled(false); // disable slider; enable after user plays vid + progressSlider.setValue(0); + + if (gstInited) { + progressSlider.addChangeListener(new ChangeListener() { + /** + * Should always try to synchronize any call to + * progressSlider.setValue() to avoid a different thread + * changing playbin while stateChanged() is processing + */ + @Override + public void stateChanged(ChangeEvent e) { + int time = progressSlider.getValue(); + synchronized (playbinLock) { + if (gstPlaybin2 != null && !autoTracking) { + State orig = gstPlaybin2.getState(); + gstPlaybin2.pause(); + gstPlaybin2.seek(ClockTime.fromMillis(time)); + gstPlaybin2.setState(orig); + } + } + } + }); + + + } + } + + private boolean initGst() { + try { + logger.log(Level.INFO, "Initializing gstreamer for video/audio viewing"); + Gst.init(); + gstInited = true; + } catch (GstException e) { + gstInited = false; + logger.log(Level.SEVERE, "Error initializing gstreamer for audio/video viewing and frame extraction capabilities", e); + MessageNotifyUtil.Notify.error("Error initializing gstreamer for audio/video viewing and frame extraction capabilities. " + + " Video and audio viewing will be disabled. ", + e.getMessage()); + return false; + } catch (UnsatisfiedLinkError | NoClassDefFoundError | Exception e) { + gstInited = false; + logger.log(Level.SEVERE, "Error initializing gstreamer for audio/video viewing and extraction capabilities", e); + MessageNotifyUtil.Notify.error("Error initializing gstreamer for audio/video viewing frame extraction capabilities. " + + " Video and audio viewing will be disabled. ", + e.getMessage()); + return false; + } + + return true; + } + + /** + * Initialize all the necessary vars to play a video/audio file. + * + * @param file video file to play + * @param dims dimension of the parent window + */ + void setupVideo(final AbstractFile file, final Dimension dims) { + currentFile = file; + final boolean deleted = file.isDirNameFlagSet(TskData.TSK_FS_NAME_FLAG_ENUM.UNALLOC); + if (deleted) { + infoLabel.setText("Playback of deleted videos is not supported, use an external player. "); + videoPanel.removeAll(); + pauseButton.setEnabled(false); + progressSlider.setEnabled(false); + return; + } else { + try { + String path = file.getUniquePath(); + infoLabel.setText(path); + infoLabel.setToolTipText(path); + pauseButton.setEnabled(true); + progressSlider.setEnabled(true); + } catch (TskCoreException ex) { + logger.log(Level.SEVERE, "Cannot get unique path of video file"); + } + } + + java.io.File ioFile = getJFile(file); + + gstVideoComponent = new VideoComponent(); + synchronized (playbinLock) { + if (gstPlaybin2 != null) { + gstPlaybin2.dispose(); + } + gstPlaybin2 = new PlayBin2("VideoPlayer"); + gstPlaybin2.setVideoSink(gstVideoComponent.getElement()); + + videoPanel.removeAll(); + + + videoPanel.setLayout(new BoxLayout(videoPanel, BoxLayout.Y_AXIS)); + videoPanel.add(gstVideoComponent); + + videoPanel.setVisible(true); + + gstPlaybin2.setInputFile(ioFile); + gstPlaybin2.setState(State.READY); + } + + } + + void reset() { + + // reset the progress label text on the event dispatch thread + SwingUtilities.invokeLater(new Runnable() { + @Override + public void run() { + progressLabel.setText(""); + // infoLabel.setText(""); + + } + }); + + + if (!isInited()) { + return; + } + + synchronized (playbinLock) { + if (gstPlaybin2 != null) { + if (gstPlaybin2.isPlaying()) { + gstPlaybin2.stop(); + } + gstPlaybin2.setState(State.NULL); + if (gstPlaybin2.getState().equals(State.NULL)) { + gstPlaybin2.dispose(); + } + gstPlaybin2 = null; + } + gstVideoComponent = null; + //videoComponent.setBackground(Color.BLACK); + //videoComponent.repaint(); + + + //videoPanel.repaint(); + } + + // get rid of any existing videoProgressWorker thread + if (videoProgressWorker != null) { + videoProgressWorker.cancel(true); + videoProgressWorker = null; + } + + currentFile = null; + } + + private java.io.File getJFile(AbstractFile file) { + // Get the temp folder path of the case + String tempPath = Case.getCurrentCase().getTempDirectory(); + String name = file.getName(); + int extStart = name.lastIndexOf("."); + String ext = ""; + if (extStart != -1) { + ext = name.substring(extStart, name.length()).toLowerCase(); + } + tempPath = tempPath + java.io.File.separator + file.getId() + ext; + + java.io.File tempFile = new java.io.File(tempPath); + return tempFile; + } + + @Override + public List captureFrames(java.io.File file, int numFrames) { + + List frames = new ArrayList<>(); + + if (!isInited()) { + return frames; + } + + RGBDataSink.Listener listener1 = new RGBDataSink.Listener() { + @Override + public void rgbFrame(boolean bln, int w, int h, IntBuffer rgbPixels) { + BufferedImage curImage = new BufferedImage(w, h, + BufferedImage.TYPE_INT_ARGB); + curImage.setRGB(0, 0, w, h, rgbPixels.array(), 0, w); + currentImage = curImage; + } + }; + + // set up a PlayBin2 object + RGBDataSink videoSink = new RGBDataSink("rgb", listener1); + PlayBin2 playbin = new PlayBin2("VideoFrameCapture"); + playbin.setInputFile(file); + playbin.setVideoSink(videoSink); + + // this is necessary to get a valid duration value + playbin.play(); + playbin.pause(); + playbin.getState(); + + // get the duration of the video + TimeUnit unit = TimeUnit.MILLISECONDS; + long myDurationMillis = playbin.queryDuration(unit); + if (myDurationMillis <= 0) { + return frames; + } + + // create a list of timestamps at which to get frames + int numFramesToGet = numFrames; + long frameInterval = myDurationMillis / numFrames; + if (frameInterval < MIN_FRAME_INTERVAL_MILLIS) { + numFramesToGet = 1; + } + + // for each timeStamp, grap a frame + for (int i = 0; i < numFramesToGet; ++i) { + long timeStamp = i * frameInterval; + + playbin.pause(); + playbin.getState(); + + currentImage = null; + if (!playbin.seek(timeStamp, unit)) { + logger.log(Level.INFO, "There was a problem seeking to " + timeStamp + " " + unit.name().toLowerCase()); + } + playbin.play(); + + while (currentImage == null) { + System.out.flush(); // not sure why this is needed + } + + playbin.stop(); + + frames.add(new VideoFrame(currentImage, timeStamp)); + } + + return frames; + } + + /** + * This method is called from within the constructor to initialize the form. + * WARNING: Do NOT modify this code. The content of this method is always + * regenerated by the Form Editor. + */ + @SuppressWarnings("unchecked") + // //GEN-BEGIN:initComponents + private void initComponents() { + + videoPanel = new javax.swing.JPanel(); + controlPanel = new javax.swing.JPanel(); + pauseButton = new javax.swing.JButton(); + progressSlider = new javax.swing.JSlider(); + progressLabel = new javax.swing.JLabel(); + infoLabel = new javax.swing.JLabel(); + + javax.swing.GroupLayout videoPanelLayout = new javax.swing.GroupLayout(videoPanel); + videoPanel.setLayout(videoPanelLayout); + videoPanelLayout.setHorizontalGroup( + videoPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGap(0, 0, Short.MAX_VALUE) + ); + videoPanelLayout.setVerticalGroup( + videoPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGap(0, 188, Short.MAX_VALUE) + ); + + org.openide.awt.Mnemonics.setLocalizedText(pauseButton, org.openide.util.NbBundle.getMessage(MediaViewVideoPanel.class, "MediaViewVideoPanel.pauseButton.text")); // NOI18N + pauseButton.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + pauseButtonActionPerformed(evt); + } + }); + + org.openide.awt.Mnemonics.setLocalizedText(progressLabel, org.openide.util.NbBundle.getMessage(MediaViewVideoPanel.class, "MediaViewVideoPanel.progressLabel.text")); // NOI18N + + org.openide.awt.Mnemonics.setLocalizedText(infoLabel, org.openide.util.NbBundle.getMessage(MediaViewVideoPanel.class, "MediaViewVideoPanel.infoLabel.text")); // NOI18N + + javax.swing.GroupLayout controlPanelLayout = new javax.swing.GroupLayout(controlPanel); + controlPanel.setLayout(controlPanelLayout); + controlPanelLayout.setHorizontalGroup( + controlPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(controlPanelLayout.createSequentialGroup() + .addComponent(pauseButton) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(progressSlider, javax.swing.GroupLayout.DEFAULT_SIZE, 357, Short.MAX_VALUE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(progressLabel) + .addContainerGap()) + .addGroup(controlPanelLayout.createSequentialGroup() + .addComponent(infoLabel) + .addGap(0, 0, Short.MAX_VALUE)) + ); + controlPanelLayout.setVerticalGroup( + controlPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(controlPanelLayout.createSequentialGroup() + .addGroup(controlPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(pauseButton) + .addComponent(progressSlider, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addComponent(progressLabel, javax.swing.GroupLayout.Alignment.TRAILING)) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(infoLabel)) + ); + + javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this); + this.setLayout(layout); + layout.setHorizontalGroup( + layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(videoPanel, javax.swing.GroupLayout.Alignment.TRAILING, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addComponent(controlPanel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + ); + layout.setVerticalGroup( + layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(layout.createSequentialGroup() + .addComponent(videoPanel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(controlPanel, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addContainerGap()) + ); + }// //GEN-END:initComponents + + private void pauseButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_pauseButtonActionPerformed + synchronized (playbinLock) { + State state = gstPlaybin2.getState(); + if (state.equals(State.PLAYING)) { + gstPlaybin2.pause(); + pauseButton.setText("►"); + gstPlaybin2.setState(State.PAUSED); + } else if (state.equals(State.PAUSED)) { + gstPlaybin2.play(); + pauseButton.setText("||"); + gstPlaybin2.setState(State.PLAYING); + } else if (state.equals(State.READY)) { + ExtractMedia em = new ExtractMedia(currentFile, getJFile(currentFile)); + em.execute(); + em.getExtractedBytes(); + } + } + }//GEN-LAST:event_pauseButtonActionPerformed + // Variables declaration - do not modify//GEN-BEGIN:variables + private javax.swing.JPanel controlPanel; + private javax.swing.JLabel infoLabel; + private javax.swing.JButton pauseButton; + private javax.swing.JLabel progressLabel; + private javax.swing.JSlider progressSlider; + private javax.swing.JPanel videoPanel; + // End of variables declaration//GEN-END:variables + + private class VideoProgressWorker extends SwingWorker { + + private String durationFormat = "%02d:%02d:%02d/%02d:%02d:%02d "; + private long millisElapsed = 0; + private final long INTER_FRAME_PERIOD_MS = 20; + private final long END_TIME_MARGIN_MS = 50; + + private boolean isPlayBinReady() { + synchronized (playbinLock) { + return gstPlaybin2 != null && !gstPlaybin2.getState().equals(State.NULL); + } + } + + private void resetVideo() { + synchronized (playbinLock) { + if (gstPlaybin2 != null) { + gstPlaybin2.stop(); + gstPlaybin2.setState(State.READY); // ready to be played again + gstPlaybin2.getState(); //NEW + } + } + pauseButton.setText("►"); + progressSlider.setValue(0); + + String durationStr = String.format(durationFormat, 0, 0, 0, + totalHours, totalMinutes, totalSeconds); + progressLabel.setText(durationStr); + } + + /** + * @return true while millisElapsed is greater than END_TIME_MARGIN_MS + * from durationMillis. This is used to indicate when the video has + * ended because for some videos the time elapsed never becomes equal to + * the reported duration of the video. + */ + private boolean hasNotEnded() { + return (durationMillis - millisElapsed) > END_TIME_MARGIN_MS; + } + + @Override + protected Object doInBackground() throws Exception { + + // enable the slider + progressSlider.setEnabled(true); + + int elapsedHours = -1, elapsedMinutes = -1, elapsedSeconds = -1; + ClockTime pos = null; + while (hasNotEnded() && isPlayBinReady() && !isCancelled()) { + + synchronized (playbinLock) { + pos = gstPlaybin2.queryPosition(); + } + millisElapsed = pos.toMillis(); + + // pick out the elapsed hours, minutes, seconds + long secondsElapsed = millisElapsed / 1000; + elapsedHours = (int) secondsElapsed / 3600; + secondsElapsed -= elapsedHours * 3600; + elapsedMinutes = (int) secondsElapsed / 60; + secondsElapsed -= elapsedMinutes * 60; + elapsedSeconds = (int) secondsElapsed; + + String durationStr = String.format(durationFormat, + elapsedHours, elapsedMinutes, elapsedSeconds, + totalHours, totalMinutes, totalSeconds); + + progressLabel.setText(durationStr); + autoTracking = true; + progressSlider.setValue((int) millisElapsed); + autoTracking = false; + + try { + Thread.sleep(INTER_FRAME_PERIOD_MS); + } catch (InterruptedException ex) { + break; + } + } + + // disable the slider + progressSlider.setEnabled(false); + + resetVideo(); + + return null; + } + } //end class progress worker + + /* Thread that extracts and plays a file */ + private class ExtractMedia extends SwingWorker { + + private ProgressHandle progress; + boolean success = false; + private AbstractFile sFile; + private java.io.File jFile; + private String duration; + private String position; + private long extractedBytes; + + ExtractMedia(org.sleuthkit.datamodel.AbstractFile sFile, java.io.File jFile) { + this.sFile = sFile; + this.jFile = jFile; + } + + public long getExtractedBytes() { + return extractedBytes; + } + + @Override + protected Object doInBackground() throws Exception { + success = false; + progress = ProgressHandleFactory.createHandle("Buffering " + sFile.getName(), new Cancellable() { + @Override + public boolean cancel() { + return ExtractMedia.this.cancel(true); + } + }); + progressLabel.setText("Buffering... "); + progress.start(); + progress.switchToDeterminate(100); + try { + extractedBytes = ContentUtils.writeToFile(sFile, jFile, progress, this, true); + } catch (IOException ex) { + logger.log(Level.WARNING, "Error buffering file", ex); + } + success = true; + return null; + } + + /* clean up or start the worker threads */ + @Override + protected void done() { + try { + super.get(); //block and get all exceptions thrown while doInBackground() + } catch (CancellationException ex) { + logger.log(Level.INFO, "Media buffering was canceled."); + } catch (InterruptedException ex) { + logger.log(Level.INFO, "Media buffering was interrupted."); + } catch (Exception ex) { + logger.log(Level.SEVERE, "Fatal error during media buffering.", ex); + } finally { + progress.finish(); + if (!this.isCancelled()) { + playMedia(); + } + } + } + + void playMedia() { + if (jFile == null || !jFile.exists()) { + progressLabel.setText("Error buffering file"); + return; + } + ClockTime dur = null; + synchronized (playbinLock) { + gstPlaybin2.play(); // must play, then pause and get state to get duration. + gstPlaybin2.pause(); + State state = gstPlaybin2.getState(); + dur = gstPlaybin2.queryDuration(); + } + duration = dur.toString(); + durationMillis = dur.toMillis(); + + // pick out the total hours, minutes, seconds + long durationSeconds = (int) durationMillis / 1000; + totalHours = (int) durationSeconds / 3600; + durationSeconds -= totalHours * 3600; + totalMinutes = (int) durationSeconds / 60; + durationSeconds -= totalMinutes * 60; + totalSeconds = (int) durationSeconds; + + SwingUtilities.invokeLater(new Runnable() { + @Override + public void run() { + progressSlider.setMaximum((int) durationMillis); + progressSlider.setMinimum(0); + + synchronized (playbinLock) { + gstPlaybin2.play(); + } + pauseButton.setText("||"); + videoProgressWorker = new VideoProgressWorker(); + videoProgressWorker.execute(); + } + }); + } + } +} diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/ExtractedContentChildren.java b/Core/src/org/sleuthkit/autopsy/datamodel/ExtractedContentChildren.java index 94b65a9d35..2e233d22f0 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/ExtractedContentChildren.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/ExtractedContentChildren.java @@ -27,7 +27,6 @@ import org.sleuthkit.datamodel.SleuthkitCase; /** * - * @author dfickling */ public class ExtractedContentChildren extends ChildFactory { @@ -51,6 +50,9 @@ public class ExtractedContentChildren extends ChildFactory 0) { + //send the final new data event + services.fireModuleDataEvent(new ModuleDataEvent(MODULE_NAME, BlackboardArtifact.ARTIFACT_TYPE.TSK_METADATA_EXIF)); + } //module specific cleanup due to completion here } @@ -277,6 +286,8 @@ public final class ExifParserFileIngestModule implements IngestModuleAbstractFil public void init(IngestModuleInit initContext) { services = IngestServices.getDefault(); logger.log(Level.INFO, "init() " + this.toString()); + + filesProcessed = 0; } @Override diff --git a/NEWS.txt b/NEWS.txt index 5b725ed82d..7994dd0d9d 100644 --- a/NEWS.txt +++ b/NEWS.txt @@ -13,12 +13,10 @@ Improvements: Bugfixes: - fixed memory leaks in "Add Image" -- fix bug with duplicate instances of content and result viewers hidden and possibly degrading performance +- The "media view" tab is inactive for deleted files (#165) - show error message in hex and string viewer if specific offset of a file could not be read. - file search actions not always enabled when new case is open. - fixed directory tree history being reset when tree is refreshed. -- The "media view" tab is inactive for deleted files (#165) - ---------------- VERSION 3.0.4 -------------- diff --git a/Testing/script/Setup.txt b/Test/script/Setup.txt similarity index 100% rename from Testing/script/Setup.txt rename to Test/script/Setup.txt diff --git a/Testing/script/config.xml b/Test/script/config.xml similarity index 100% rename from Testing/script/config.xml rename to Test/script/config.xml diff --git a/Testing/script/getcounts.py b/Test/script/getcounts.py similarity index 100% rename from Testing/script/getcounts.py rename to Test/script/getcounts.py diff --git a/Testing/script/regression.py b/Test/script/regression.py similarity index 95% rename from Testing/script/regression.py rename to Test/script/regression.py index 868597375a..a3aa9d8558 100644 --- a/Testing/script/regression.py +++ b/Test/script/regression.py @@ -148,9 +148,9 @@ class Args: class TestAutopsy: def __init__(self): # Paths: - self.input_dir = make_local_path("../","input") + self.input_dir = make_local_path("..","input") self.output_dir = "" - self.gold = make_local_path("../", "output", "gold") + self.gold = make_local_path("..", "output", "gold") # Logs: self.antlog_dir = "" self.common_log = "" @@ -307,7 +307,7 @@ class Database: def generate_autopsy_artifacts(self): if not self.autopsy_artifacts: - autopsy_db_file = os.path.join("./", case.output_dir, case.image_name, + autopsy_db_file = make_path(case.output_dir, case.image_name, "AutopsyTestCase", "autopsy.db") autopsy_con = sqlite3.connect(autopsy_db_file) autopsy_cur = autopsy_con.cursor() @@ -325,7 +325,7 @@ class Database: def generate_autopsy_attributes(self): if self.autopsy_attributes == 0: - autopsy_db_file = os.path.join("./", case.output_dir, case.image_name, + autopsy_db_file = make_path(case.output_dir, case.image_name, "AutopsyTestCase", "autopsy.db") autopsy_con = sqlite3.connect(autopsy_db_file) autopsy_cur = autopsy_con.cursor() @@ -335,7 +335,7 @@ class Database: def generate_autopsy_objects(self): if self.autopsy_objects == 0: - autopsy_db_file = os.path.join("./", case.output_dir, case.image_name, + autopsy_db_file = make_path(case.output_dir, case.image_name, "AutopsyTestCase", "autopsy.db") autopsy_con = sqlite3.connect(autopsy_db_file) autopsy_cur = autopsy_con.cursor() @@ -345,7 +345,7 @@ class Database: def generate_gold_artifacts(self): if not self.gold_artifacts: - gold_db_file = os.path.join("./", case.gold, case.image_name, "autopsy.db") + gold_db_file = make_path(case.gold, case.image_name, "autopsy.db") gold_con = sqlite3.connect(gold_db_file) gold_cur = gold_con.cursor() gold_cur.execute("SELECT COUNT(*) FROM blackboard_artifact_types") @@ -361,7 +361,7 @@ class Database: def generate_gold_attributes(self): if self.gold_attributes == 0: - gold_db_file = os.path.join("./", case.gold, case.image_name, "autopsy.db") + gold_db_file = make_path(case.gold, case.image_name, "autopsy.db") gold_con = sqlite3.connect(gold_db_file) gold_cur = gold_con.cursor() gold_cur.execute("SELECT COUNT(*) FROM blackboard_attributes") @@ -369,7 +369,7 @@ class Database: def generate_gold_objects(self): if self.gold_objects == 0: - gold_db_file = os.path.join("./", case.gold, case.image_name, "autopsy.db") + gold_db_file = make_path(case.gold, case.image_name, "autopsy.db") gold_con = sqlite3.connect(gold_db_file) gold_cur = gold_con.cursor() gold_cur.execute("SELECT COUNT(*) FROM tsk_objects") @@ -545,12 +545,13 @@ def run_test(image_file, count): # Now test in comparison to the gold standards if not args.gold_creation: try: - gold_path = os.path.join(case.gold) - img_gold = os.path.join(case.gold, case.image_name) - img_archive = os.path.join(case.gold, case.image_name+"-archive.zip") + gold_path = case.gold + img_gold = make_path(case.gold, case.image_name) + img_archive = make_path(case.gold, case.image_name+"-archive.zip") extrctr = zipfile.ZipFile(img_archive, 'r', compression=zipfile.ZIP_DEFLATED) extrctr.extractall(gold_path) extrctr.close + time.sleep(1) compare_to_gold_db() compare_to_gold_html() compare_errors() @@ -566,7 +567,7 @@ def run_test(image_file, count): if args.rebuild or args.gold_creation: rebuild() # Reset the case and return the tests sucessfully finished - clear_dir(make_local_path(case.output_dir, case.image_name, "AutopsyTestCase", "ModuleOutput", "keywordsearch")) + clear_dir(make_path(case.output_dir, case.image_name, "AutopsyTestCase", "ModuleOutput", "keywordsearch")) case.reset() return True @@ -578,13 +579,11 @@ def run_ant(): if dir_exists(test_case_path): shutil.rmtree(test_case_path) os.makedirs(test_case_path) - if not dir_exists(make_local_path("gold")): - os.makedirs(make_local_path("gold")) case.ant = ["ant"] case.ant.append("-v") case.ant.append("-f") # case.ant.append(case.build_path) - case.ant.append(os.path.join("..","build.xml")) + case.ant.append(os.path.join("..","..","Testing","build.xml")) case.ant.append("regression-test") case.ant.append("-l") case.ant.append(case.antlog_dir) @@ -592,7 +591,7 @@ def run_ant(): case.ant.append("-Dknown_bad_path=" + case.known_bad_path) case.ant.append("-Dkeyword_path=" + case.keyword_path) case.ant.append("-Dnsrl_path=" + case.nsrl_path) - case.ant.append("-Dgold_path=" + make_local_path(case.gold)) + case.ant.append("-Dgold_path=" + make_path(case.gold)) case.ant.append("-Dout_path=" + make_local_path(case.output_dir, case.image_name)) case.ant.append("-Dignore_unalloc=" + "%s" % args.unallocated) case.ant.append("-Dcontin_mode=" + str(args.contin)) @@ -640,12 +639,14 @@ def rebuild(): # Errors to print errors = [] # Delete the current gold standards - gold_dir = make_local_path(case.gold, case.image_name) + gold_dir = make_path(case.gold, case.image_name) clear_dir(gold_dir) - dbinpth = make_local_path(case.output_dir, case.image_name, "AutopsyTestCase", "autopsy.db") - dboutpth = make_local_path(case.gold, case.image_name, "autopsy.db") + dbinpth = make_path(case.output_dir, case.image_name, "AutopsyTestCase", "autopsy.db") + dboutpth = make_path(case.gold, case.image_name, "autopsy.db") + if not os.path.exists(gold_dir): + os.makedirs(gold_dir) copy_file(dbinpth, dboutpth) - error_pth = make_local_path(case.gold, case.image_name, case.image_name+"SortedErrors.txt") + error_pth = make_path(case.gold, case.image_name, case.image_name+"SortedErrors.txt") copy_file(case.sorted_log, error_pth) # Rebuild the HTML report htmlfolder = "" @@ -654,12 +655,12 @@ def rebuild(): htmlfolder = fs autopsy_html_path = make_local_path(case.output_dir, case.image_name, "AutopsyTestCase", "Reports", htmlfolder) - html_path = make_local_path(case.output_dir, case.image_name, + html_path = make_path(case.output_dir, case.image_name, "AutopsyTestCase", "Reports") try: - os.makedirs(os.path.join(os.getcwd(), case.gold, case.image_name, htmlfolder)) + os.makedirs(os.path.join(case.gold, case.image_name, htmlfolder)) for file in os.listdir(autopsy_html_path): - html_to = make_local_path(case.gold, case.image_name, file.replace("HTML Report", "Report")) + html_to = make_path(case.gold, case.image_name, file.replace("HTML Report", "Report")) copy_dir(get_file_in_dir(autopsy_html_path, file), html_to) except FileNotFoundException as e: errors.append(e.error) @@ -668,7 +669,7 @@ def rebuild(): errors.append(str(e) + "\n") oldcwd = os.getcwd() os.chdir(case.gold) - img_archive = make_local_path(case.image_name+"-archive.zip") + img_archive = make_path(case.image_name+"-archive.zip") img_gold = os.path.join(case.image_name) comprssr = zipfile.ZipFile(img_archive, 'w',compression=zipfile.ZIP_DEFLATED) zipdir(img_gold, comprssr) @@ -689,8 +690,8 @@ def zipdir(path, zip): # from queries while comparing def compare_to_gold_db(): # SQLITE needs unix style pathing - gold_db_file = os.path.join("./", case.gold, case.image_name, "autopsy.db") - autopsy_db_file = os.path.join("./", case.output_dir, case.image_name, + gold_db_file = make_path(case.gold, case.image_name, "autopsy.db") + autopsy_db_file = make_path(case.output_dir, case.image_name, "AutopsyTestCase", "autopsy.db") # Try to query the databases. Ignore any exceptions, the function will # return an error later on if these do fail @@ -744,13 +745,12 @@ def compare_to_gold_db(): # Using the global case's variables, compare the html report file made by # the regression test against the gold standard html report def compare_to_gold_html(): - gold_html_file = make_local_path(case.gold, case.image_name, "Report", "index.html") + gold_html_file = make_path(case.gold, case.image_name, "Report", "index.html") htmlfolder = "" - for fs in os.listdir(os.path.join(os.getcwd(),case.output_dir, case.image_name, "AutopsyTestCase", "Reports")): - if os.path.isdir(os.path.join(os.getcwd(), case.output_dir, case.image_name, "AutopsyTestCase", "Reports", fs)): + for fs in os.listdir(make_path(case.output_dir, case.image_name, "AutopsyTestCase", "Reports")): + if os.path.isdir(make_path(case.output_dir, case.image_name, "AutopsyTestCase", "Reports", fs)): htmlfolder = fs - autopsy_html_path = make_local_path(case.output_dir, case.image_name, "AutopsyTestCase", "Reports", htmlfolder, "HTML Report") #, "AutopsyTestCase", "Reports", htmlfolder) - print(autopsy_html_path) + autopsy_html_path = make_path(case.output_dir, case.image_name, "AutopsyTestCase", "Reports", htmlfolder, "HTML Report") #, "AutopsyTestCase", "Reports", htmlfolder) try: @@ -765,14 +765,14 @@ def compare_to_gold_html(): return #Find all gold .html files belonging to this case ListGoldHTML = [] - for fs in os.listdir(os.path.join(case.output_dir, case.image_name, "AutopsyTestCase", "Reports", htmlfolder)): + for fs in os.listdir(make_path(case.output_dir, case.image_name, "AutopsyTestCase", "Reports", htmlfolder)): if(fs.endswith(".html")): ListGoldHTML.append(os.path.join(case.output_dir, case.image_name, "AutopsyTestCase", "Reports", htmlfolder, fs)) #Find all new .html files belonging to this case ListNewHTML = [] - for fs in os.listdir(os.path.join(case.gold, case.image_name)): + for fs in os.listdir(make_path(case.gold, case.image_name)): if (fs.endswith(".html")): - ListNewHTML.append(os.path.join(case.gold, case.image_name, fs)) + ListNewHTML.append(make_path(case.gold, case.image_name, fs)) #ensure both reports have the same number of files and are in the same order if(len(ListGoldHTML) != len(ListNewHTML)): printerror("The reports did not have the same number of files. One of the reports may have been corrupted") @@ -870,7 +870,6 @@ def generate_common_log(): try: logs_path = make_local_path(case.output_dir, case.image_name, "logs") common_log = codecs.open(case.common_log_path, "w", "utf_8") - print(case.common_log_path) warning_log = codecs.open(case.warning_log, "w", "utf_8") common_log.write("--------------------------------------------------\n") common_log.write(case.image_name + "\n") @@ -903,7 +902,7 @@ def generate_common_log(): logging.critical(traceback.format_exc()) def compare_errors(): - gold_dir = make_local_path(case.gold, case.image_name, case.image_name + "SortedErrors.txt") + gold_dir = make_path(case.gold, case.image_name, case.image_name + "SortedErrors.txt") common_log = codecs.open(case.sorted_log, "r", "utf_8") gold_log = codecs.open(gold_dir, "r", "utf_8") gold_dat = gold_log.read() @@ -1226,7 +1225,6 @@ def generate_html(): write_html_head() try: global html - print(case.html_log) html = open(case.html_log, "a") # The image title title = "

" + case.image_name + " \ @@ -1547,7 +1545,7 @@ def wgetcwd(): # Copy the log files from Autopsy's default directory def copy_logs(): try: - log_dir = os.path.join("..","build","test","qa-functional","work","userdir0","var","log") + log_dir = os.path.join("..", "..", "Testing","build","test","qa-functional","work","userdir0","var","log") shutil.copytree(log_dir, make_local_path(case.output_dir, case.image_name, "logs")) except Exception as e: printerror("Error: Failed to copy the logs.") @@ -1578,6 +1576,7 @@ def del_dir(dir): def copy_file(ffrom, to): try : if not file_exists(ffrom): + print("hi") raise FileNotFoundException(ffrom) shutil.copy(ffrom, to) except: @@ -1727,7 +1726,7 @@ def execute_test(): global failedbool global html global attachl - case.output_dir = make_path("../", "output", "results", time.strftime("%Y.%m.%d-%H.%M.%S")) + case.output_dir = make_path("..", "output", "results", time.strftime("%Y.%m.%d-%H.%M.%S")) os.makedirs(case.output_dir) case.common_log = "AutopsyErrors.txt" case.csv = make_local_path(case.output_dir, "CSV.txt") diff --git a/Testing/test/qa-functional/src/org/sleuthkit/autopsy/testing/RegressionTest.java b/Testing/test/qa-functional/src/org/sleuthkit/autopsy/testing/RegressionTest.java index c906be081e..78acbac82c 100644 --- a/Testing/test/qa-functional/src/org/sleuthkit/autopsy/testing/RegressionTest.java +++ b/Testing/test/qa-functional/src/org/sleuthkit/autopsy/testing/RegressionTest.java @@ -40,6 +40,7 @@ import java.util.logging.Level; import java.util.logging.Logger; import javax.imageio.ImageIO; import javax.swing.JDialog; +import javax.swing.JLabel; import javax.swing.JTextField; import junit.framework.Test; import junit.framework.TestCase; @@ -301,7 +302,9 @@ public class RegressionTest extends TestCase{ new Timeout("pausing", 500).sleep(); long size = 0; java.io.File rprtfldr = new java.io.File(System.getProperty("out_path")+java.io.File.separator+"AutopsyTestCase"+java.io.File.separator+"Reports"+java.io.File.separator+"AutopsyTestCase "+datenotime+java.io.File.separator+"HTML Report"); - while(waiting) + JDialog previewDialog = JDialogOperator.waitJDialog("Progress", false, false); + JLabel waiter = JLabelOperator.waitJLabel(previewDialog, "Complete", false, false); + /*while(waiting) { new Timeout("pausing", 500).sleep(); long currsize = size; @@ -310,19 +313,15 @@ public class RegressionTest extends TestCase{ { size+=elem.getTotalSpace(); } - System.out.println("\nJAVA:"+size); - System.out.println("JAVA:"+currsize); waiting = size>currsize; - } + }*/ screenshot("Progress"); - JDialog previewDialog = JDialogOperator.waitJDialog("Progress", false, false); JDialogOperator previewDialogOperator = new JDialogOperator(previewDialog); JButtonOperator jbo2 = new JButtonOperator(previewDialogOperator, "Close"); jbo2.pushNoBlock(); new Timeout("pausing", 3000).sleep(); // Give the program a second to idle to be safe System.setProperty("ReportStr", datenotime); screenshot("Done Testing"); - System.exit(0); } public void screenshot(String name) { diff --git a/Timeline/src/org/sleuthkit/autopsy/timeline/Timeline.java b/Timeline/src/org/sleuthkit/autopsy/timeline/Timeline.java index 4357430fba..c4fa6b3a52 100644 --- a/Timeline/src/org/sleuthkit/autopsy/timeline/Timeline.java +++ b/Timeline/src/org/sleuthkit/autopsy/timeline/Timeline.java @@ -65,11 +65,8 @@ import javafx.scene.input.MouseEvent; import javafx.scene.layout.HBox; import javafx.scene.layout.VBox; import javafx.scene.paint.Color; -import javax.swing.BoxLayout; import javax.swing.JFrame; import javax.swing.JOptionPane; -import javax.swing.JPanel; -import javax.swing.JSplitPane; import javax.swing.SwingUtilities; import org.netbeans.api.progress.ProgressHandle; import org.netbeans.api.progress.ProgressHandleFactory; @@ -78,6 +75,7 @@ import org.openide.awt.ActionReference; import org.openide.awt.ActionReferences; import org.openide.awt.ActionRegistration; import org.openide.modules.InstalledFileLocator; +import org.openide.modules.ModuleInstall; import org.openide.nodes.ChildFactory; import org.openide.nodes.Children; import org.openide.nodes.Node; @@ -141,6 +139,18 @@ public class Timeline extends CallableSystemAction implements Presenter.Toolbar, private EventHandler fxMouseEnteredListener; private EventHandler fxMouseExitedListener; private SleuthkitCase skCase; + private boolean fxInited = false; + + public Timeline() { + super(); + + org.sleuthkit.autopsy.core.Installer coreInstaller = + ModuleInstall.findObject(org.sleuthkit.autopsy.core.Installer.class, false); + if (coreInstaller != null) { + fxInited = coreInstaller.isJavaFxInited(); + } + + } //Swing components and JavafX components don't play super well together //Swing components need to be initialized first, in the swing specific thread @@ -165,7 +175,7 @@ public class Timeline extends CallableSystemAction implements Presenter.Toolbar, @Override public void run() { //Making the main frame * - + mainFrame = new TimelineFrame(); mainFrame.setFrameName(Case.getCurrentCase().getName() + " - Autopsy Timeline (Beta)"); @@ -1019,7 +1029,7 @@ public class Timeline extends CallableSystemAction implements Presenter.Toolbar, @Override public boolean isEnabled() { - return Case.isCaseOpen(); + return Case.isCaseOpen() && this.fxInited; } @Override diff --git a/branding/core/core.jar/org/netbeans/core/startup/Bundle.properties b/branding/core/core.jar/org/netbeans/core/startup/Bundle.properties index 306c928fea..12f71a64e3 100644 --- a/branding/core/core.jar/org/netbeans/core/startup/Bundle.properties +++ b/branding/core/core.jar/org/netbeans/core/startup/Bundle.properties @@ -1,6 +1,6 @@ #Updated by build script -#Fri, 18 Jan 2013 11:06:14 -0500 -currentVersion=Autopsy 20130118 +#Tue, 26 Mar 2013 10:08:52 -0400 +currentVersion=Autopsy 20130326 LBL_splash_window_title=Starting Autopsy SPLASH_HEIGHT=288 SPLASH_WIDTH=538 diff --git a/branding/modules/org-netbeans-core-windows.jar/org/netbeans/core/windows/view/ui/Bundle.properties b/branding/modules/org-netbeans-core-windows.jar/org/netbeans/core/windows/view/ui/Bundle.properties index 7b2239be66..8bdd341dbd 100644 --- a/branding/modules/org-netbeans-core-windows.jar/org/netbeans/core/windows/view/ui/Bundle.properties +++ b/branding/modules/org-netbeans-core-windows.jar/org/netbeans/core/windows/view/ui/Bundle.properties @@ -1,4 +1,4 @@ #Updated by build script -#Fri, 18 Jan 2013 11:06:14 -0500 -CTL_MainWindow_Title=Autopsy 20130118 -CTL_MainWindow_Title_No_Project=Autopsy 20130118 +#Tue, 26 Mar 2013 10:08:52 -0400 +CTL_MainWindow_Title=Autopsy 20130326 +CTL_MainWindow_Title_No_Project=Autopsy 20130326