class LocalStorageTest(unittest.TestCase):
    def setUp(self):
        import tempfile
        self.basefolder = os.path.realpath(os.path.abspath(tempfile.mkdtemp()))
        self.storage = LocalFileStorage(self.basefolder)

        # mock file manager module
        self.filemanager_patcher = mock.patch("octoprint.filemanager")
        self.filemanager = self.filemanager_patcher.start()

        self.filemanager.valid_file_type.return_value = True

        def get_file_type(name):
            if name.lower().endswith(".stl"):
                return ["model", "stl"]
            elif name.lower().endswith(".gco") or name.lower().endswith(
                    ".gcode") or name.lower.endswith(".g"):
                return ["machinecode", "gcode"]
            else:
                return None

        self.filemanager.get_file_type.side_effect = get_file_type

    def tearDown(self):
        import shutil
        shutil.rmtree(self.basefolder)

        self.filemanager_patcher.stop()

    def test_add_file(self):
        self._add_and_verify_file("bp_case.stl", "bp_case.stl",
                                  FILE_BP_CASE_STL)

    def test_add_file_overwrite(self):
        self._add_and_verify_file("bp_case.stl", "bp_case.stl",
                                  FILE_BP_CASE_STL)

        try:
            self._add_and_verify_file("bp_case.stl",
                                      "bp_case.stl",
                                      FILE_BP_CASE_STL,
                                      overwrite=False)
        except:
            pass

        self._add_and_verify_file("bp_case.stl",
                                  "bp_case.stl",
                                  FILE_BP_CASE_STL,
                                  overwrite=True)

    def test_add_file_with_web(self):
        import time
        href = "http://www.example.com"
        retrieved = time.time()

        stl_name = self._add_and_verify_file("bp_case.stl",
                                             "bp_case.stl",
                                             FILE_BP_CASE_STL,
                                             links=[("web",
                                                     dict(href=href,
                                                          retrieved=retrieved))
                                                    ])
        stl_metadata = self.storage.get_metadata(stl_name)

        self.assertIsNotNone(stl_metadata)
        self.assertEqual(1, len(stl_metadata["links"]))
        link = stl_metadata["links"][0]
        self.assertTrue("web", link["rel"])
        self.assertTrue("href" in link)
        self.assertEqual(href, link["href"])
        self.assertTrue("retrieved" in link)
        self.assertEqual(retrieved, link["retrieved"])

    def test_add_file_with_association(self):
        stl_name = self._add_and_verify_file("bp_case.stl", "bp_case.stl",
                                             FILE_BP_CASE_STL)
        gcode_name = self._add_and_verify_file("bp_case.gcode",
                                               "bp_case.gcode",
                                               FILE_BP_CASE_GCODE,
                                               links=[("model",
                                                       dict(name=stl_name))])

        stl_metadata = self.storage.get_metadata(stl_name)
        gcode_metadata = self.storage.get_metadata(gcode_name)

        # forward link
        self.assertEqual(1, len(gcode_metadata["links"]))
        link = gcode_metadata["links"][0]
        self.assertEqual("model", link["rel"])
        self.assertTrue("name" in link)
        self.assertEqual(stl_name, link["name"])
        self.assertTrue("hash" in link)
        self.assertEqual(FILE_BP_CASE_STL.hash, link["hash"])

        # reverse link
        self.assertEqual(1, len(stl_metadata["links"]))
        link = stl_metadata["links"][0]
        self.assertEqual("machinecode", link["rel"])
        self.assertTrue("name" in link)
        self.assertEqual(gcode_name, link["name"])
        self.assertTrue("hash" in link)
        self.assertEqual(FILE_BP_CASE_GCODE.hash, link["hash"])

    def test_remove_file(self):
        stl_name = self._add_and_verify_file("bp_case.stl", "bp_case.stl",
                                             FILE_BP_CASE_STL)
        gcode_name = self._add_and_verify_file("bp_case.gcode",
                                               "bp_case.gcode",
                                               FILE_BP_CASE_GCODE,
                                               links=[("model",
                                                       dict(name=stl_name))])

        stl_metadata = self.storage.get_metadata(stl_name)
        gcode_metadata = self.storage.get_metadata(gcode_name)

        self.assertIsNotNone(stl_metadata)
        self.assertIsNotNone(gcode_metadata)

        self.storage.remove_file(stl_name)
        self.assertFalse(
            os.path.exists(os.path.join(self.basefolder, stl_name)))

        stl_metadata = self.storage.get_metadata(stl_name)
        gcode_metadata = self.storage.get_metadata(gcode_name)

        self.assertIsNone(stl_metadata)
        self.assertIsNotNone(gcode_metadata)

        self.assertEqual(0, len(gcode_metadata["links"]))

    def test_copy_file(self):
        self._add_file("bp_case.stl", FILE_BP_CASE_STL)
        self._add_folder("test")

        self.assertTrue(
            os.path.isfile(os.path.join(self.basefolder, "bp_case.stl")))
        self.assertTrue(os.path.isdir(os.path.join(self.basefolder, "test")))

        self.storage.copy_file("bp_case.stl", "test/copied.stl")

        self.assertTrue(
            os.path.isfile(os.path.join(self.basefolder, "bp_case.stl")))
        self.assertTrue(
            os.path.isfile(os.path.join(self.basefolder, "test",
                                        "copied.stl")))

        stl_metadata = self.storage.get_metadata("bp_case.stl")
        copied_metadata = self.storage.get_metadata("test/copied.stl")

        self.assertIsNotNone(stl_metadata)
        self.assertIsNotNone(copied_metadata)
        self.assertDictEqual(stl_metadata, copied_metadata)

    def test_move_file(self):
        self._add_file("bp_case.stl", FILE_BP_CASE_STL)
        self._add_folder("test")

        self.assertTrue(
            os.path.isfile(os.path.join(self.basefolder, "bp_case.stl")))
        self.assertTrue(os.path.isdir(os.path.join(self.basefolder, "test")))

        before_stl_metadata = self.storage.get_metadata("bp_case.stl")

        self.storage.move_file("bp_case.stl", "test/copied.stl")

        self.assertFalse(
            os.path.isfile(os.path.join(self.basefolder, "bp_case.stl")))
        self.assertTrue(
            os.path.isfile(os.path.join(self.basefolder, "test",
                                        "copied.stl")))

        after_stl_metadata = self.storage.get_metadata("bp_case.stl")
        copied_metadata = self.storage.get_metadata("test/copied.stl")

        self.assertIsNotNone(before_stl_metadata)
        self.assertIsNone(after_stl_metadata)
        self.assertIsNotNone(copied_metadata)
        self.assertDictEqual(before_stl_metadata, copied_metadata)

    @data("copy_file", "move_file")
    def test_copy_move_file_missing_source(self, operation):
        try:
            getattr(self.storage, operation)("bp_case.stl", "test/copied.stl")
            self.fail("Expected an exception")
        except StorageError as e:
            self.assertEqual(e.code, StorageError.INVALID_SOURCE)

    @data("copy_file", "move_file")
    def test_copy_move_file_missing_destination_folder(self, operation):
        self._add_file("bp_case.stl", FILE_BP_CASE_STL)

        try:
            getattr(self.storage, operation)("bp_case.stl", "test/copied.stl")
            self.fail("Expected an exception")
        except StorageError as e:
            self.assertEqual(e.code, StorageError.INVALID_DESTINATION)

    @data("copy_file", "move_file")
    def test_copy_move_file_existing_destination_path(self, operation):
        self._add_file("bp_case.stl", FILE_BP_CASE_STL)
        self._add_folder("test")
        self._add_file("test/crazyradio.stl", FILE_CRAZYRADIO_STL)

        try:
            getattr(self.storage, operation)("bp_case.stl",
                                             "test/crazyradio.stl")
            self.fail("Expected an exception")
        except StorageError as e:
            self.assertEqual(e.code, StorageError.INVALID_DESTINATION)

    def test_add_folder(self):
        self._add_and_verify_folder("test", "test")

    def test_add_subfolder(self):
        folder_name = self._add_and_verify_folder("folder with some spaces",
                                                  "folder_with_some_spaces")
        subfolder_name = self._add_and_verify_folder(
            (folder_name, "subfolder"), folder_name + "/subfolder")
        stl_name = self._add_and_verify_file((subfolder_name, "bp_case.stl"),
                                             subfolder_name + "/bp_case.stl",
                                             FILE_BP_CASE_STL)

        self.assertTrue(
            os.path.exists(os.path.join(self.basefolder, folder_name)))
        self.assertTrue(
            os.path.exists(os.path.join(self.basefolder, subfolder_name)))
        self.assertTrue(os.path.exists(os.path.join(self.basefolder,
                                                    stl_name)))

    def test_remove_folder(self):
        content_folder = self._add_and_verify_folder("content", "content")
        other_stl_name = self._add_and_verify_file(
            (content_folder, "crazyradio.stl"),
            content_folder + "/crazyradio.stl", FILE_CRAZYRADIO_STL)

        empty_folder = self._add_and_verify_folder("empty", "empty")

        try:
            self.storage.remove_folder(content_folder, recursive=False)
        except:
            self.assertTrue(
                os.path.exists(os.path.join(self.basefolder, content_folder)))
            self.assertTrue(
                os.path.isdir(os.path.join(self.basefolder, content_folder)))
            self.assertTrue(
                os.path.exists(os.path.join(self.basefolder, other_stl_name)))
            self.assertIsNotNone(self.storage.get_metadata(other_stl_name))

        self.storage.remove_folder(content_folder, recursive=True)
        self.assertFalse(
            os.path.exists(os.path.join(self.basefolder, content_folder)))
        self.assertFalse(
            os.path.isdir(os.path.join(self.basefolder, content_folder)))

        self.storage.remove_folder(empty_folder, recursive=False)
        self.assertFalse(
            os.path.exists(os.path.join(self.basefolder, empty_folder)))
        self.assertFalse(
            os.path.isdir(os.path.join(self.basefolder, empty_folder)))

    def test_remove_folder_with_metadata(self):
        content_folder = self._add_and_verify_folder("content", "content")
        other_stl_name = self._add_and_verify_file(
            (content_folder, "crazyradio.stl"),
            content_folder + "/crazyradio.stl", FILE_CRAZYRADIO_STL)
        self.storage.remove_file(other_stl_name)

        self.storage.remove_folder(content_folder, recursive=False)

    def test_copy_folder(self):
        self._add_folder("source")
        self._add_folder("destination")
        self._add_file("source/crazyradio.stl", FILE_CRAZYRADIO_STL)

        source_metadata = self.storage.get_metadata("source/crazyradio.stl")
        self.storage.copy_folder("source", "destination/copied")
        copied_metadata = self.storage.get_metadata(
            "destination/copied/crazyradio.stl")

        self.assertTrue(os.path.isdir(os.path.join(self.basefolder, "source")))
        self.assertTrue(
            os.path.isfile(
                os.path.join(self.basefolder, "source", "crazyradio.stl")))
        self.assertTrue(
            os.path.isdir(os.path.join(self.basefolder, "destination")))
        self.assertTrue(
            os.path.isdir(
                os.path.join(self.basefolder, "destination", "copied")))
        self.assertTrue(
            os.path.isfile(
                os.path.join(self.basefolder, "destination", "copied",
                             ".metadata.yaml")))
        self.assertTrue(
            os.path.isfile(
                os.path.join(self.basefolder, "destination", "copied",
                             "crazyradio.stl")))

        self.assertIsNotNone(source_metadata)
        self.assertIsNotNone(copied_metadata)
        self.assertDictEqual(source_metadata, copied_metadata)

    def test_move_folder(self):
        self._add_folder("source")
        self._add_folder("destination")
        self._add_file("source/crazyradio.stl", FILE_CRAZYRADIO_STL)

        before_source_metadata = self.storage.get_metadata(
            "source/crazyradio.stl")
        self.storage.move_folder("source", "destination/copied")
        after_source_metadata = self.storage.get_metadata(
            "source/crazyradio.stl")
        copied_metadata = self.storage.get_metadata(
            "destination/copied/crazyradio.stl")

        self.assertFalse(os.path.isdir(os.path.join(self.basefolder,
                                                    "source")))
        self.assertFalse(
            os.path.isfile(
                os.path.join(self.basefolder, "source", "crazyradio.stl")))
        self.assertTrue(
            os.path.isdir(os.path.join(self.basefolder, "destination")))
        self.assertTrue(
            os.path.isdir(
                os.path.join(self.basefolder, "destination", "copied")))
        self.assertTrue(
            os.path.isfile(
                os.path.join(self.basefolder, "destination", "copied",
                             ".metadata.yaml")))
        self.assertTrue(
            os.path.isfile(
                os.path.join(self.basefolder, "destination", "copied",
                             "crazyradio.stl")))

        self.assertIsNotNone(before_source_metadata)
        self.assertIsNone(after_source_metadata)
        self.assertIsNotNone(copied_metadata)
        self.assertDictEqual(before_source_metadata, copied_metadata)

    @data("copy_folder", "move_folder")
    def test_copy_move_folder_missing_source(self, operation):
        try:
            getattr(self.storage, operation)("source", "destination/copied")
            self.fail("Expected an exception")
        except StorageError as e:
            self.assertEqual(e.code, StorageError.INVALID_SOURCE)

    @data("copy_folder", "move_folder")
    def test_copy_move_folder_missing_destination_folder(self, operation):
        self._add_folder("source")
        self._add_file("source/crazyradio.stl", FILE_CRAZYRADIO_STL)

        try:
            getattr(self.storage, operation)("source", "destination/copied")
            self.fail("Expected an exception")
        except StorageError as e:
            self.assertEqual(e.code, StorageError.INVALID_DESTINATION)

    @data("copy_folder", "move_folder")
    def test_copy_move_folder_existing_destination_path(self, operation):
        self._add_folder("source")
        self._add_file("source/crazyradio.stl", FILE_CRAZYRADIO_STL)
        self._add_folder("destination")
        self._add_folder("destination/copied")

        try:
            getattr(self.storage, operation)("source", "destination/copied")
            self.fail("Expected an exception")
        except StorageError as e:
            self.assertEqual(e.code, StorageError.INVALID_DESTINATION)

    def test_list(self):
        bp_case_stl = self._add_and_verify_file("bp_case.stl", "bp_case.stl",
                                                FILE_BP_CASE_STL)
        self._add_and_verify_file("bp_case.gcode",
                                  "bp_case.gcode",
                                  FILE_BP_CASE_GCODE,
                                  links=[("model", dict(name=bp_case_stl))])

        content_folder = self._add_and_verify_folder("content", "content")
        self._add_and_verify_file((content_folder, "crazyradio.stl"),
                                  content_folder + "/crazyradio.stl",
                                  FILE_CRAZYRADIO_STL)

        self._add_and_verify_folder("empty", "empty")

        file_list = self.storage.list_files()
        self.assertEqual(4, len(file_list))
        self.assertTrue("bp_case.stl" in file_list)
        self.assertTrue("bp_case.gcode" in file_list)
        self.assertTrue("content" in file_list)
        self.assertTrue("empty" in file_list)

        self.assertEqual("model", file_list["bp_case.stl"]["type"])
        self.assertEqual(FILE_BP_CASE_STL.hash,
                         file_list["bp_case.stl"]["hash"])

        self.assertEqual("machinecode", file_list["bp_case.gcode"]["type"])
        self.assertEqual(FILE_BP_CASE_GCODE.hash,
                         file_list["bp_case.gcode"]["hash"])

        self.assertEqual("folder", file_list[content_folder]["type"])
        self.assertEqual(1, len(file_list[content_folder]["children"]))
        self.assertTrue("crazyradio.stl" in file_list["content"]["children"])
        self.assertEqual(
            "model",
            file_list["content"]["children"]["crazyradio.stl"]["type"])
        self.assertEqual(
            FILE_CRAZYRADIO_STL.hash,
            file_list["content"]["children"]["crazyradio.stl"]["hash"])

        self.assertEqual("folder", file_list["empty"]["type"])
        self.assertEqual(0, len(file_list["empty"]["children"]))

    def test_add_link_model(self):
        stl_name = self._add_and_verify_file("bp_case.stl", "bp_case.stl",
                                             FILE_BP_CASE_STL)
        gcode_name = self._add_and_verify_file("bp_case.gcode",
                                               "bp_case.gcode",
                                               FILE_BP_CASE_GCODE)

        self.storage.add_link(gcode_name, "model", dict(name=stl_name))

        stl_metadata = self.storage.get_metadata(stl_name)
        gcode_metadata = self.storage.get_metadata(gcode_name)

        # forward link
        self.assertEqual(1, len(gcode_metadata["links"]))
        link = gcode_metadata["links"][0]
        self.assertEqual("model", link["rel"])
        self.assertTrue("name" in link)
        self.assertEqual(stl_name, link["name"])
        self.assertTrue("hash" in link)
        self.assertEqual(FILE_BP_CASE_STL.hash, link["hash"])

        # reverse link
        self.assertEqual(1, len(stl_metadata["links"]))
        link = stl_metadata["links"][0]
        self.assertEqual("machinecode", link["rel"])
        self.assertTrue("name" in link)
        self.assertEqual(gcode_name, link["name"])
        self.assertTrue("hash" in link)
        self.assertEqual(FILE_BP_CASE_GCODE.hash, link["hash"])

    def test_add_link_machinecode(self):
        stl_name = self._add_and_verify_file("bp_case.stl", "bp_case.stl",
                                             FILE_BP_CASE_STL)
        gcode_name = self._add_and_verify_file("bp_case.gcode",
                                               "bp_case.gcode",
                                               FILE_BP_CASE_GCODE)

        self.storage.add_link(stl_name, "machinecode", dict(name=gcode_name))

        stl_metadata = self.storage.get_metadata(stl_name)
        gcode_metadata = self.storage.get_metadata(gcode_name)

        # forward link
        self.assertEqual(1, len(gcode_metadata["links"]))
        link = gcode_metadata["links"][0]
        self.assertEqual("model", link["rel"])
        self.assertTrue("name" in link)
        self.assertEqual(stl_name, link["name"])
        self.assertTrue("hash" in link)
        self.assertEqual(FILE_BP_CASE_STL.hash, link["hash"])

        # reverse link
        self.assertEqual(1, len(stl_metadata["links"]))
        link = stl_metadata["links"][0]
        self.assertEqual("machinecode", link["rel"])
        self.assertTrue("name" in link)
        self.assertEqual(gcode_name, link["name"])
        self.assertTrue("hash" in link)
        self.assertEqual(FILE_BP_CASE_GCODE.hash, link["hash"])

    def test_remove_link(self):
        stl_name = self._add_and_verify_file("bp_case.stl", "bp_case.stl",
                                             FILE_BP_CASE_STL)

        self.storage.add_link(stl_name, "web",
                              dict(href="http://www.example.com"))
        self.storage.add_link(stl_name, "web",
                              dict(href="http://www.example2.com"))

        stl_metadata = self.storage.get_metadata(stl_name)
        self.assertEqual(2, len(stl_metadata["links"]))

        self.storage.remove_link(stl_name, "web",
                                 dict(href="http://www.example.com"))

        stl_metadata = self.storage.get_metadata(stl_name)
        self.assertEqual(1, len(stl_metadata["links"]))

        self.storage.remove_link(stl_name, "web", dict(href="wrong_href"))

        stl_metadata = self.storage.get_metadata(stl_name)
        self.assertEqual(1, len(stl_metadata["links"]))

    def test_remove_link_bidirectional(self):
        stl_name = self._add_and_verify_file("bp_case.stl", "bp_case.stl",
                                             FILE_BP_CASE_STL)
        gcode_name = self._add_and_verify_file("bp_case.gcode",
                                               "bp_case.gcode",
                                               FILE_BP_CASE_GCODE)

        self.storage.add_link(stl_name, "machinecode", dict(name=gcode_name))
        self.storage.add_link(stl_name, "web",
                              dict(href="http://www.example.com"))

        stl_metadata = self.storage.get_metadata(stl_name)
        gcode_metadata = self.storage.get_metadata(gcode_name)

        self.assertEqual(1, len(gcode_metadata["links"]))
        self.assertEqual(2, len(stl_metadata["links"]))

        self.storage.remove_link(
            gcode_name, "model", dict(name=stl_name,
                                      hash=FILE_BP_CASE_STL.hash))

        stl_metadata = self.storage.get_metadata(stl_name)
        gcode_metadata = self.storage.get_metadata(gcode_name)

        self.assertEqual(0, len(gcode_metadata["links"]))
        self.assertEqual(1, len(stl_metadata["links"]))

    @data(("some_file.gco", "some_file.gco"),
          ("some_file with (parentheses) and ümläuts and digits 123.gco",
           "some_file_with_(parentheses)_and_umlauts_and_digits_123.gco"),
          ("pengüino pequeño.stl", "penguino_pequeno.stl"))
    @unpack
    def test_sanitize_name(self, input, expected):
        actual = self.storage.sanitize_name(input)
        self.assertEqual(expected, actual)

    @data("some/folder/still/left.gco", "also\\no\\backslashes.gco")
    def test_sanitize_name_invalid(self, input):
        try:
            self.storage.sanitize_name(input)
            self.fail("expected a ValueError")
        except ValueError as e:
            self.assertEqual("name must not contain / or \\", e.message)

    @data(
        ("folder/with/subfolder", "/folder/with/subfolder"),
        ("folder/with/subfolder/../other/folder", "/folder/with/other/folder"),
        ("/folder/with/leading/slash", "/folder/with/leading/slash"),
        ("folder/with/leading/dot", "/folder/with/leading/dot"))
    @unpack
    def test_sanitize_path(self, input, expected):
        actual = self.storage.sanitize_path(input)
        self.assertTrue(actual.startswith(self.basefolder))
        self.assertEqual(
            expected, actual[len(self.basefolder):].replace(os.path.sep, "/"))

    @data("../../folder/out/of/the/basefolder",
          "some/folder/../../../and/then/back")
    def test_sanitize_path_invalid(self, input):
        try:
            self.storage.sanitize_path(input)
            self.fail("expected a ValueError")
        except ValueError as e:
            self.assertTrue(
                e.message.startswith("path not contained in base folder: "))

    @data(
        ("some/folder/and/some file.gco", "/some/folder/and", "some_file.gco"),
        (("some", "folder", "and", "some file.gco"), "/some/folder/and",
         "some_file.gco"), ("some file.gco", "/", "some_file.gco"),
        (("some file.gco", ), "/", "some_file.gco"), ("", "/", ""),
        ("some/folder/with/trailing/slash/",
         "/some/folder/with/trailing/slash", ""),
        (("some", "folder", ""), "/some/folder", ""))
    @unpack
    def test_sanitize(self, input, expected_path, expected_name):
        actual = self.storage.sanitize(input)
        self.assertTrue(isinstance(actual, tuple))
        self.assertEqual(2, len(actual))

        actual_path, actual_name = actual
        self.assertTrue(actual_path.startswith(self.basefolder))
        actual_path = actual_path[len(self.basefolder):].replace(
            os.path.sep, "/")
        if not actual_path.startswith("/"):
            # if the actual path originally was just the base folder, we just stripped
            # away everything, so let's add a / again so the behaviour matches the
            # other preprocessing of our test data here
            actual_path = "/" + actual_path

        self.assertEqual(expected_path, actual_path)
        self.assertEqual(expected_name, actual_name)

    def _add_and_verify_file(self,
                             path,
                             expected_path,
                             file_object,
                             links=None,
                             overwrite=False):
        """Adds a file to the storage and verifies the sanitized path."""
        sanitized_path = self._add_file(path,
                                        file_object,
                                        links=links,
                                        overwrite=overwrite)
        self.assertEqual(expected_path, sanitized_path)
        return sanitized_path

    def _add_file(self, path, file_object, links=None, overwrite=False):
        """
		Adds a file to the storage.

		Ensures file is present, metadata is present, hash and links (if applicable)
		are populated correctly.

		Returns sanitized path.
		"""
        sanitized_path = self.storage.add_file(path,
                                               file_object,
                                               links=links,
                                               allow_overwrite=overwrite)

        split_path = sanitized_path.split("/")
        if len(split_path) == 1:
            file_path = os.path.join(self.basefolder, split_path[0])
            folder_path = self.basefolder
        else:
            file_path = os.path.join(self.basefolder,
                                     os.path.join(*split_path))
            folder_path = os.path.join(self.basefolder,
                                       os.path.join(*split_path[:-1]))

        self.assertTrue(os.path.isfile(file_path))
        self.assertTrue(
            os.path.isfile(os.path.join(folder_path, ".metadata.yaml")))

        metadata = self.storage.get_metadata(sanitized_path)
        self.assertIsNotNone(metadata)

        # assert hash
        self.assertTrue("hash" in metadata)
        self.assertEqual(file_object.hash, metadata["hash"])

        # assert presence of links if supplied
        if links:
            self.assertTrue("links" in metadata)

        return sanitized_path

    def _add_and_verify_folder(self, path, expected_path):
        """Adds a folder to the storage and verifies sanitized path."""
        sanitized_path = self._add_folder(path)
        self.assertEqual(expected_path, sanitized_path)
        return sanitized_path

    def _add_folder(self, path):
        """
		Adds a folder to the storage.

		Verifies existance of folder.

		Returns sanitized path.
		"""
        sanitized_path = self.storage.add_folder(path)
        self.assertTrue(
            os.path.isdir(
                os.path.join(self.basefolder,
                             os.path.join(*sanitized_path.split("/")))))
        return sanitized_path
class LocalStorageTest(unittest.TestCase):

	def setUp(self):
		import tempfile
		self.basefolder = os.path.realpath(os.path.abspath(tempfile.mkdtemp()))
		self.storage = LocalFileStorage(self.basefolder)

		# mock file manager module
		self.filemanager_patcher = mock.patch("octoprint.filemanager")
		self.filemanager = self.filemanager_patcher.start()

		self.filemanager.valid_file_type.return_value = True

		def get_file_type(name):
			if name.lower().endswith(".stl"):
				return ["model", "stl"]
			elif name.lower().endswith(".gco") or name.lower().endswith(".gcode") or name.lower.endswith(".g"):
				return ["machinecode", "gcode"]
			else:
				return None
		self.filemanager.get_file_type.side_effect = get_file_type

	def tearDown(self):
		import shutil
		shutil.rmtree(self.basefolder)

		self.filemanager_patcher.stop()

	def test_add_file(self):
		self._add_and_verify_file("bp_case.stl", "bp_case.stl", FILE_BP_CASE_STL)

	def test_add_file_overwrite(self):
		self._add_and_verify_file("bp_case.stl", "bp_case.stl", FILE_BP_CASE_STL)

		try:
			self._add_and_verify_file("bp_case.stl", "bp_case.stl", FILE_BP_CASE_STL, overwrite=False)
		except:
			pass

		self._add_and_verify_file("bp_case.stl", "bp_case.stl", FILE_BP_CASE_STL, overwrite=True)

	def test_add_file_with_web(self):
		import time
		href = "http://www.example.com"
		retrieved = time.time()

		stl_name = self._add_and_verify_file("bp_case.stl", "bp_case.stl", FILE_BP_CASE_STL, links=[("web", dict(href=href, retrieved=retrieved))])
		stl_metadata = self.storage.get_metadata(stl_name)

		self.assertIsNotNone(stl_metadata)
		self.assertEqual(1, len(stl_metadata["links"]))
		link = stl_metadata["links"][0]
		self.assertTrue("web", link["rel"])
		self.assertTrue("href" in link)
		self.assertEqual(href, link["href"])
		self.assertTrue("retrieved" in link)
		self.assertEqual(retrieved, link["retrieved"])

	def test_add_file_with_association(self):
		stl_name = self._add_and_verify_file("bp_case.stl", "bp_case.stl", FILE_BP_CASE_STL)
		gcode_name = self._add_and_verify_file("bp_case.gcode", "bp_case.gcode", FILE_BP_CASE_GCODE, links=[("model", dict(name=stl_name))])

		stl_metadata = self.storage.get_metadata(stl_name)
		gcode_metadata = self.storage.get_metadata(gcode_name)

		# forward link
		self.assertEqual(1, len(gcode_metadata["links"]))
		link = gcode_metadata["links"][0]
		self.assertEqual("model", link["rel"])
		self.assertTrue("name" in link)
		self.assertEqual(stl_name, link["name"])
		self.assertTrue("hash" in link)
		self.assertEqual(FILE_BP_CASE_STL.hash, link["hash"])

		# reverse link
		self.assertEqual(1, len(stl_metadata["links"]))
		link = stl_metadata["links"][0]
		self.assertEqual("machinecode", link["rel"])
		self.assertTrue("name" in link)
		self.assertEqual(gcode_name, link["name"])
		self.assertTrue("hash" in link)
		self.assertEqual(FILE_BP_CASE_GCODE.hash, link["hash"])

	def test_remove_file(self):
		stl_name = self._add_and_verify_file("bp_case.stl", "bp_case.stl", FILE_BP_CASE_STL)
		gcode_name = self._add_and_verify_file("bp_case.gcode", "bp_case.gcode", FILE_BP_CASE_GCODE, links=[("model", dict(name=stl_name))])

		stl_metadata = self.storage.get_metadata(stl_name)
		gcode_metadata = self.storage.get_metadata(gcode_name)

		self.assertIsNotNone(stl_metadata)
		self.assertIsNotNone(gcode_metadata)

		self.storage.remove_file(stl_name)
		self.assertFalse(os.path.exists(os.path.join(self.basefolder, stl_name)))

		stl_metadata = self.storage.get_metadata(stl_name)
		gcode_metadata = self.storage.get_metadata(gcode_name)

		self.assertIsNone(stl_metadata)
		self.assertIsNotNone(gcode_metadata)

		self.assertEqual(0, len(gcode_metadata["links"]))

	def test_copy_file(self):
		self._add_file("bp_case.stl", FILE_BP_CASE_STL)
		self._add_folder("test")

		self.assertTrue(os.path.isfile(os.path.join(self.basefolder, "bp_case.stl")))
		self.assertTrue(os.path.isdir(os.path.join(self.basefolder, "test")))

		self.storage.copy_file("bp_case.stl", "test/copied.stl")

		self.assertTrue(os.path.isfile(os.path.join(self.basefolder, "bp_case.stl")))
		self.assertTrue(os.path.isfile(os.path.join(self.basefolder, "test", "copied.stl")))

		stl_metadata = self.storage.get_metadata("bp_case.stl")
		copied_metadata = self.storage.get_metadata("test/copied.stl")

		self.assertIsNotNone(stl_metadata)
		self.assertIsNotNone(copied_metadata)
		self.assertDictEqual(stl_metadata, copied_metadata)

	def test_move_file(self):
		self._add_file("bp_case.stl", FILE_BP_CASE_STL)
		self._add_folder("test")

		self.assertTrue(os.path.isfile(os.path.join(self.basefolder, "bp_case.stl")))
		self.assertTrue(os.path.isdir(os.path.join(self.basefolder, "test")))

		before_stl_metadata = self.storage.get_metadata("bp_case.stl")

		self.storage.move_file("bp_case.stl", "test/copied.stl")

		self.assertFalse(os.path.isfile(os.path.join(self.basefolder, "bp_case.stl")))
		self.assertTrue(os.path.isfile(os.path.join(self.basefolder, "test", "copied.stl")))

		after_stl_metadata = self.storage.get_metadata("bp_case.stl")
		copied_metadata = self.storage.get_metadata("test/copied.stl")

		self.assertIsNotNone(before_stl_metadata)
		self.assertIsNone(after_stl_metadata)
		self.assertIsNotNone(copied_metadata)
		self.assertDictEqual(before_stl_metadata, copied_metadata)

	@data("copy_file", "move_file")
	def test_copy_move_file_missing_source(self, operation):
		try:
			getattr(self.storage, operation)("bp_case.stl", "test/copied.stl")
			self.fail("Expected an exception")
		except StorageError as e:
			self.assertEqual(e.code, StorageError.INVALID_SOURCE)

	@data("copy_file", "move_file")
	def test_copy_move_file_missing_destination_folder(self, operation):
		self._add_file("bp_case.stl", FILE_BP_CASE_STL)

		try:
			getattr(self.storage, operation)("bp_case.stl", "test/copied.stl")
			self.fail("Expected an exception")
		except StorageError as e:
			self.assertEqual(e.code, StorageError.INVALID_DESTINATION)

	@data("copy_file", "move_file")
	def test_copy_move_file_existing_destination_path(self, operation):
		self._add_file("bp_case.stl", FILE_BP_CASE_STL)
		self._add_folder("test")
		self._add_file("test/crazyradio.stl", FILE_CRAZYRADIO_STL)

		try:
			getattr(self.storage, operation)("bp_case.stl", "test/crazyradio.stl")
			self.fail("Expected an exception")
		except StorageError as e:
			self.assertEqual(e.code, StorageError.INVALID_DESTINATION)

	def test_add_folder(self):
		self._add_and_verify_folder("test", "test")

	def test_add_subfolder(self):
		folder_name = self._add_and_verify_folder("folder with some spaces", "folder_with_some_spaces")
		subfolder_name = self._add_and_verify_folder((folder_name, "subfolder"), folder_name + "/subfolder")
		stl_name = self._add_and_verify_file((subfolder_name, "bp_case.stl"), subfolder_name + "/bp_case.stl", FILE_BP_CASE_STL)

		self.assertTrue(os.path.exists(os.path.join(self.basefolder, folder_name)))
		self.assertTrue(os.path.exists(os.path.join(self.basefolder, subfolder_name)))
		self.assertTrue(os.path.exists(os.path.join(self.basefolder, stl_name)))

	def test_remove_folder(self):
		content_folder = self._add_and_verify_folder("content", "content")
		other_stl_name = self._add_and_verify_file((content_folder, "crazyradio.stl"), content_folder + "/crazyradio.stl", FILE_CRAZYRADIO_STL)

		empty_folder = self._add_and_verify_folder("empty", "empty")

		try:
			self.storage.remove_folder(content_folder, recursive=False)
		except:
			self.assertTrue(os.path.exists(os.path.join(self.basefolder, content_folder)))
			self.assertTrue(os.path.isdir(os.path.join(self.basefolder, content_folder)))
			self.assertTrue(os.path.exists(os.path.join(self.basefolder, other_stl_name)))
			self.assertIsNotNone(self.storage.get_metadata(other_stl_name))

		self.storage.remove_folder(content_folder, recursive=True)
		self.assertFalse(os.path.exists(os.path.join(self.basefolder, content_folder)))
		self.assertFalse(os.path.isdir(os.path.join(self.basefolder, content_folder)))

		self.storage.remove_folder(empty_folder, recursive=False)
		self.assertFalse(os.path.exists(os.path.join(self.basefolder, empty_folder)))
		self.assertFalse(os.path.isdir(os.path.join(self.basefolder, empty_folder)))

	def test_remove_folder_with_metadata(self):
		content_folder = self._add_and_verify_folder("content", "content")
		other_stl_name = self._add_and_verify_file((content_folder, "crazyradio.stl"), content_folder + "/crazyradio.stl", FILE_CRAZYRADIO_STL)
		self.storage.remove_file(other_stl_name)

		self.storage.remove_folder(content_folder, recursive=False)

	def test_copy_folder(self):
		self._add_folder("source")
		self._add_folder("destination")
		self._add_file("source/crazyradio.stl", FILE_CRAZYRADIO_STL)

		source_metadata = self.storage.get_metadata("source/crazyradio.stl")
		self.storage.copy_folder("source", "destination/copied")
		copied_metadata = self.storage.get_metadata("destination/copied/crazyradio.stl")

		self.assertTrue(os.path.isdir(os.path.join(self.basefolder, "source")))
		self.assertTrue(os.path.isfile(os.path.join(self.basefolder, "source", "crazyradio.stl")))
		self.assertTrue(os.path.isdir(os.path.join(self.basefolder, "destination")))
		self.assertTrue(os.path.isdir(os.path.join(self.basefolder, "destination", "copied")))
		self.assertTrue(os.path.isfile(os.path.join(self.basefolder, "destination", "copied", ".metadata.yaml")))
		self.assertTrue(os.path.isfile(os.path.join(self.basefolder, "destination", "copied", "crazyradio.stl")))

		self.assertIsNotNone(source_metadata)
		self.assertIsNotNone(copied_metadata)
		self.assertDictEqual(source_metadata, copied_metadata)

	def test_move_folder(self):
		self._add_folder("source")
		self._add_folder("destination")
		self._add_file("source/crazyradio.stl", FILE_CRAZYRADIO_STL)

		before_source_metadata = self.storage.get_metadata("source/crazyradio.stl")
		self.storage.move_folder("source", "destination/copied")
		after_source_metadata = self.storage.get_metadata("source/crazyradio.stl")
		copied_metadata = self.storage.get_metadata("destination/copied/crazyradio.stl")

		self.assertFalse(os.path.isdir(os.path.join(self.basefolder, "source")))
		self.assertFalse(os.path.isfile(os.path.join(self.basefolder, "source", "crazyradio.stl")))
		self.assertTrue(os.path.isdir(os.path.join(self.basefolder, "destination")))
		self.assertTrue(os.path.isdir(os.path.join(self.basefolder, "destination", "copied")))
		self.assertTrue(os.path.isfile(os.path.join(self.basefolder, "destination", "copied", ".metadata.yaml")))
		self.assertTrue(os.path.isfile(os.path.join(self.basefolder, "destination", "copied", "crazyradio.stl")))

		self.assertIsNotNone(before_source_metadata)
		self.assertIsNone(after_source_metadata)
		self.assertIsNotNone(copied_metadata)
		self.assertDictEqual(before_source_metadata, copied_metadata)

	@data("copy_folder", "move_folder")
	def test_copy_move_folder_missing_source(self, operation):
		try:
			getattr(self.storage, operation)("source", "destination/copied")
			self.fail("Expected an exception")
		except StorageError as e:
			self.assertEqual(e.code, StorageError.INVALID_SOURCE)

	@data("copy_folder", "move_folder")
	def test_copy_move_folder_missing_destination_folder(self, operation):
		self._add_folder("source")
		self._add_file("source/crazyradio.stl", FILE_CRAZYRADIO_STL)

		try:
			getattr(self.storage, operation)("source", "destination/copied")
			self.fail("Expected an exception")
		except StorageError as e:
			self.assertEqual(e.code, StorageError.INVALID_DESTINATION)

	@data("copy_folder", "move_folder")
	def test_copy_move_folder_existing_destination_path(self, operation):
		self._add_folder("source")
		self._add_file("source/crazyradio.stl", FILE_CRAZYRADIO_STL)
		self._add_folder("destination")
		self._add_folder("destination/copied")

		try:
			getattr(self.storage, operation)("source", "destination/copied")
			self.fail("Expected an exception")
		except StorageError as e:
			self.assertEqual(e.code, StorageError.INVALID_DESTINATION)

	def test_list(self):
		bp_case_stl = self._add_and_verify_file("bp_case.stl", "bp_case.stl", FILE_BP_CASE_STL)
		self._add_and_verify_file("bp_case.gcode", "bp_case.gcode", FILE_BP_CASE_GCODE, links=[("model", dict(name=bp_case_stl))])

		content_folder = self._add_and_verify_folder("content", "content")
		self._add_and_verify_file((content_folder, "crazyradio.stl"), content_folder + "/crazyradio.stl", FILE_CRAZYRADIO_STL)

		self._add_and_verify_folder("empty", "empty")

		file_list = self.storage.list_files()
		self.assertEqual(4, len(file_list))
		self.assertTrue("bp_case.stl" in file_list)
		self.assertTrue("bp_case.gcode" in file_list)
		self.assertTrue("content" in file_list)
		self.assertTrue("empty" in file_list)

		self.assertEqual("model", file_list["bp_case.stl"]["type"])
		self.assertEqual(FILE_BP_CASE_STL.hash, file_list["bp_case.stl"]["hash"])

		self.assertEqual("machinecode", file_list["bp_case.gcode"]["type"])
		self.assertEqual(FILE_BP_CASE_GCODE.hash, file_list["bp_case.gcode"]["hash"])

		self.assertEqual("folder", file_list[content_folder]["type"])
		self.assertEqual(1, len(file_list[content_folder]["children"]))
		self.assertTrue("crazyradio.stl" in file_list["content"]["children"])
		self.assertEqual("model", file_list["content"]["children"]["crazyradio.stl"]["type"])
		self.assertEqual(FILE_CRAZYRADIO_STL.hash, file_list["content"]["children"]["crazyradio.stl"]["hash"])

		self.assertEqual("folder", file_list["empty"]["type"])
		self.assertEqual(0, len(file_list["empty"]["children"]))

	def test_add_link_model(self):
		stl_name = self._add_and_verify_file("bp_case.stl", "bp_case.stl", FILE_BP_CASE_STL)
		gcode_name = self._add_and_verify_file("bp_case.gcode", "bp_case.gcode", FILE_BP_CASE_GCODE)

		self.storage.add_link(gcode_name, "model", dict(name=stl_name))

		stl_metadata = self.storage.get_metadata(stl_name)
		gcode_metadata = self.storage.get_metadata(gcode_name)

		# forward link
		self.assertEqual(1, len(gcode_metadata["links"]))
		link = gcode_metadata["links"][0]
		self.assertEqual("model", link["rel"])
		self.assertTrue("name" in link)
		self.assertEqual(stl_name, link["name"])
		self.assertTrue("hash" in link)
		self.assertEqual(FILE_BP_CASE_STL.hash, link["hash"])

		# reverse link
		self.assertEqual(1, len(stl_metadata["links"]))
		link = stl_metadata["links"][0]
		self.assertEqual("machinecode", link["rel"])
		self.assertTrue("name" in link)
		self.assertEqual(gcode_name, link["name"])
		self.assertTrue("hash" in link)
		self.assertEqual(FILE_BP_CASE_GCODE.hash, link["hash"])

	def test_add_link_machinecode(self):
		stl_name = self._add_and_verify_file("bp_case.stl", "bp_case.stl", FILE_BP_CASE_STL)
		gcode_name = self._add_and_verify_file("bp_case.gcode", "bp_case.gcode", FILE_BP_CASE_GCODE)

		self.storage.add_link(stl_name, "machinecode", dict(name=gcode_name))

		stl_metadata = self.storage.get_metadata(stl_name)
		gcode_metadata = self.storage.get_metadata(gcode_name)

		# forward link
		self.assertEqual(1, len(gcode_metadata["links"]))
		link = gcode_metadata["links"][0]
		self.assertEqual("model", link["rel"])
		self.assertTrue("name" in link)
		self.assertEqual(stl_name, link["name"])
		self.assertTrue("hash" in link)
		self.assertEqual(FILE_BP_CASE_STL.hash, link["hash"])

		# reverse link
		self.assertEqual(1, len(stl_metadata["links"]))
		link = stl_metadata["links"][0]
		self.assertEqual("machinecode", link["rel"])
		self.assertTrue("name" in link)
		self.assertEqual(gcode_name, link["name"])
		self.assertTrue("hash" in link)
		self.assertEqual(FILE_BP_CASE_GCODE.hash, link["hash"])

	def test_remove_link(self):
		stl_name = self._add_and_verify_file("bp_case.stl", "bp_case.stl", FILE_BP_CASE_STL)

		self.storage.add_link(stl_name, "web", dict(href="http://www.example.com"))
		self.storage.add_link(stl_name, "web", dict(href="http://www.example2.com"))

		stl_metadata = self.storage.get_metadata(stl_name)
		self.assertEqual(2, len(stl_metadata["links"]))

		self.storage.remove_link(stl_name, "web", dict(href="http://www.example.com"))

		stl_metadata = self.storage.get_metadata(stl_name)
		self.assertEqual(1, len(stl_metadata["links"]))

		self.storage.remove_link(stl_name, "web", dict(href="wrong_href"))

		stl_metadata = self.storage.get_metadata(stl_name)
		self.assertEqual(1, len(stl_metadata["links"]))

	def test_remove_link_bidirectional(self):
		stl_name = self._add_and_verify_file("bp_case.stl", "bp_case.stl", FILE_BP_CASE_STL)
		gcode_name = self._add_and_verify_file("bp_case.gcode", "bp_case.gcode", FILE_BP_CASE_GCODE)

		self.storage.add_link(stl_name, "machinecode", dict(name=gcode_name))
		self.storage.add_link(stl_name, "web", dict(href="http://www.example.com"))

		stl_metadata = self.storage.get_metadata(stl_name)
		gcode_metadata = self.storage.get_metadata(gcode_name)

		self.assertEqual(1, len(gcode_metadata["links"]))
		self.assertEqual(2, len(stl_metadata["links"]))

		self.storage.remove_link(gcode_name, "model", dict(name=stl_name, hash=FILE_BP_CASE_STL.hash))

		stl_metadata = self.storage.get_metadata(stl_name)
		gcode_metadata = self.storage.get_metadata(gcode_name)

		self.assertEqual(0, len(gcode_metadata["links"]))
		self.assertEqual(1, len(stl_metadata["links"]))

	@data(
		("some_file.gco", "some_file.gco"),
		("some_file with (parentheses) and ümläuts and digits 123.gco", "some_file_with_(parentheses)_and_umlauts_and_digits_123.gco"),
		("pengüino pequeño.stl", "penguino_pequeno.stl")
	)
	@unpack
	def test_sanitize_name(self, input, expected):
		actual = self.storage.sanitize_name(input)
		self.assertEqual(expected, actual)

	@data(
		"some/folder/still/left.gco",
		"also\\no\\backslashes.gco"
	)
	def test_sanitize_name_invalid(self, input):
		try:
			self.storage.sanitize_name(input)
			self.fail("expected a ValueError")
		except ValueError as e:
			self.assertEqual("name must not contain / or \\", e.message)

	@data(
		("folder/with/subfolder", "/folder/with/subfolder"),
		("folder/with/subfolder/../other/folder", "/folder/with/other/folder"),
		("/folder/with/leading/slash", "/folder/with/leading/slash"),
		("folder/with/leading/dot", "/folder/with/leading/dot")
	)
	@unpack
	def test_sanitize_path(self, input, expected):
		actual = self.storage.sanitize_path(input)
		self.assertTrue(actual.startswith(self.basefolder))
		self.assertEqual(expected, actual[len(self.basefolder):].replace(os.path.sep, "/"))

	@data(
		"../../folder/out/of/the/basefolder",
		"some/folder/../../../and/then/back"
	)
	def test_sanitize_path_invalid(self, input):
		try:
			self.storage.sanitize_path(input)
			self.fail("expected a ValueError")
		except ValueError as e:
			self.assertTrue(e.message.startswith("path not contained in base folder: "))

	@data(
		("some/folder/and/some file.gco", "/some/folder/and", "some_file.gco"),
		(("some", "folder", "and", "some file.gco"), "/some/folder/and", "some_file.gco"),
		("some file.gco", "/", "some_file.gco"),
		(("some file.gco",), "/", "some_file.gco"),
		("", "/", ""),
		("some/folder/with/trailing/slash/", "/some/folder/with/trailing/slash", ""),
		(("some", "folder", ""), "/some/folder", "")
	)
	@unpack
	def test_sanitize(self, input, expected_path, expected_name):
		actual = self.storage.sanitize(input)
		self.assertTrue(isinstance(actual, tuple))
		self.assertEqual(2, len(actual))

		actual_path, actual_name = actual
		self.assertTrue(actual_path.startswith(self.basefolder))
		actual_path = actual_path[len(self.basefolder):].replace(os.path.sep, "/")
		if not actual_path.startswith("/"):
			# if the actual path originally was just the base folder, we just stripped
			# away everything, so let's add a / again so the behaviour matches the
			# other preprocessing of our test data here
			actual_path = "/" + actual_path

		self.assertEqual(expected_path, actual_path)
		self.assertEqual(expected_name, actual_name)

	def _add_and_verify_file(self, path, expected_path, file_object, links=None, overwrite=False):
		"""Adds a file to the storage and verifies the sanitized path."""
		sanitized_path = self._add_file(path, file_object, links=links, overwrite=overwrite)
		self.assertEqual(expected_path, sanitized_path)
		return sanitized_path

	def _add_file(self, path, file_object, links=None, overwrite=False):
		"""
		Adds a file to the storage.

		Ensures file is present, metadata is present, hash and links (if applicable)
		are populated correctly.

		Returns sanitized path.
		"""
		sanitized_path = self.storage.add_file(path, file_object, links=links, allow_overwrite=overwrite)

		split_path = sanitized_path.split("/")
		if len(split_path) == 1:
			file_path = os.path.join(self.basefolder, split_path[0])
			folder_path = self.basefolder
		else:
			file_path = os.path.join(self.basefolder, os.path.join(*split_path))
			folder_path = os.path.join(self.basefolder, os.path.join(*split_path[:-1]))

		self.assertTrue(os.path.isfile(file_path))
		self.assertTrue(os.path.isfile(os.path.join(folder_path, ".metadata.yaml")))

		metadata = self.storage.get_metadata(sanitized_path)
		self.assertIsNotNone(metadata)

		# assert hash
		self.assertTrue("hash" in metadata)
		self.assertEqual(file_object.hash, metadata["hash"])

		# assert presence of links if supplied
		if links:
			self.assertTrue("links" in metadata)

		return sanitized_path

	def _add_and_verify_folder(self, path, expected_path):
		"""Adds a folder to the storage and verifies sanitized path."""
		sanitized_path = self._add_folder(path)
		self.assertEqual(expected_path, sanitized_path)
		return sanitized_path

	def _add_folder(self, path):
		"""
		Adds a folder to the storage.

		Verifies existance of folder.

		Returns sanitized path.
		"""
		sanitized_path = self.storage.add_folder(path)
		self.assertTrue(os.path.isdir(os.path.join(self.basefolder, os.path.join(*sanitized_path.split("/")))))
		return sanitized_path
class FileOptions(Scroll_Box_Even):
    '''
    List Options will have a few different screens in it containing different lists of options.

    First List = Sort, Select Items to Edit, Create new Folder, Refresh

    These lists spawn different lists. instead of constantly switching out screens we are going
    to use one screen and update Scroll_Box_Even with a new list as needed.

    '''
    def __init__(self, show_files_callback, refresh_files,
                 show_folders_callback, create_folder_callback, **kwargs):
        self.buttons = []
        super(FileOptions, self).__init__(self.buttons)
        self.waiting_for_input = False
        self.show_folders_callback = show_folders_callback
        self.settings = roboprinter.printer_instance._settings
        self.show_files_callback = show_files_callback
        self.refresh_files = refresh_files
        self.create_folder_callback = create_folder_callback
        self.original_screen = None
        self.sort_observer = Button_Group_Observer()
        self.modify_files_observer = Button_Group_Observer()

        #initialize this so that we can edit files on octoprint.
        base_folder = '/home/pi/.octoprint/uploads'
        self.storage = LocalFileStorage(base_folder)

        self.show_selectable_files_options = {
            'callback': self.get_selected_files,
            'icon': "Icons/Files_Icons/File_Options/Next.png"
        }

        self.button_dict = {
            'root_list': {
                'buttons': [
                    File_Option_Button(
                        name=lang.pack['Files']['File_Options']['Sort_Files'],
                        callback=partial(self.switch_lists,
                                         next_list='sort_files'),
                        default_icon=
                        "Icons/Files_Icons/File_Options/Sort List.png"),
                    File_Option_Button(
                        name=lang.pack['Files']['File_Options']
                        ['Select_Items'],
                        callback=partial(
                            self.show_files_callback,
                            callback_dict=self.show_selectable_files_options),
                        default_icon=
                        "Icons/Files_Icons/File_Options/Select items.png"),
                    File_Option_Button(
                        name=lang.pack['Files']['File_Options']
                        ['Create_Folder'],
                        callback=self.create_folder_callback,
                        default_icon=
                        "Icons/Files_Icons/File_Options/Create new folder.png"
                    ),
                    File_Option_Button(
                        name=lang.pack['Files']['File_Options']['Refresh'],
                        callback=self.refresh,
                        default_icon=
                        "Icons/Files_Icons/File_Options/Refresh.png")
                ],
                'title':
                lang.pack['Files']['File_Options']['Title'],
                'option_function':
                self.exit_from_file_explorer,
                'option_icon':
                "Icons/cancel_button_icon.png"
            },
            'sort_files': {
                'buttons': [
                    File_Option_Button(
                        name=lang.pack['Files']['Sort_Files']['Alphabet'],
                        default_icon=
                        "Icons/Files_Icons/File_Options/Alphabetically.png",
                        observer=self.sort_observer,
                        selected=False,
                        extra_content=True,
                        option_list=[
                            lang.pack['Files']['Sort_Files']
                            ['Alphabet_Options']['A2Z'], lang.pack['Files']
                            ['Sort_Files']['Alphabet_Options']['Z2A']
                        ]),
                    File_Option_Button(
                        name=lang.pack['Files']['Sort_Files']['Size'],
                        observer=self.sort_observer,
                        default_icon=
                        "Icons/Files_Icons/File_Options/By size.png",
                        selected=False,
                        extra_content=True,
                        option_list=[
                            lang.pack['Files']['Sort_Files']['Size_Options']
                            ['L2S'], lang.pack['Files']['Sort_Files']
                            ['Size_Options']['S2L']
                        ]),
                    File_Option_Button(
                        name=lang.pack['Files']['Sort_Files']['Date'],
                        observer=self.sort_observer,
                        default_icon=
                        "Icons/Files_Icons/File_Options/By date.png",
                        selected=True,
                        extra_content=True,
                        option_list=[
                            lang.pack['Files']['Sort_Files']['Date_Options']
                            ['New'], lang.pack['Files']['Sort_Files']
                            ['Date_Options']['Old']
                        ]),
                    File_Option_Button(
                        name=lang.pack['Files']['Sort_Files']['Type'],
                        observer=self.sort_observer,
                        default_icon=
                        "Icons/Files_Icons/File_Options/By type.png",
                        selected=False,
                        extra_content=True,
                        option_list=[
                            lang.pack['Files']['Sort_Files']['Type_Options']
                            ['STL'], lang.pack['Files']['Sort_Files']
                            ['Type_Options']['GCODE'], lang.pack['Files']
                            ['Sort_Files']['Type_Options']['HEX'],
                            lang.pack['Files']['Sort_Files']['Type_Options']
                            ['Folder']
                        ])
                ],
                'title':
                lang.pack['Files']['Sort_Files']['Title'],
                'option_function':
                self.set_sorting_options,
                'option_icon':
                "Icons/Files_Icons/File_Options/Next.png"
            },
            'modify_files': {
                'buttons': [
                    File_Option_Button(
                        default_icon=
                        "Icons/Files_Icons/File_Options/Copy files.png",
                        selected_icon="Icons/check_icon.png",
                        name=lang.pack['Files']['Modify_Files']['Copy'],
                        observer=self.modify_files_observer,
                        selected=False),
                    File_Option_Button(
                        default_icon=
                        "Icons/Files_Icons/File_Options/Move files.png",
                        selected_icon="Icons/check_icon.png",
                        name=lang.pack['Files']['Modify_Files']['Move'],
                        observer=self.modify_files_observer,
                        selected=False),
                    File_Option_Button(
                        default_icon=
                        "Icons/Files_Icons/File_Options/Delete files.png",
                        selected_icon="Icons/check_icon.png",
                        name=lang.pack['Files']['Modify_Files']['Delete'],
                        observer=self.modify_files_observer,
                        selected=False),
                    File_Counter("0")
                ],
                'title':
                lang.pack['Files']['Modify_Files']['Title'],
                'option_function':
                self.modify_files,
                'option_icon':
                "Icons/Files_Icons/File_Options/Next.png"
            }
        }

        self.cur_list = Screen_Node(screen=self.button_dict['root_list'])
        self.populate_screen()
        self.set_default_sorting_option()

    def exit_from_file_explorer(self):

        #back function for cancel and the backbutton
        back_destination = roboprinter.screen_controls.get_screen_data()
        back_function = partial(
            roboprinter.screen_controls.populate_old_screen,
            screen=back_destination)

        def confirm_exit():
            body_text = lang.pack['Files']['File_Options'][
                'Exit_File_Options']['Body']
            option1 = lang.pack['Files']['File_Options']['Exit_File_Options'][
                'Option1']
            option2 = lang.pack['Files']['File_Options']['Exit_File_Options'][
                'Option2']
            modal_screen = Modal_Question_No_Title(body_text, option1, option2,
                                                   exit_fe, cancel_action)

            #add to the screen
            roboprinter.screen_controls.set_screen_content(
                modal_screen,
                lang.pack['Files']['File_Options']['Exit_File_Options']
                ['Title'],
                back_function=back_function,
                option_function='no_option')

        def cancel_action():
            back_function()

        def exit_fe():
            roboprinter.robosm.go_back_to_main('printer_status_tab')

        confirm_exit()

    def switch_lists(self, next_list):
        if next_list in self.button_dict:
            self.cur_list = Screen_Node(screen=self.button_dict[next_list],
                                        prev_screen=self.cur_list)
            self.populate_screen()
            return True
        return False

    def return_to_previous_list(self):
        if self.cur_list.prev_screen != None:
            self.cur_list = self.cur_list.prev_screen
            self.populate_screen()
            return True
        else:
            if self.original_screen != None:
                roboprinter.screen_controls.populate_old_screen(
                    self.original_screen)
            return False

    def refresh(self, *args, **kwargs):
        #refresh File Lists
        self.refresh_files()
        roboprinter.screen_controls.populate_old_screen(self.original_screen)

    def populate_screen(self):

        self.buttons = self.cur_list.screen['buttons']
        self.title = self.cur_list.screen['title']
        self.repopulate_for_new_screen()

        #remake the File_BB screen
        roboprinter.screen_controls.update_title(self.title)

        #change the option function as needed
        if 'option_function' in self.cur_list.screen:
            roboprinter.screen_controls.update_option_function(
                self.cur_list.screen['option_function'],
                self.cur_list.screen['option_icon'])

    #This function links to the sorting buttons to detirmine what the sorting order of files should be
    #it will store this as a dictionary with the sort type and the option that goes along with that sorting type.
    def set_sorting_options(self):

        #grab all of the button states

        for option in self.button_dict['sort_files']['buttons']:
            if option.selected:
                self.sorting_option = {
                    'sort': option.original_name,
                    'modify': option.option_list[option.list_pos]
                }
                break
        #save the sorting option to settings
        self.settings.set(['sorting_config'], self.sorting_option)
        self.settings.save()

        #Go back to the file screen.
        self.return_to_previous_list()
        if self.original_screen != None:
            roboprinter.screen_controls.populate_old_screen(
                self.original_screen)

    def set_default_sorting_option(self):
        #get the saved sorting option
        self.sorting_option = self.settings.get(['sorting_config'])

        #Protection for new machines that do not have this value initialized yet.
        robo_sorting_options = [
            lang.pack['Files']['Sort_Files']['Type'],
            lang.pack['Files']['Sort_Files']['Size'],
            lang.pack['Files']['Sort_Files']['Date'],
            lang.pack['Files']['Sort_Files']['Alphabet']
        ]
        if self.sorting_option == {} or not self.sorting_option[
                'sort'] in robo_sorting_options:
            self.set_sorting_options()

        #set that button to active
        for button in self.button_dict['sort_files']['buttons']:
            if button.original_name == self.sorting_option['sort']:
                button.select(True)
                for x in range(0, len(button.option_list)):
                    if button.option_list[x] == self.sorting_option['modify']:
                        button.list_pos = x
                        break
                break

    def get_sorting_options(self):
        return self.sorting_option

    #this callback has the previous screen passed to it, so it can restore the File_options object then switch the
    #layout of that screen.
    def get_selected_files(self, file_list, file_options_screen,
                           resume_file_select, update_selected_folders):
        #Logger.info("Getting selected files")

        self.selected_files = []
        selected_folders_path = {}

        for file in file_list:
            if file['selected']:
                #Logger.info('File: ' + str(file['name']) + ' added to list')
                self.selected_files.append(file)
                if file['type'] == 'folder':
                    selected_folders_path[file['path']] = file['name']

        #if nothing is selected then do nothing
        if len(self.selected_files) == 0:
            #Logger.info("No Items Selected")
            ep = Error_Popup(
                lang.pack['Files']['Errors']['No_Items1']['Title'],
                lang.pack['Files']['Errors']['No_Items1']['Body'],
            )
            ep.show()
            return

        #return modify buttons to their original state
        #This is so the user can pick their option without interference from us
        for button in self.button_dict['modify_files']['buttons']:
            if hasattr(button, 'selected'):
                button.selected = False
                button.select(button.selected)
            elif hasattr(button, 'update_count'):
                button.update_count(str(len(self.selected_files)))
            else:
                raise AttributeError("Has neither selected or update count.")

        #Throw the selected list over to file_explorer so it can block out any
        update_selected_folders(selected_folders_path)
        #change screen back to lead back to the file select screen
        file_select_screen = roboprinter.screen_controls.get_screen_data()

        roboprinter.screen_controls.populate_old_screen(file_options_screen)
        self.switch_lists('modify_files')

        def back_function():
            self.return_to_previous_list()  #return to the List Option Page
            resume_file_select(
            )  #tell the File Explorer to go back to the file select screen
            roboprinter.screen_controls.populate_old_screen(
                file_select_screen,
                ignore_update=True)  #return to the file select screen

        roboprinter.screen_controls.update_back_function(back_function)

    def modify_files(self):
        #Logger.info("Modify the files!")

        for button in self.button_dict['modify_files']['buttons']:
            if hasattr(button, 'selected'):
                if button.selected:
                    self.execute_modification(button.original_name)
                    return

        #Logger.info("No Items Selected")
        ep = Error_Popup(
            lang.pack['Files']['Errors']['No_Items2']['Title'],
            lang.pack['Files']['Errors']['No_Items2']['Body'],
        )
        ep.show()
        #The code will only reach here if we did not find a selected button
        #Logger.info("No Option Selected!")

    def execute_modification(self, modify_option):

        self.modify_progress = File_Progress()
        self.modify_progress.update_title("[size=40]" + modify_option)

        #make the popup
        self.prog_popup = Empty_Popup(self.modify_progress)

        if modify_option == lang.pack['Files']['Modify_Files']['Delete']:

            #back function for cancel and the backbutton
            back_destination = roboprinter.screen_controls.get_screen_data()
            back_function = partial(
                roboprinter.screen_controls.populate_old_screen,
                screen=back_destination)

            def confirm_delete():
                body_text = lang.pack['Files']['Delete_File_Conf']['Body']
                option1 = lang.pack['Files']['Delete_File_Conf']['positive']
                option2 = lang.pack['Files']['Delete_File_Conf']['negative']
                modal_screen = Modal_Question_No_Title(body_text, option1,
                                                       option2, delete_action,
                                                       cancel_action)

                #add to the screen
                roboprinter.screen_controls.set_screen_content(
                    modal_screen,
                    lang.pack['Files']['Delete_File_Conf']['Title'],
                    back_function=back_function,
                    option_function='no_option')

            def cancel_action():
                back_function()

            def delete_action():
                self.delete_files()
                while (self.return_to_previous_list()):
                    pass

            confirm_delete()

        elif modify_option == lang.pack['Files']['Modify_Files']['Move']:
            #Logger.info("Moving files")
            self.MODE = 'MOVE'
            show_selectable_folders_options = {
                'callback': self.get_directory,
                'icon': "Icons/Files_Icons/File_Options/Next.png"
            }
            self.show_folders_callback(show_selectable_folders_options)
        elif modify_option == lang.pack['Files']['Modify_Files']['Copy']:
            #Logger.info("Copying files")
            self.MODE = 'COPY'
            show_selectable_folders_options = {
                'callback': self.get_directory,
                'icon': "Icons/Files_Icons/File_Options/Next.png"
            }
            self.show_folders_callback(show_selectable_folders_options)

    def get_directory(self, directory, file_explorer_callback):

        #Logger.info("Moving selected files to directory " + str(directory))

        if self.MODE == 'MOVE':
            self.move_files(directory, file_explorer_callback)
        elif self.MODE == 'COPY':
            self.copy_files(directory, file_explorer_callback)
        else:
            raise Exception("self.MODE is not set")

    #This function takes in filename and destination and outputs a filename with a _#.extension
    def name_iterator(self, filename, destination, file_type="folder"):
        #get the name and extension
        name, extension = os.path.splitext(filename)
        final_name = destination + "/" + name + extension  #gotta check if the basename is available
        file_exists = True  #initialize the value

        #different function based on filetype
        if file_type == "folder":
            file_exists = self.storage.folder_exists(final_name)
        else:
            file_exists = self.storage.file_exists(final_name)

        #if file doesn't exist then it will just return the basename
        counter = 0
        while file_exists:
            counter += 1
            final_name = destination + "/" + name + "_" + str(
                counter) + extension
            if file_type == "folder":
                file_exists = self.storage.folder_exists(final_name)
            else:
                file_exists = self.storage.file_exists(final_name)

        return final_name

    #This function is used for testing if two paths are the same file
    def same_file(self, filepath, name, destination):
        Logger.info(filepath)
        Logger.info(name)
        Logger.info(destination)
        if destination == '':
            final_destination = name
        else:
            final_destination = destination + "/" + name
        Logger.info(final_destination)
        if filepath == final_destination:
            return True
        return False

    '''
    Earlier in the class the user selects the files that he wants to modify, then chooses the type of modification(Copy). 
    Then the user selects the destination and then copy_files recieves a destination folder and a callback to call when it's 
    done modifying the files. Copy_Files then iterates over a list of files, if the file exists at the destination folder 
    already the User gets notified and asked to choose to either Replace, Keep Both Files, Or just skip the file. Then the 
    Correct action takes place and the function moves onto the next file. Rinse Repeat, then call the callback when finished
    '''

    def copy_files(self, destination, callback):

        #display Popup
        self.prog_popup.show()
        self.counter = 0  #progress
        self.max_count = len(self.selected_files)
        self.error_popup = False

        def skip():
            self.counter += 1
            prog = float(float(self.counter) / float(self.max_count) *
                         100.00)  #percent progress
            self.modify_progress.update_progress(prog)
            self.modify_progress.file_exists = "[size=30]" + lang.pack[
                'Files']['Modify_Files']['Progress'] + str(
                    self.counter) + " / " + str(
                        self.max_count
                    ) + lang.pack['Files']['Modify_Files']['Files']
            #Logger.info( str(self.counter) + " " + str(self.max_count) + " " + str(prog))
            self.waiting_for_input = False
            self.modify_progress.button_state = True
            Clock.schedule_once(copy_next_file, 0.2)  #loop

        def replace():
            self.modify_progress.button_state = True
            self.modify_progress.file_exists = "[size=30]" + lang.pack[
                'Files']['Modify_Files']['Progress'] + str(
                    self.counter) + " / " + str(
                        self.max_count
                    ) + lang.pack['Files']['Modify_Files']['Files']

            def replace_action(dt):
                try:
                    #check to see if the file is the same file we are about to delete
                    source_abs_path = self.storage.path_on_disk(
                        self.file['path'])
                    dest_abs_path = self.storage.path_on_disk(
                        destination + "/" + self.file['name'])
                    if os.path.samefile(source_abs_path, dest_abs_path):
                        #Logger.info("File and destination are the same, Not doing anything\n" + str(source_abs_path) + "\n" + str(dest_abs_path))
                        pass
                    else:

                        if self.file['type'] == "folder":
                            #Logger.info("Removing File")
                            self.storage.remove_folder(destination + "/" +
                                                       self.file['name'],
                                                       recursive=True)
                            #Logger.info("Copying File")
                            self.storage.copy_folder(
                                self.file['path'],
                                destination + "/" + self.file['name'])
                        else:
                            #Logger.info("Removing File")
                            self.storage.remove_file(destination + "/" +
                                                     self.file['name'])
                            #Logger.info("Copying File")
                            self.storage.copy_file(
                                self.file['path'],
                                destination + "/" + self.file['name'])

                except Exception as e:
                    Logger.info("!!!!!!!!!!!!!!! Error: " + str(e))
                    traceback.print_exc()
                    error_out()
                finally:
                    skip()

            #schedule the replace action so the screen can update accordingly
            Clock.schedule_once(replace_action, 0.2)

        def keep_both():
            self.modify_progress.button_state = True
            self.modify_progress.file_exists = "[size=30]" + lang.pack[
                'Files']['Modify_Files']['Progress'] + str(
                    self.counter) + " / " + str(
                        self.max_count
                    ) + lang.pack['Files']['Modify_Files']['Files']

            #check if the file exists or not and ask user what they want to do
            def keep_action(dt):
                try:
                    if self.file['type'] == "folder":
                        final_name = self.name_iterator(self.file['name'],
                                                        destination,
                                                        file_type='folder')
                        self.storage.copy_folder(self.file['path'], final_name)

                    else:
                        final_name = self.name_iterator(self.file['name'],
                                                        destination,
                                                        file_type='file')
                        self.storage.copy_file(self.file['path'], final_name)
                except Exception as e:
                    Logger.info("!!!!!!!!!!!!!!! Error: " + str(e))
                    traceback.print_exc()
                    error_out()
                finally:
                    skip()

            Clock.schedule_once(keep_action, 0.2)

        def file_exists():
            self.modify_progress.file_exists = "[size=30][color=FF0000]" + lang.pack[
                'Files']['Modify_Files']['File_Exists']
            self.modify_progress.replace = replace
            self.modify_progress.keep_both = keep_both
            self.modify_progress.skip = skip
            self.modify_progress.button_state = False
            self.waiting_for_input = True

        def error_out():
            if not self.error_popup:
                self.error_popup = True
                ep = Error_Popup(
                    lang.pack['Files']['File_Options']['Copy_Error']['Title'],
                    lang.pack['Files']['File_Options']['Copy_Error']['Body'])
                ep.show()
            self.counter = self.max_count - 1

        #Copy next ticker. This gets called until all files have been copied. It updates the progress bar.
        def copy_next_file(*args, **kwargs):
            try:
                if self.counter < self.max_count:
                    file = self.selected_files[self.counter]
                    self.file = file
                    #Logger.info("Copying: " + str(file['name']) + " " + str(file['path']) + " to " + destination)
                    self.modify_progress.update_file(str(file['name']))
                    #check if the file exists or not and ask user what they want to do
                    if file['type'] == "folder":
                        if not self.storage.folder_exists(destination + "/" +
                                                          file['name']):
                            self.storage.copy_folder(
                                file['path'], destination + "/" + file['name'])
                        else:
                            file_exists()

                    else:
                        if not self.storage.file_exists(destination + "/" +
                                                        file['name']):
                            self.storage.copy_file(
                                file['path'], destination + "/" + file['name'])
                        else:
                            file_exists()
            except Exception as e:
                Logger.info("!!!!!!!!!!!!!!! Error: " + str(e))
                traceback.print_exc()
                error_out()

            #check if it's finished
            if self.counter == self.max_count:
                self.prog_popup.hide()
                #return file options to root options
                while self.return_to_previous_list():
                    pass
                callback()
                return False
            else:
                if not self.waiting_for_input:
                    skip()

        Clock.schedule_once(copy_next_file, 0.2)

    '''
    move_files does the same thing as copy_files, but it actually moves the original file. I know we are supposed to use the DRY method, but Copy files is 
    all over the place and adding in another clause for checking the mode seemed to make the source code even more confusing than it already is. So I opted to 
    just repeat copy files except change copy_folder/file to move_folder/file
    '''

    def move_files(self, destination, callback):

        #display Popup
        self.prog_popup.show()
        self.counter = 0  #progress
        self.max_count = len(self.selected_files)
        self.error_popup = False

        def skip():
            self.counter += 1
            prog = float(float(self.counter) / float(self.max_count) *
                         100.00)  #percent progress
            self.modify_progress.update_progress(prog)
            self.modify_progress.file_exists = "[size=30]" + lang.pack[
                'Files']['Modify_Files']['Progress'] + str(
                    self.counter) + " / " + str(
                        self.max_count
                    ) + lang.pack['Files']['Modify_Files']['Files']
            #Logger.info( str(self.counter) + " " + str(self.max_count) + " " + str(prog))
            self.waiting_for_input = False
            self.modify_progress.button_state = True
            Clock.schedule_once(move_next_file, 0.2)  #loop

        def replace():
            self.modify_progress.button_state = True
            self.modify_progress.file_exists = "[size=30]" + lang.pack[
                'Files']['Modify_Files']['Progress'] + str(
                    self.counter) + " / " + str(
                        self.max_count
                    ) + lang.pack['Files']['Modify_Files']['Files']

            def replace_action(dt):
                try:
                    #check to see if the file is the same file we are about to delete
                    source_abs_path = self.storage.path_on_disk(
                        self.file['path'])
                    dest_abs_path = self.storage.path_on_disk(
                        destination + "/" + self.file['name'])
                    if os.path.samefile(source_abs_path, dest_abs_path):
                        #Logger.info("File and destination are the same, Not doing anything\n" + str(source_abs_path) + "\n" + str(dest_abs_path))
                        pass
                    else:
                        if self.file['type'] == "folder":
                            #Logger.info("Removing File")
                            self.storage.remove_folder(destination + "/" +
                                                       self.file['name'],
                                                       recursive=True)
                            #Logger.info("Moving File")
                            self.storage.move_folder(
                                self.file['path'],
                                destination + "/" + self.file['name'])
                        else:
                            #Logger.info("Removing File")
                            self.storage.remove_file(destination + "/" +
                                                     self.file['name'])
                            #Logger.info("Moving File")
                            self.storage.move_file(
                                self.file['path'],
                                destination + "/" + self.file['name'])
                except Exception as e:
                    Logger.info("!!!!!!!!!!!!!!! Error: " + str(e))
                    traceback.print_exc()
                    error_out()
                finally:
                    skip()

            #schedule the replace action so the screen can update accordingly
            Clock.schedule_once(replace_action, 0.2)

        def keep_both():
            self.modify_progress.button_state = True
            self.modify_progress.file_exists = "[size=30]" + lang.pack[
                'Files']['Modify_Files']['Progress'] + str(
                    self.counter) + " / " + str(
                        self.max_count
                    ) + lang.pack['Files']['Modify_Files']['Files']

            #check if the file exists or not and ask user what they want to do
            def keep_action(dt):
                try:
                    if self.file['type'] == "folder":
                        same_folder = self.same_file(self.file['path'],
                                                     self.file['name'],
                                                     destination)
                        final_name = self.name_iterator(self.file['name'],
                                                        destination,
                                                        file_type='folder')

                        if not same_folder:
                            self.storage.move_folder(self.file['path'],
                                                     final_name)
                        else:
                            self.storage.copy_folder(self.file['path'],
                                                     final_name)

                    else:
                        same_file = self.same_file(self.file['path'],
                                                   self.file['name'],
                                                   destination)
                        final_name = self.name_iterator(self.file['name'],
                                                        destination,
                                                        file_type='file')

                        if not same_file:
                            self.storage.move_file(self.file['path'],
                                                   final_name)
                        else:
                            self.storage.copy_file(self.file['path'],
                                                   final_name)

                except Exception as e:
                    Logger.info("!!!!!!!!!!!!!!! Error: " + str(e))
                    traceback.print_exc()
                    error_out()
                finally:
                    skip()

            Clock.schedule_once(keep_action, 0.2)

        def file_exists():
            self.modify_progress.file_exists = "[size=30][color=FF0000]" + lang.pack[
                'Files']['Modify_Files']['File_Exists']
            self.modify_progress.replace = replace
            self.modify_progress.keep_both = keep_both
            self.modify_progress.skip = skip
            self.modify_progress.button_state = False
            self.waiting_for_input = True

        def error_out():
            if not self.error_popup:
                self.error_popup = True
                ep = Error_Popup(
                    lang.pack['Files']['File_Options']['Move_Error']['Title'],
                    lang.pack['Files']['File_Options']['Move_Error']['Body'])
                ep.show()
            self.counter = self.max_count - 1

        #Copy next ticker. This gets called until all files have been copied. It updates the progress bar.
        def move_next_file(*args, **kwargs):
            try:
                if self.counter < self.max_count:
                    file = self.selected_files[self.counter]
                    self.file = file
                    #Logger.info("Moving: " + str(file['name']) + " " + str(file['path']) + " to " + destination)
                    self.modify_progress.update_file(str(file['name']))
                    #check if the file exists or not and ask user what they want to do
                    if file['type'] == "folder":
                        if not self.storage.folder_exists(destination + "/" +
                                                          file['name']):
                            self.storage.move_folder(
                                file['path'], destination + "/" + file['name'])
                        else:
                            file_exists()

                    else:
                        if not self.storage.file_exists(destination + "/" +
                                                        file['name']):
                            self.storage.move_file(
                                file['path'], destination + "/" + file['name'])
                        else:
                            file_exists()
            except Exception as e:
                Logger.info("!!!!!!!!!!!!!!! Error: " + str(e))
                traceback.print_exc()
                error_out()

            #check if it's finished
            if self.counter == self.max_count:
                self.prog_popup.hide()
                #return file options to root options
                while self.return_to_previous_list():
                    pass
                callback()
                return False
            else:
                if not self.waiting_for_input:
                    skip()

        Clock.schedule_once(move_next_file, 0.2)

    '''
    delete_files will delete all selected files and show the progress to the screen.
    '''

    def delete_files(self):
        #display Popup
        self.prog_popup.show()
        self.counter = 0  #progress
        self.max_count = len(self.selected_files)
        self.error_popup = False

        def error_out():
            if not self.error_popup:
                self.error_popup = True
                ep = Error_Popup(
                    lang.pack['Files']['File_Options']['Delete_Error']
                    ['Title'],
                    lang.pack['Files']['File_Options']['Delete_Error']['Body'])
                ep.show()
            self.counter = self.max_count - 1

        def skip():
            self.counter += 1
            prog = float(float(self.counter) / float(self.max_count) *
                         100.00)  #percent progress
            self.modify_progress.update_progress(prog)
            self.modify_progress.file_exists = "[size=30]" + lang.pack[
                'Files']['Modify_Files']['Progress'] + str(
                    self.counter) + " / " + str(
                        self.max_count
                    ) + lang.pack['Files']['Modify_Files']['Files']
            #Logger.info( str(self.counter) + " " + str(self.max_count) + " " + str(prog))
            self.waiting_for_input = False
            self.modify_progress.button_state = True
            Clock.schedule_once(delete_next_file, 0.2)  #loop

        #delete ticker, this will delete all files until there are no more left
        def delete_next_file(*args, **kwargs):
            try:
                if self.counter < self.max_count:
                    file = self.selected_files[self.counter]
                    self.file = file
                    #Logger.info("Deleting: " + str(file['name']) + " " + str(file['path']))
                    self.modify_progress.update_file(str(file['name']))
                    #delete the files
                    if file['type'] == "folder":
                        self.storage.remove_folder(file['path'],
                                                   recursive=True)
                    else:
                        self.storage.remove_file(file['path'])
            except:
                Logger.info("!!!!!!!!!!!!!!! Error: " + str(e))
                traceback.print_exc()
                error_out()

            #check if it's finished
            if self.counter == self.max_count:
                self.prog_popup.hide()
                #return file options to root options
                while self.return_to_previous_list():
                    pass
                return False
            else:
                if not self.waiting_for_input:
                    skip()

        Clock.schedule_once(delete_next_file, 0.2)

    #This will change the permissions to the USB folder and try again. This
    # def change_permissions_and_try_create_folder(self, destination):
    #     try:
    #         import os
    #         import subprocess
    #         #chown the USB folder
    #         USB_folder =  os.path.expanduser('~/.octoprint/uploads/USB')
    #         command = "sudo chown pi:pi " + str(USB_folder)
    #         Logger.info("Executing command " + str(command))

    #         #wait for command to finish executing
    #         subprocess.Popen(command,
    #                          stdout=subprocess.PIPE,
    #                          shell=True
    #                          )
    #         output, error = temp_p.communicate()
    #         p_status = temp_p.wait()

    #         Logger.info("Finished Executing! Attempting to add folder!")

    #         self.storage.add_folder(destination, ignore_existing=True)
    #         return True
    #     except Exception as e:
    #         Logger.info("Creating Permissions Failed! Erroring out!")
    #         return False

    def create_new_folder(self, destination, file_callback):

        #get the current screen so we can travel back to it
        current_screen = roboprinter.screen_controls.get_screen_data()
        back_function = partial(
            roboprinter.screen_controls.populate_old_screen,
            screen=current_screen)
        self.error_popup = False

        def error_out():
            if not self.error_popup:
                self.error_popup = True
                ep = Error_Popup(
                    lang.pack['Files']['File_Options']['Folder_Error']
                    ['Title'],
                    lang.pack['Files']['File_Options']['Folder_Error']['Body'])
                ep.show()

        def create_keyboard(
                default_text=lang.pack['Files']['Keyboard']['New_Folder']):
            #populate a screen with a keyboard as the object
            KeyboardInput_file_bb(keyboard_callback=get_keyboard_data,
                                  back_destination=current_screen,
                                  default_text=default_text)

        def get_keyboard_data(folder_name):
            def rename():
                create_keyboard(default_text=folder_name)

            def make_new_folder_action():
                try:
                    self.storage.add_folder(destination + '/' + folder_name,
                                            ignore_existing=True)
                except Exception as e:
                    Logger.info("!!!!!!!!!!!!!!!!!!!!! Error!")
                    traceback.print_exc()
                    error_out()

                #return file options to root options
                while self.return_to_previous_list():
                    pass

                #go back to file explorer
                file_callback()

            if self.storage.folder_exists(destination + '/' + folder_name):

                title = lang.pack['Files']['Keyboard']['Error']['Title']
                body_text = lang.pack['Files']['Keyboard']['Error']['Body']
                option1 = lang.pack['Files']['Keyboard']['Error']['Rename']
                option2 = lang.pack['Files']['Keyboard']['Error']['Cancel']
                modal_screen = Modal_Question_No_Title(body_text, option1,
                                                       option2, rename,
                                                       back_function)

                #make screen
                roboprinter.screen_controls.set_screen_content(
                    modal_screen,
                    title,
                    back_function=back_function,
                    option_function='no_option')
            else:
                make_new_folder_action()

        create_keyboard()