예제 #1
0
class TestProjectManager(TestCase):
    def setUp(self):
        self.manager = ProjectManager(None)
        self.listener = ProjectManagerListener(self.manager)
        self.signals = self.listener.signals

    def testLoadProjectFailedUnknownFormat(self):
        """
        Check that new-project-failed is emitted when we don't have a suitable
        formatter.
        """
        uri = "file:///Untitled.meh"
        self.manager.loadProject(uri)

        # loading
        name, args = self.signals[0]
        self.assertEqual(uri, args[0], self.signals)

        # failed
        name, args = self.signals[1]
        self.assertEqual("new-project-failed", name)
        signalUri, unused_message = args
        self.assertEqual(uri, signalUri, self.signals)

    def testLoadProjectClosesCurrent(self):
        """
        Check that new-project-failed is emited if we can't close the current
        project instance.
        """
        state = {"tried-close": False}

        def close():
            state["tried-close"] = True
            return False

        self.manager.closeRunningProject = close

        uri = "file:///Untitled.xptv"
        self.manager.current_project = MockProject()
        self.manager.loadProject(uri)

        self.assertEqual(0, len(self.signals))
        self.assertTrue(state["tried-close"], self.signals)

    def testLoadProject(self):
        self.manager.newBlankProject()

        name, args = self.signals[0]
        self.assertEqual("new-project-loading", name, self.signals)

        name, args = self.signals[1]
        self.assertEqual("new-project-created", name, self.signals)

        name, args = self.signals[2]
        self.assertEqual("new-project-loaded", name, self.signals)

    def testMissingUriForwarded(self):
        def quit(mainloop):
            mainloop.quit()

        def missingUriCb(self, project, error, clip_asset, mainloop, result):
            print(project, error, clip_asset, mainloop, result)
            result[0] = True
            mainloop.quit()

        self.mainloop = GLib.MainLoop()

        result = [False]
        self.manager.connect("missing-uri", missingUriCb, self.mainloop,
                             result)

        # Load a project with a missing asset.
        unused, xges_path = tempfile.mkstemp()
        with open(xges_path, "w") as xges:
            xges.write("""<ges version='0.1'>
  <project>
    <ressources>
      <asset id='file:///icantpossiblyexist.png' extractable-type-name='GESUriClip' />
    </ressources>
    <timeline>
      <track caps='video/x-raw' track-type='4' track-id='0' />
      <layer priority='0'>
        <clip id='0' asset-id='file:///icantpossiblyexist.png' type-name='GESUriClip' layer-priority='0' track-types='4' start='0' duration='2590000000' inpoint='0' rate='0' />
      </layer>
    </timeline>
</project>
</ges>""")
        uri = "file://%s" % xges_path
        try:
            self.assertTrue(self.manager.loadProject(uri))

            GLib.timeout_add_seconds(5, quit, self.mainloop)
            self.mainloop.run()
            self.assertTrue(result[0], "missing not missing")
        finally:
            os.remove(xges_path)

    def testCloseRunningProjectNoProject(self):
        self.assertTrue(self.manager.closeRunningProject())
        self.assertFalse(self.signals)

    def testCloseRunningProjectRefuseFromSignal(self):
        def closing(manager, project):
            return False

        self.manager.current_project = MockProject()
        self.manager.current_project.uri = "file:///ciao"
        self.manager.connect("closing-project", closing)

        self.assertFalse(self.manager.closeRunningProject())
        self.assertEqual(1, len(self.signals))
        name, args = self.signals[0]
        self.assertEqual("closing-project", name)
        project = args[0]
        self.assertTrue(project is self.manager.current_project)

    def testCloseRunningProject(self):
        current = self.manager.current_project = MockProject()
        self.assertTrue(self.manager.closeRunningProject())
        self.assertEqual(2, len(self.signals))

        name, args = self.signals[0]
        self.assertEqual("closing-project", name)
        project = args[0]
        self.assertTrue(project is current)

        name, args = self.signals[1]
        self.assertEqual("project-closed", name)
        project = args[0]
        self.assertTrue(project is current)

        self.assertTrue(self.manager.current_project is None)

    def testNewBlankProjectCantCloseCurrent(self):
        def closing(manager, project):
            return False

        self.manager.current_project = MockProject()
        self.manager.current_project.uri = "file:///ciao"
        self.manager.connect("closing-project", closing)
        self.assertFalse(self.manager.newBlankProject())
        self.assertEqual(1, len(self.signals))
        signal, args = self.signals[0]
        self.assertEqual("closing-project", signal)

    def testNewBlankProject(self):
        self.assertTrue(self.manager.newBlankProject())
        self.assertEqual(3, len(self.signals))

        name, args = self.signals[0]
        self.assertEqual("new-project-loading", name)
        uri = args[0]
        self.assertTrue(uri is None)

        name, args = self.signals[1]
        self.assertEqual("new-project-created", name)
        project = args[0]
        self.assertEqual(uri, project.uri)

        name, args = self.signals[2]
        self.assertEqual("new-project-loaded", name)
        project = args[0]
        self.assertTrue(project is self.manager.current_project)

    def testSaveProject(self):
        self.assertTrue(self.manager.newBlankProject())

        unused, path = tempfile.mkstemp(suffix=".xges")
        unused, path2 = tempfile.mkstemp(suffix=".xges")
        try:
            uri = "file://" + os.path.abspath(path)
            uri2 = "file://" + os.path.abspath(path2)

            # Save the project.
            self.assertTrue(self.manager.saveProject(uri=uri, backup=False))
            self.assertTrue(uri_is_reachable(uri))

            # Wait a bit.
            time.sleep(0.1)

            # Save the project at a new location.
            self.assertTrue(self.manager.saveProject(uri2, backup=False))
            self.assertTrue(uri_is_reachable(uri2))

            # Make sure the old path and the new path have different mtimes.
            mtime = os.path.getmtime(path)
            mtime2 = os.path.getmtime(path2)
            self.assertLess(mtime, mtime2)

            # Wait a bit more.
            time.sleep(0.1)

            # Save project again under the new path (by omitting uri arg)
            self.assertTrue(self.manager.saveProject(backup=False))

            # regression test for bug 594396
            # make sure we didn't save to the old URI
            self.assertEqual(mtime, os.path.getmtime(path))
            # make sure we did save to the new URI
            self.assertLess(mtime2, os.path.getmtime(path2))
        finally:
            os.remove(path)
            os.remove(path2)

    def testMakeBackupUri(self):
        uri = "file:///tmp/x.xges"
        self.assertEqual(uri + "~", self.manager._makeBackupURI(uri))

    def testBackupProject(self):
        self.manager.newBlankProject()

        # Assign an uri to the project where it's saved by default.
        unused, xges_path = tempfile.mkstemp(suffix=".xges")
        uri = "file://" + os.path.abspath(xges_path)
        self.manager.current_project.uri = uri
        # This is where the automatic backup file is saved.
        backup_uri = self.manager._makeBackupURI(uri)

        # Save the backup
        self.assertTrue(
            self.manager.saveProject(self.manager.current_project,
                                     backup=True))
        self.assertTrue(uri_is_reachable(backup_uri))

        self.manager.closeRunningProject()
        self.assertFalse(uri_is_reachable(backup_uri),
                         "Backup file not deleted when project closed")
예제 #2
0
class TestProjectManager(common.TestCase):
    def setUp(self):
        super(TestProjectManager, self).setUp()
        app = mock.MagicMock()
        self.manager = ProjectManager(app)
        self.listener = ProjectManagerListener(self.manager)
        self.signals = self.listener.signals

    def testLoadProjectFailedUnknownFormat(self):
        """
        Check that new-project-failed is emitted when we don't have a suitable
        formatter.
        """
        uri = "file:///Untitled.meh"
        self.manager.loadProject(uri)

        # loading
        name, args = self.signals[0]
        self.assertEqual(uri, args[0].get_uri(), self.signals)

        # failed
        name, args = self.signals[1]
        self.assertEqual("new-project-failed", name)
        signalUri, unused_message = args
        self.assertEqual(uri, signalUri, self.signals)

    def testLoadProject(self):
        self.manager.newBlankProject()

        name, args = self.signals[0]
        self.assertEqual("new-project-loading", name, self.signals)

        name, args = self.signals[1]
        self.assertEqual("new-project-created", name, self.signals)

        name, args = self.signals[2]
        self.assertEqual("new-project-loaded", name, self.signals)

    def testMissingUriForwarded(self):
        mainloop = common.create_main_loop()

        def missingUriCb(self, project, error, clip_asset, result):
            result[0] = True
            mainloop.quit()

        result = [False]
        self.manager.connect("missing-uri", missingUriCb, result)

        with common.cloned_sample():
            asset_uri = common.get_sample_uri("missing.png")
            with common.created_project_file(asset_uri) as uri:
                self.assertTrue(self.manager.loadProject(uri))
                mainloop.run()
        self.assertTrue(result[0], "missing-uri has not been emitted")

    def testLoaded(self):
        mainloop = common.create_main_loop()

        def new_project_loaded_cb(project_manager, project):
            mainloop.quit()

        self.manager.connect("new-project-loaded", new_project_loaded_cb)

        with common.cloned_sample("flat_colour1_640x480.png"):
            asset_uri = common.get_sample_uri("flat_colour1_640x480.png")
            with common.created_project_file(asset_uri=asset_uri) as uri:
                self.assertTrue(self.manager.loadProject(uri))
                mainloop.run()

        project = self.manager.current_project
        self.assertFalse(project.at_least_one_asset_missing)
        self.assertTrue(project.loaded)
        self.assertFalse(project.hasUnsavedModifications())

    def testCloseRunningProjectNoProject(self):
        self.assertTrue(self.manager.closeRunningProject())
        self.assertFalse(self.signals)

    def testCloseRunningProjectRefuseFromSignal(self):
        def closing(manager, project):
            return False

        self.manager.current_project = mock.Mock()
        self.manager.current_project.uri = "file:///ciao"
        self.manager.connect("closing-project", closing)

        self.assertFalse(self.manager.closeRunningProject())
        self.assertEqual(1, len(self.signals))
        name, args = self.signals[0]
        self.assertEqual("closing-project", name)
        project = args[0]
        self.assertTrue(project is self.manager.current_project)

    def testCloseRunningProject(self):
        current = mock.Mock()
        current.uri = None
        self.manager.current_project = current
        self.assertTrue(self.manager.closeRunningProject())
        self.assertEqual(2, len(self.signals))

        name, args = self.signals[0]
        self.assertEqual("closing-project", name)
        project = args[0]
        self.assertTrue(project is current)

        name, args = self.signals[1]
        self.assertEqual("project-closed", name)
        project = args[0]
        self.assertTrue(project is current)

        self.assertTrue(self.manager.current_project is None)

    def testNewBlankProject(self):
        self.assertTrue(self.manager.newBlankProject())
        self.assertEqual(3, len(self.signals))

        name, args = self.signals[0]
        self.assertEqual("new-project-loading", name)
        project = args[0]
        self.assertTrue(project.get_uri() is None)

        name, args = self.signals[1]
        self.assertEqual("new-project-created", name)
        project = args[0]
        self.assertEqual(project.get_uri(), project.uri)

        name, args = self.signals[2]
        self.assertEqual("new-project-loaded", name)
        project = args[0]
        self.assertTrue(project is self.manager.current_project)

    def testSaveProject(self):
        self.assertTrue(self.manager.newBlankProject())

        unused, path = tempfile.mkstemp(suffix=".xges")
        unused, path2 = tempfile.mkstemp(suffix=".xges")
        try:
            uri = "file://" + os.path.abspath(path)
            uri2 = "file://" + os.path.abspath(path2)

            # Save the project.
            self.assertTrue(self.manager.saveProject(uri=uri, backup=False))
            self.assertTrue(os.path.isfile(path))

            # Wait a bit.
            time.sleep(0.1)

            # Save the project at a new location.
            self.assertTrue(self.manager.saveProject(uri2, backup=False))
            self.assertTrue(os.path.isfile(path2))

            # Make sure the old path and the new path have different mtimes.
            mtime = os.path.getmtime(path)
            mtime2 = os.path.getmtime(path2)
            self.assertLess(mtime, mtime2)

            # Wait a bit more.
            time.sleep(0.1)

            # Save project again under the new path (by omitting uri arg)
            self.assertTrue(self.manager.saveProject(backup=False))

            # regression test for bug 594396
            # make sure we didn't save to the old URI
            self.assertEqual(mtime, os.path.getmtime(path))
            # make sure we did save to the new URI
            self.assertLess(mtime2, os.path.getmtime(path2))
        finally:
            os.remove(path)
            os.remove(path2)

    def testMakeBackupUri(self):
        uri = "file:///tmp/x.xges"
        self.assertEqual(uri + "~", self.manager._makeBackupURI(uri))

    def testBackupProject(self):
        self.manager.newBlankProject()

        # Assign an uri to the project where it's saved by default.
        unused, xges_path = tempfile.mkstemp(suffix=".xges")
        uri = "file://" + os.path.abspath(xges_path)
        self.manager.current_project.uri = uri
        # This is where the automatic backup file is saved.
        backup_uri = self.manager._makeBackupURI(uri)

        # Save the backup
        self.assertTrue(
            self.manager.saveProject(self.manager.current_project,
                                     backup=True))
        self.assertTrue(os.path.isfile(path_from_uri(backup_uri)))

        self.manager.closeRunningProject()
        self.assertFalse(os.path.isfile(path_from_uri(backup_uri)),
                         "Backup file not deleted when project closed")
예제 #3
0
class TestProjectManager(TestCase):

    def setUp(self):
        self.manager = ProjectManager(None)
        self.listener = ProjectManagerListener(self.manager)
        self.signals = self.listener.signals

    def testLoadProjectFailedUnknownFormat(self):
        """
        Check that new-project-failed is emitted when we don't have a suitable
        formatter.
        """
        uri = "file:///Untitled.meh"
        self.manager.loadProject(uri)

        # loading
        name, args = self.signals[0]
        self.assertEqual(uri, args[0], self.signals)

        # failed
        name, args = self.signals[1]
        self.assertEqual("new-project-failed", name)
        signalUri, unused_message = args
        self.assertEqual(uri, signalUri, self.signals)

    def testLoadProjectClosesCurrent(self):
        """
        Check that new-project-failed is emited if we can't close the current
        project instance.
        """
        state = {"tried-close": False}

        def close():
            state["tried-close"] = True
            return False
        self.manager.closeRunningProject = close

        uri = "file:///Untitled.xptv"
        self.manager.current_project = MockProject()
        self.manager.loadProject(uri)

        self.assertEqual(0, len(self.signals))
        self.assertTrue(state["tried-close"], self.signals)

    def testLoadProject(self):
        self.manager.newBlankProject()

        name, args = self.signals[0]
        self.assertEqual("new-project-loading", name, self.signals)

        name, args = self.signals[1]
        self.assertEqual("new-project-created", name, self.signals)

        name, args = self.signals[2]
        self.assertEqual("new-project-loaded", name, self.signals)

    def testMissingUriForwarded(self):
        def quit(mainloop):
            mainloop.quit()

        def missingUriCb(self, project, error, clip_asset, mainloop, result):
            print(project, error, clip_asset, mainloop, result)
            result[0] = True
            mainloop.quit()

        self.mainloop = GLib.MainLoop()

        result = [False]
        self.manager.connect(
            "missing-uri", missingUriCb, self.mainloop, result)

        # Load a project with a missing asset.
        unused, xges_path = tempfile.mkstemp()
        with open(xges_path, "w") as xges:
            xges.write("""<ges version='0.1'>
  <project>
    <ressources>
      <asset id='file:///icantpossiblyexist.png' extractable-type-name='GESUriClip' />
    </ressources>
    <timeline>
      <track caps='video/x-raw' track-type='4' track-id='0' />
      <layer priority='0'>
        <clip id='0' asset-id='file:///icantpossiblyexist.png' type-name='GESUriClip' layer-priority='0' track-types='4' start='0' duration='2590000000' inpoint='0' rate='0' />
      </layer>
    </timeline>
</project>
</ges>""")
        uri = "file://%s" % xges_path
        try:
            self.assertTrue(self.manager.loadProject(uri))

            GLib.timeout_add_seconds(5, quit, self.mainloop)
            self.mainloop.run()
            self.assertTrue(result[0], "missing not missing")
        finally:
            os.remove(xges_path)

    def testCloseRunningProjectNoProject(self):
        self.assertTrue(self.manager.closeRunningProject())
        self.assertFalse(self.signals)

    def testCloseRunningProjectRefuseFromSignal(self):
        def closing(manager, project):
            return False

        self.manager.current_project = MockProject()
        self.manager.current_project.uri = "file:///ciao"
        self.manager.connect("closing-project", closing)

        self.assertFalse(self.manager.closeRunningProject())
        self.assertEqual(1, len(self.signals))
        name, args = self.signals[0]
        self.assertEqual("closing-project", name)
        project = args[0]
        self.assertTrue(project is self.manager.current_project)

    def testCloseRunningProject(self):
        current = self.manager.current_project = MockProject()
        self.assertTrue(self.manager.closeRunningProject())
        self.assertEqual(2, len(self.signals))

        name, args = self.signals[0]
        self.assertEqual("closing-project", name)
        project = args[0]
        self.assertTrue(project is current)

        name, args = self.signals[1]
        self.assertEqual("project-closed", name)
        project = args[0]
        self.assertTrue(project is current)

        self.assertTrue(self.manager.current_project is None)

    def testNewBlankProjectCantCloseCurrent(self):
        def closing(manager, project):
            return False

        self.manager.current_project = MockProject()
        self.manager.current_project.uri = "file:///ciao"
        self.manager.connect("closing-project", closing)
        self.assertFalse(self.manager.newBlankProject())
        self.assertEqual(1, len(self.signals))
        signal, args = self.signals[0]
        self.assertEqual("closing-project", signal)

    def testNewBlankProject(self):
        self.assertTrue(self.manager.newBlankProject())
        self.assertEqual(3, len(self.signals))

        name, args = self.signals[0]
        self.assertEqual("new-project-loading", name)
        uri = args[0]
        self.assertTrue(uri is None)

        name, args = self.signals[1]
        self.assertEqual("new-project-created", name)
        project = args[0]
        self.assertEqual(uri, project.uri)

        name, args = self.signals[2]
        self.assertEqual("new-project-loaded", name)
        project = args[0]
        self.assertTrue(project is self.manager.current_project)

    def testSaveProject(self):
        self.assertTrue(self.manager.newBlankProject())

        unused, path = tempfile.mkstemp(suffix=".xges")
        unused, path2 = tempfile.mkstemp(suffix=".xges")
        try:
            uri = "file://" + os.path.abspath(path)
            uri2 = "file://" + os.path.abspath(path2)

            # Save the project.
            self.assertTrue(self.manager.saveProject(uri=uri, backup=False))
            self.assertTrue(uri_is_reachable(uri))

            # Wait a bit.
            time.sleep(0.1)

            # Save the project at a new location.
            self.assertTrue(self.manager.saveProject(uri2, backup=False))
            self.assertTrue(uri_is_reachable(uri2))

            # Make sure the old path and the new path have different mtimes.
            mtime = os.path.getmtime(path)
            mtime2 = os.path.getmtime(path2)
            self.assertLess(mtime, mtime2)

            # Wait a bit more.
            time.sleep(0.1)

            # Save project again under the new path (by omitting uri arg)
            self.assertTrue(self.manager.saveProject(backup=False))

            # regression test for bug 594396
            # make sure we didn't save to the old URI
            self.assertEqual(mtime, os.path.getmtime(path))
            # make sure we did save to the new URI
            self.assertLess(mtime2, os.path.getmtime(path2))
        finally:
            os.remove(path)
            os.remove(path2)

    def testMakeBackupUri(self):
        uri = "file:///tmp/x.xges"
        self.assertEqual(uri + "~", self.manager._makeBackupURI(uri))

    def testBackupProject(self):
        self.manager.newBlankProject()

        # Assign an uri to the project where it's saved by default.
        unused, xges_path = tempfile.mkstemp(suffix=".xges")
        uri = "file://" + os.path.abspath(xges_path)
        self.manager.current_project.uri = uri
        # This is where the automatic backup file is saved.
        backup_uri = self.manager._makeBackupURI(uri)

        # Save the backup
        self.assertTrue(self.manager.saveProject(
            self.manager.current_project, backup=True))
        self.assertTrue(uri_is_reachable(backup_uri))

        self.manager.closeRunningProject()
        self.assertFalse(uri_is_reachable(backup_uri),
                         "Backup file not deleted when project closed")
예제 #4
0
class Pitivi(Gtk.Application, Loggable):
    """Hello world.

    Attributes:
        action_log (UndoableActionLog): The undo/redo log for the current project.
        effects (EffectsManager): The effects which can be applied to a clip.
        gui (MainWindow): The main window of the app.
        project_manager (ProjectManager): The holder of the current project.
        settings (GlobalSettings): The application-wide settings.
        system (pitivi.utils.system.System): The system running the app.
    """

    __gsignals__ = {
        "version-info-received": (GObject.SIGNAL_RUN_LAST, None, (object,))
    }

    def __init__(self):
        Gtk.Application.__init__(self,
                                 application_id="org.pitivi",
                                 flags=Gio.ApplicationFlags.HANDLES_OPEN)
        Loggable.__init__(self)

        self.settings = None
        self.threads = None
        self.effects = None
        self.system = None
        self.project_manager = ProjectManager(self)

        self.action_log = None
        self.project_observer = None
        self._last_action_time = Gst.util_get_timestamp()

        self.gui = None
        self.__welcome_wizard = None

        self._version_information = {}

        self._scenario_file = None
        self._first_action = True

        Zoomable.app = self
        self.shortcuts = ShortcutsManager(self)

    def write_action(self, action, **kwargs):
        if self._scenario_file is None:
            return

        if self._first_action:
            self._scenario_file.write(
                "description, seek=true, handles-states=true\n")
            self._first_action = False

        now = Gst.util_get_timestamp()
        if now - self._last_action_time > 0.05 * Gst.SECOND:
            # We need to make sure that the waiting time was more than 50 ms.
            st = Gst.Structure.new_empty("wait")
            st["duration"] = float((now - self._last_action_time) / Gst.SECOND)
            self._scenario_file.write(st.to_string() + "\n")
            self._last_action_time = now

        if not isinstance(action, Gst.Structure):
            structure = Gst.Structure.new_empty(action)

            for key, value in kwargs.items():
                key = key.replace("_", "-")
                structure[key] = value

            action = structure

        self._scenario_file.write(action.to_string() + "\n")
        self._scenario_file.flush()

    def do_startup(self):
        Gtk.Application.do_startup(self)

        # Init logging as early as possible so we can log startup code
        enable_color = not os.environ.get(
            'PITIVI_DEBUG_NO_COLOR', '0') in ('', '1')
        # Let's show a human-readable Pitivi debug output by default, and only
        # show a crazy unreadable mess when surrounded by gst debug statements.
        enable_crack_output = "GST_DEBUG" in os.environ
        loggable.init('PITIVI_DEBUG', enable_color, enable_crack_output)

        self.info('starting up')
        self._setup()
        self._checkVersion()

    def _setup(self):
        self.settings = GlobalSettings()
        self.threads = ThreadMaster()
        self.effects = EffectsManager()
        self.proxy_manager = ProxyManager(self)
        self.system = get_system()

        self.project_manager.connect(
            "new-project-loading", self._newProjectLoadingCb)
        self.project_manager.connect(
            "new-project-loaded", self._newProjectLoaded)
        self.project_manager.connect("project-closed", self._projectClosed)

        self._createActions()
        self._syncDoUndo()

    def _createActions(self):
        self.shortcuts.register_group("app", _("General"), position=10)
        self.undo_action = Gio.SimpleAction.new("undo", None)
        self.undo_action.connect("activate", self._undoCb)
        self.add_action(self.undo_action)
        self.shortcuts.add("app.undo", ["<Primary>z"],
                           _("Undo the most recent action"))

        self.redo_action = Gio.SimpleAction.new("redo", None)
        self.redo_action.connect("activate", self._redoCb)
        self.add_action(self.redo_action)
        self.shortcuts.add("app.redo", ["<Primary><Shift>z"],
                           _("Redo the most recent action"))

        self.quit_action = Gio.SimpleAction.new("quit", None)
        self.quit_action.connect("activate", self._quitCb)
        self.add_action(self.quit_action)
        self.shortcuts.add("app.quit", ["<Primary>q"], _("Quit"))

        self.show_shortcuts_action = Gio.SimpleAction.new("shortcuts_window", None)
        self.show_shortcuts_action.connect("activate", self._show_shortcuts_cb)
        self.add_action(self.show_shortcuts_action)
        self.shortcuts.add("app.shortcuts_window",
                           ["<Primary>F1", "<Primary>question"],
                           _("Show the Shortcuts Window"))

    def do_activate(self):
        if self.gui:
            # The app is already started and the window already created.
            # Present the already existing window.
            if self.system.has_x11():
                # TODO: Use present() instead of present_with_time() when
                # https://bugzilla.gnome.org/show_bug.cgi?id=688830 is fixed.
                from gi.repository import GdkX11
                x11_server_time = GdkX11.x11_get_server_time(self.gui.get_window())
                self.gui.present_with_time(x11_server_time)
            else:
                # On Wayland or Quartz (Mac OS X) backend there is no GdkX11,
                # so just use present() directly here.
                self.gui.present()
            # No need to show the welcome wizard.
            return
        self.createMainWindow()
        self.welcome_wizard.show()

    @property
    def welcome_wizard(self):
        if not self.__welcome_wizard:
            self.__welcome_wizard = StartUpWizard(self)
        return self.__welcome_wizard

    def createMainWindow(self):
        if self.gui:
            return
        self.gui = MainWindow(self)
        self.add_window(self.gui)
        self.gui.checkScreenConstraints()
        # We might as well show it.
        self.gui.show()

    def do_open(self, giofiles, unused_count, unused_hint):
        assert giofiles
        self.createMainWindow()
        if len(giofiles) > 1:
            self.warning(
                "Can open only one project file at a time. Ignoring the rest!")
        project_file = giofiles[0]
        self.project_manager.loadProject(quote_uri(project_file.get_uri()))
        return True

    def shutdown(self):
        """Closes the app.

        Returns:
            bool: True if successful, False otherwise.
        """
        self.debug("shutting down")
        # Refuse to close if we are not done with the current project.
        if not self.project_manager.closeRunningProject():
            self.warning(
                "Not closing since running project doesn't want to close")
            return False
        if self.welcome_wizard:
            self.welcome_wizard.hide()
        if self.gui:
            self.gui.destroy()
        self.threads.stopAllThreads()
        self.settings.storeSettings()
        self.quit()
        return True

    def _setScenarioFile(self, uri):
        if uri:
            project_path = path_from_uri(uri)
        else:
            # New project.
            project_path = None
        if 'PITIVI_SCENARIO_FILE' in os.environ:
            scenario_path = os.environ['PITIVI_SCENARIO_FILE']
        else:
            cache_dir = get_dir(os.path.join(xdg_cache_home(), "scenarios"))
            scenario_name = str(time.strftime("%Y%m%d-%H%M%S"))
            if project_path:
                scenario_name += os.path.splitext(project_path.replace(os.sep, "_"))[0]
            scenario_path = os.path.join(cache_dir, scenario_name + ".scenario")

        scenario_path = path_from_uri(quote_uri(scenario_path))
        self._scenario_file = open(scenario_path, "w")

        if project_path and not project_path.endswith(".scenario"):
            # It's an xges file probably.
            with open(project_path) as project:
                content = project.read().replace("\n", "")
                self.write_action("load-project",
                                  serialized_content=content)

    def _newProjectLoadingCb(self, unused_project_manager, project):
        self._setScenarioFile(project.get_uri())

    def _newProjectLoaded(self, unused_project_manager, project):
        self.action_log = UndoableActionLog()
        self.action_log.connect("pre-push", self._action_log_pre_push_cb)
        self.action_log.connect("commit", self._actionLogCommit)
        self.action_log.connect("move", self._action_log_move_cb)

        self.project_observer = ProjectObserver(project, self.action_log)

    def _projectClosed(self, unused_project_manager, project):
        if project.loaded:
            self.action_log = None
            self._syncDoUndo()

        if self._scenario_file:
            self.write_action("stop")
            self._scenario_file.close()
            self._scenario_file = None

    def _checkVersion(self):
        """Checks online for new versions of the app."""
        self.info("Requesting version information async")
        giofile = Gio.File.new_for_uri(RELEASES_URL)
        giofile.load_contents_async(None, self._versionInfoReceivedCb, None)

    def _versionInfoReceivedCb(self, giofile, result, user_data):
        try:
            raw = giofile.load_contents_finish(result)[1]
            if not isinstance(raw, str):
                raw = raw.decode()
            raw = raw.split("\n")
            # Split line at '=' if the line is not empty or a comment line
            data = [element.split("=") for element in raw
                    if element and not element.startswith("#")]

            # search newest version and status
            status = "UNSUPPORTED"
            current_version = None
            for version, version_status in data:
                if VERSION == version:
                    status = version_status
                if version_status.upper() == "CURRENT":
                    # This is the latest.
                    current_version = version
                    self.info("Latest software version is %s", current_version)

            VERSION_split = [int(i) for i in VERSION.split(".")]
            current_version_split = [int(i)
                                     for i in current_version.split(".")]
            if VERSION_split > current_version_split:
                status = "CURRENT"
                self.info(
                    "Running version %s, which is newer than the latest known version. Considering it as the latest current version.", VERSION)
            elif status is "UNSUPPORTED":
                self.warning(
                    "Using an outdated version of Pitivi (%s)", VERSION)

            self._version_information["current"] = current_version
            self._version_information["status"] = status
            self.emit("version-info-received", self._version_information)
        except Exception as e:
            self.warning("Version info could not be read: %s", e)

    def isLatest(self):
        """Whether the app's version is the latest as far as we know."""
        status = self._version_information.get("status")
        return status is None or status.upper() == "CURRENT"

    def getLatest(self):
        """Get the latest version of the app or None."""
        return self._version_information.get("current")

    def _quitCb(self, unused_action, unused_param):
        self.shutdown()

    def _undoCb(self, unused_action, unused_param):
        self.action_log.undo()

    def _redoCb(self, unused_action, unused_param):
        self.action_log.redo()

    def _show_shortcuts_cb(self, unused_action, unused_param):
        show_shortcuts(self)

    def _action_log_pre_push_cb(self, unused_action_log, action):
        try:
            st = action.asScenarioAction()
        except NotImplementedError:
            self.warning("No serialization method for action %s", action)
            return
        if st:
            self.write_action(st)

    def _actionLogCommit(self, action_log, unused_stack):
        if action_log.is_in_transaction():
            return
        self._syncDoUndo()

    def _action_log_move_cb(self, action_log, unused_stack):
        self._syncDoUndo()

    def _syncDoUndo(self):
        can_undo = self.action_log and bool(self.action_log.undo_stacks)
        self.undo_action.set_enabled(bool(can_undo))

        can_redo = self.action_log and bool(self.action_log.redo_stacks)
        self.redo_action.set_enabled(bool(can_redo))

        if not self.project_manager.current_project:
            return

        dirty = self.action_log and self.action_log.dirty()
        self.project_manager.current_project.setModificationState(dirty)
        # In the tests we do not want to create any gui
        if self.gui is not None:
            self.gui.showProjectStatus()
예제 #5
0
class Pitivi(Gtk.Application, Loggable):
    """Hello world.

    Attributes:
        action_log (UndoableActionLog): The undo/redo log for the current project.
        effects (EffectsManager): The effects which can be applied to a clip.
        gui (MainWindow): The main window of the app.
        project_manager (ProjectManager): The holder of the current project.
        settings (GlobalSettings): The application-wide settings.
        system (pitivi.utils.system.System): The system running the app.
    """

    __gsignals__ = {
        "version-info-received": (GObject.SIGNAL_RUN_LAST, None, (object, ))
    }

    def __init__(self):
        Gtk.Application.__init__(self,
                                 application_id="org.pitivi.Pitivi",
                                 flags=Gio.ApplicationFlags.HANDLES_OPEN)
        Loggable.__init__(self)

        self.settings = None
        self.threads = None
        self.effects = None
        self.system = None
        self.project_manager = ProjectManager(self)

        self.action_log = None
        self.project_observer = None
        self._last_action_time = Gst.util_get_timestamp()

        self.gui = None
        self.__welcome_wizard = None
        self.__inhibit_cookies = {}

        self._version_information = {}

        self._scenario_file = None
        self._first_action = True

        Zoomable.app = self
        self.shortcuts = ShortcutsManager(self)

    def write_action(self, action, **kwargs):
        if self._scenario_file is None:
            return

        if self._first_action:
            self._scenario_file.write(
                "description, seek=true, handles-states=true\n")
            self._first_action = False

        now = Gst.util_get_timestamp()
        if now - self._last_action_time > 0.05 * Gst.SECOND:
            # We need to make sure that the waiting time was more than 50 ms.
            st = Gst.Structure.new_empty("wait")
            st["duration"] = float((now - self._last_action_time) / Gst.SECOND)
            self._scenario_file.write(st.to_string() + "\n")
            self._last_action_time = now

        if not isinstance(action, Gst.Structure):
            structure = Gst.Structure.new_empty(action)

            for key, value in kwargs.items():
                key = key.replace("_", "-")
                structure[key] = value

            action = structure

        self._scenario_file.write(action.to_string() + "\n")
        self._scenario_file.flush()

    def do_startup(self):
        Gtk.Application.do_startup(self)

        # Init logging as early as possible so we can log startup code
        enable_color = not os.environ.get('PITIVI_DEBUG_NO_COLOR',
                                          '0') in ('', '1')
        # Let's show a human-readable Pitivi debug output by default, and only
        # show a crazy unreadable mess when surrounded by gst debug statements.
        enable_crack_output = "GST_DEBUG" in os.environ
        loggable.init('PITIVI_DEBUG', enable_color, enable_crack_output)

        self.info('starting up')
        self._setup()
        self._checkVersion()

    def _setup(self):
        self.settings = GlobalSettings()
        self.threads = ThreadMaster()
        self.effects = EffectsManager()
        self.proxy_manager = ProxyManager(self)
        self.system = get_system()
        self.plugin_manager = PluginManager(self)

        self.project_manager.connect("new-project-loading",
                                     self._newProjectLoadingCb)
        self.project_manager.connect("new-project-loaded",
                                     self._newProjectLoaded)
        self.project_manager.connect("project-closed", self._projectClosed)

        self._createActions()
        self._syncDoUndo()

    def _createActions(self):
        self.shortcuts.register_group("app", _("General"), position=10)
        self.undo_action = Gio.SimpleAction.new("undo", None)
        self.undo_action.connect("activate", self._undoCb)
        self.add_action(self.undo_action)
        self.shortcuts.add("app.undo", ["<Primary>z"],
                           _("Undo the most recent action"))

        self.redo_action = Gio.SimpleAction.new("redo", None)
        self.redo_action.connect("activate", self._redoCb)
        self.add_action(self.redo_action)
        self.shortcuts.add("app.redo", ["<Primary><Shift>z"],
                           _("Redo the most recent action"))

        self.quit_action = Gio.SimpleAction.new("quit", None)
        self.quit_action.connect("activate", self._quitCb)
        self.add_action(self.quit_action)
        self.shortcuts.add("app.quit", ["<Primary>q"], _("Quit"))

        self.show_shortcuts_action = Gio.SimpleAction.new(
            "shortcuts_window", None)
        self.show_shortcuts_action.connect("activate", self._show_shortcuts_cb)
        self.add_action(self.show_shortcuts_action)
        self.shortcuts.add("app.shortcuts_window",
                           ["<Primary>F1", "<Primary>question"],
                           _("Show the Shortcuts Window"))

    def do_activate(self):
        if self.gui:
            # The app is already started and the window already created.
            # Present the already existing window.
            if self.system.has_x11():
                # TODO: Use present() instead of present_with_time() when
                # https://bugzilla.gnome.org/show_bug.cgi?id=688830 is fixed.
                from gi.repository import GdkX11
                x11_server_time = GdkX11.x11_get_server_time(
                    self.gui.get_window())
                self.gui.present_with_time(x11_server_time)
            else:
                # On Wayland or Quartz (Mac OS X) backend there is no GdkX11,
                # so just use present() directly here.
                self.gui.present()
            # No need to show the welcome wizard.
            return
        self.createMainWindow()
        self.welcome_wizard.show()

    @property
    def welcome_wizard(self):
        if not self.__welcome_wizard:
            self.__welcome_wizard = StartUpWizard(self)
        return self.__welcome_wizard

    def createMainWindow(self):
        if self.gui:
            return
        self.gui = MainWindow(self)
        self.add_window(self.gui)
        self.gui.checkScreenConstraints()
        # We might as well show it.
        self.gui.show()

    def do_open(self, giofiles, unused_count, unused_hint):
        assert giofiles
        self.createMainWindow()
        if len(giofiles) > 1:
            self.warning(
                "Can open only one project file at a time. Ignoring the rest!")
        project_file = giofiles[0]
        self.project_manager.loadProject(quote_uri(project_file.get_uri()))
        return True

    def shutdown(self):
        """Closes the app.

        Returns:
            bool: True if successful, False otherwise.
        """
        self.debug("shutting down")
        # Refuse to close if we are not done with the current project.
        if not self.project_manager.closeRunningProject():
            self.warning(
                "Not closing since running project doesn't want to close")
            return False
        if self.welcome_wizard:
            self.welcome_wizard.hide()
        if self.gui:
            self.gui.destroy()
        self.threads.stopAllThreads()
        self.settings.storeSettings()
        self.quit()
        return True

    def _setScenarioFile(self, uri):
        if uri:
            project_path = path_from_uri(uri)
        else:
            # New project.
            project_path = None
        if 'PITIVI_SCENARIO_FILE' in os.environ:
            scenario_path = os.environ['PITIVI_SCENARIO_FILE']
        else:
            cache_dir = get_dir(os.path.join(xdg_cache_home(), "scenarios"))
            scenario_name = str(time.strftime("%Y%m%d-%H%M%S"))
            if project_path:
                scenario_name += os.path.splitext(
                    project_path.replace(os.sep, "_"))[0]
            scenario_path = os.path.join(cache_dir,
                                         scenario_name + ".scenario")

        scenario_path = path_from_uri(quote_uri(scenario_path))
        self._scenario_file = open(scenario_path, "w")

        if project_path and not project_path.endswith(".scenario"):
            # It's an xges file probably.
            with open(project_path) as project:
                content = project.read().replace("\n", "")
                self.write_action("load-project", serialized_content=content)

    def _newProjectLoadingCb(self, unused_project_manager, project):
        self._setScenarioFile(project.get_uri())

    def _newProjectLoaded(self, unused_project_manager, project):
        self.action_log = UndoableActionLog()
        self.action_log.connect("pre-push", self._action_log_pre_push_cb)
        self.action_log.connect("commit", self._actionLogCommit)
        self.action_log.connect("move", self._action_log_move_cb)

        self.project_observer = ProjectObserver(project, self.action_log)

    def _projectClosed(self, unused_project_manager, project):
        if project.loaded:
            self.action_log = None
            self._syncDoUndo()

        if self._scenario_file:
            self.write_action("stop")
            self._scenario_file.close()
            self._scenario_file = None

    def _checkVersion(self):
        """Checks online for new versions of the app."""
        self.info("Requesting version information async")
        giofile = Gio.File.new_for_uri(RELEASES_URL)
        giofile.load_contents_async(None, self._version_info_received_cb, None)

    def _version_info_received_cb(self, giofile, result, user_data):
        try:
            raw = giofile.load_contents_finish(result)[1]
            if not isinstance(raw, str):
                raw = raw.decode()
            raw = raw.split("\n")
            # Split line at '=' if the line is not empty or a comment line
            data = [
                element.split("=") for element in raw
                if element and not element.startswith("#")
            ]

            # search newest version and status
            status = "UNSUPPORTED"
            current_version = None
            for version, version_status in data:
                if VERSION == version:
                    status = version_status
                if version_status.upper() == "CURRENT":
                    # This is the latest.
                    current_version = version
                    self.info("Latest software version is %s", current_version)

            VERSION_split = [int(i) for i in VERSION.split(".")]
            current_version_split = [
                int(i) for i in current_version.split(".")
            ]
            if VERSION_split > current_version_split:
                status = "CURRENT"
                self.info(
                    "Running version %s, which is newer than the latest known version. Considering it as the latest current version.",
                    VERSION)
            elif status is "UNSUPPORTED":
                self.warning("Using an outdated version of Pitivi (%s)",
                             VERSION)

            self._version_information["current"] = current_version
            self._version_information["status"] = status
            self.emit("version-info-received", self._version_information)
        except Exception as e:
            self.warning("Version info could not be read: %s", e)

    def isLatest(self):
        """Whether the app's version is the latest as far as we know."""
        status = self._version_information.get("status")
        return status is None or status.upper() == "CURRENT"

    def getLatest(self):
        """Get the latest version of the app or None."""
        return self._version_information.get("current")

    def _quitCb(self, unused_action, unused_param):
        self.shutdown()

    def _undoCb(self, unused_action, unused_param):
        self.action_log.undo()

    def _redoCb(self, unused_action, unused_param):
        self.action_log.redo()

    def _show_shortcuts_cb(self, unused_action, unused_param):
        show_shortcuts(self)

    def _action_log_pre_push_cb(self, unused_action_log, action):
        try:
            st = action.asScenarioAction()
        except NotImplementedError:
            self.warning("No serialization method for action %s", action)
            return
        if st:
            self.write_action(st)

    def _actionLogCommit(self, action_log, unused_stack):
        if action_log.is_in_transaction():
            return
        self._syncDoUndo()

    def _action_log_move_cb(self, action_log, unused_stack):
        self._syncDoUndo()

    def _syncDoUndo(self):
        can_undo = self.action_log and bool(self.action_log.undo_stacks)
        self.undo_action.set_enabled(bool(can_undo))

        can_redo = self.action_log and bool(self.action_log.redo_stacks)
        self.redo_action.set_enabled(bool(can_redo))

        if not self.project_manager.current_project:
            return

        dirty = self.action_log and self.action_log.dirty()
        self.project_manager.current_project.setModificationState(dirty)
        # In the tests we do not want to create any gui
        if self.gui is not None:
            self.gui.showProjectStatus()

    def simple_inhibit(self, reason, flags):
        """Informs the session manager about actions to be inhibited.

        Keeps track of the reasons received. A specific reason should always
        be accompanied by the same flags. Calling the method a second time
        with the same reason has no effect unless `simple_uninhibit` has been
        called in the meanwhile.

        Args:
            reason (str): The reason for which to perform the inhibition.
            flags (Gtk.ApplicationInhibitFlags): What should be inhibited.
        """
        if reason in self.__inhibit_cookies:
            self.debug("Inhibit reason already active: %s", reason)
            return
        self.debug("Inhibiting %s for %s", flags, reason)
        cookie = self.inhibit(self.gui, flags, reason)
        self.__inhibit_cookies[reason] = cookie

    def simple_uninhibit(self, reason):
        """Informs the session manager that an inhibition is not needed anymore.

        Args:
            reason (str): The reason which is not valid anymore.
        """
        try:
            cookie = self.__inhibit_cookies.pop(reason)
        except KeyError:
            self.debug("Inhibit reason not active: %s", reason)
            return
        self.debug("Uninhibiting %s", reason)
        self.uninhibit(cookie)
예제 #6
0
class Pitivi(Gtk.Application, Loggable):
    """
    Pitivi's application.

    @type effects: L{EffectsManager}
    @ivar gui: The main window of the app.
    @type gui: L{PitiviMainWindow}
    @ivar project_manager: The project manager object used in the application
    @type project_manager: L{ProjectManager}
    @ivar settings: Application-wide settings.
    @type settings: L{GlobalSettings}.
    """

    __gsignals__ = {
        "version-info-received": (GObject.SIGNAL_RUN_LAST, None, (object, ))
    }

    def __init__(self):
        Gtk.Application.__init__(self,
                                 application_id="org.pitivi",
                                 flags=Gio.ApplicationFlags.HANDLES_OPEN)
        Loggable.__init__(self)

        self.settings = None
        self.threads = None
        self.effects = None
        self.system = None
        self.project_manager = ProjectManager(self)

        self.action_log = UndoableActionLog()
        self.timeline_log_observer = None
        self.project_log_observer = None

        self.gui = None
        self.welcome_wizard = None

        self._version_information = {}

        self.connect("startup", self._startupCb)
        self.connect("activate", self._activateCb)
        self.connect("open", self.openCb)

    def _startupCb(self, unused_app):
        # Init logging as early as possible so we can log startup code
        enable_color = not os.environ.get('PITIVI_DEBUG_NO_COLOR',
                                          '0') in ('', '1')
        # Let's show a human-readable Pitivi debug output by default, and only
        # show a crazy unreadable mess when surrounded by gst debug statements.
        enable_crack_output = "GST_DEBUG" in os.environ
        log.init('PITIVI_DEBUG', enable_color, enable_crack_output)

        self.info('starting up')
        self.settings = GlobalSettings()
        self.threads = ThreadMaster()
        self.effects = EffectsManager()
        self.system = getSystem()

        self.action_log.connect("commit", self._actionLogCommit)
        self.action_log.connect("undo", self._actionLogUndo)
        self.action_log.connect("redo", self._actionLogRedo)
        self.action_log.connect("cleaned", self._actionLogCleaned)
        self.timeline_log_observer = TimelineLogObserver(self.action_log)
        self.project_log_observer = ProjectLogObserver(self.action_log)

        self.project_manager.connect("new-project-loaded",
                                     self._newProjectLoaded)
        self.project_manager.connect("project-closed", self._projectClosed)

        self._createActions()
        self._checkVersion()

    def _createActions(self):
        self.undo_action = Gio.SimpleAction.new("undo", None)
        self.undo_action.connect("activate", self._undoCb)
        self.add_action(self.undo_action)
        self.add_accelerator("<Control>z", "app.undo", None)

        self.redo_action = Gio.SimpleAction.new("redo", None)
        self.redo_action.connect("activate", self._redoCb)
        self.add_action(self.redo_action)
        self.add_accelerator("<Control><Shift>z", "app.redo", None)

        self.quit_action = Gio.SimpleAction.new("quit", None)
        self.quit_action.connect("activate", self._quitCb)
        self.add_action(self.quit_action)
        self.add_accelerator("<Control>q", "app.quit", None)

    def _activateCb(self, unused_app):
        if self.gui:
            # The app is already started and the window already created.
            # Present the already existing window.
            # TODO: Use present() instead of present_with_time() when
            # https://bugzilla.gnome.org/show_bug.cgi?id=688830 is fixed.
            x11_server_time = GdkX11.x11_get_server_time(self.gui.get_window())
            self.gui.present_with_time(x11_server_time)
            # No need to show the welcome wizard.
            return
        self.createMainWindow()
        self.welcome_wizard = StartUpWizard(self)
        self.welcome_wizard.show()

    def createMainWindow(self):
        if self.gui:
            return
        self.gui = PitiviMainWindow(self)
        self.add_window(self.gui)
        # We might as well show it.
        self.gui.show()

    def openCb(self, unused_app, giofiles, unused_count, unused_hint):
        assert giofiles
        self.createMainWindow()
        if len(giofiles) > 1:
            self.warning(
                "Can open only one project file at a time. Ignoring the rest!")
        project_file = giofiles[0]
        self.project_manager.loadProject(quote_uri(project_file.get_uri()))
        return True

    def shutdown(self):
        """
        Close Pitivi.

        @return: C{True} if Pitivi was successfully closed, else C{False}.
        @rtype: C{bool}
        """
        self.debug("shutting down")
        # Refuse to close if we are not done with the current project.
        if not self.project_manager.closeRunningProject():
            self.warning(
                "Not closing since running project doesn't want to close")
            return False
        if self.welcome_wizard:
            self.welcome_wizard.hide()
        if self.gui:
            self.gui.destroy()
        self.threads.stopAllThreads()
        self.settings.storeSettings()
        self.quit()
        return True

    def _newProjectLoaded(self, unused_project_manager, project,
                          unused_fully_loaded):
        self.action_log.clean()
        self.timeline_log_observer.startObserving(project.timeline)
        self.project_log_observer.startObserving(project)

    def _projectClosed(self, unused_project_manager, project):
        self.project_log_observer.stopObserving(project)
        self.timeline_log_observer.stopObserving(project.timeline)

    def _checkVersion(self):
        """
        Check online for release versions information.
        """
        self.info("Requesting version information async")
        giofile = Gio.File.new_for_uri(RELEASES_URL)
        giofile.load_contents_async(None, self._versionInfoReceivedCb, None)

    def _versionInfoReceivedCb(self, giofile, result, user_data):
        try:
            raw = giofile.load_contents_finish(result)[1]
            if not isinstance(raw, str):
                raw = raw.decode()
            raw = raw.split("\n")
            # Split line at '=' if the line is not empty or a comment line
            data = [
                element.split("=") for element in raw
                if element and not element.startswith("#")
            ]

            # search newest version and status
            status = "UNSUPPORTED"
            current_version = None
            for version, version_status in data:
                if VERSION == version:
                    status = version_status
                if version_status.upper() == "CURRENT":
                    # This is the latest.
                    current_version = version
                    self.info("Latest software version is %s", current_version)

            VERSION_split = [int(i) for i in VERSION.split(".")]
            current_version_split = [
                int(i) for i in current_version.split(".")
            ]
            if VERSION_split > current_version_split:
                status = "CURRENT"
                self.info(
                    "Running version %s, which is newer than the latest known version. Considering it as the latest current version.",
                    VERSION)
            elif status is "UNSUPPORTED":
                self.warning("Using an outdated version of Pitivi (%s)",
                             VERSION)

            self._version_information["current"] = current_version
            self._version_information["status"] = status
            self.emit("version-info-received", self._version_information)
        except Exception as e:
            self.warning("Version info could not be read: %s", e)

    def isLatest(self):
        """
        Whether the app's version is the latest as far as we know.
        """
        status = self._version_information.get("status")
        return status is None or status.upper() == "CURRENT"

    def getLatest(self):
        """
        Get the latest version of the app or None.
        """
        return self._version_information.get("current")

    def _quitCb(self, unused_action, unused_param):
        self.shutdown()

    def _undoCb(self, unused_action, unused_param):
        self.action_log.undo()

    def _redoCb(self, unused_action, unused_param):
        self.action_log.redo()

    def _actionLogCommit(self, action_log, unused_stack, nested):
        if nested:
            return
        self._syncDoUndo(action_log)

    def _actionLogUndo(self, action_log, unused_stack):
        self._syncDoUndo(action_log)

    def _actionLogRedo(self, action_log, unused_stack):
        self._syncDoUndo(action_log)

    def _actionLogCleaned(self, action_log):
        self._syncDoUndo(action_log)

    def _syncDoUndo(self, action_log):
        can_undo = bool(action_log.undo_stacks)
        self.undo_action.set_enabled(can_undo)

        can_redo = bool(action_log.redo_stacks)
        self.redo_action.set_enabled(can_redo)

        dirty = action_log.dirty()
        self.project_manager.current_project.setModificationState(dirty)
        # In the tests we do not want to create any gui
        if self.gui is not None:
            self.gui.showProjectStatus()
예제 #7
0
class TestProjectManager(TestCase):

    def setUp(self):
        app = mock.MagicMock()
        self.manager = ProjectManager(app)
        self.listener = ProjectManagerListener(self.manager)
        self.signals = self.listener.signals

    def testLoadProjectFailedUnknownFormat(self):
        """
        Check that new-project-failed is emitted when we don't have a suitable
        formatter.
        """
        uri = "file:///Untitled.meh"
        self.manager.loadProject(uri)

        # loading
        name, args = self.signals[0]
        self.assertEqual(uri, args[0].get_uri(), self.signals)

        # failed
        name, args = self.signals[1]
        self.assertEqual("new-project-failed", name)
        signalUri, unused_message = args
        self.assertEqual(uri, signalUri, self.signals)

    def testLoadProjectClosesCurrent(self):
        """
        Check that new-project-failed is emited if we can't close the current
        project instance.
        """
        state = {"tried-close": False}

        def close():
            state["tried-close"] = True
            return False
        self.manager.closeRunningProject = close

        uri = "file:///Untitled.xptv"
        self.manager.current_project = mock.Mock()
        self.manager.loadProject(uri)

        self.assertEqual(0, len(self.signals))
        self.assertTrue(state["tried-close"], self.signals)

    def testLoadProject(self):
        self.manager.newBlankProject()

        name, args = self.signals[0]
        self.assertEqual("new-project-loading", name, self.signals)

        name, args = self.signals[1]
        self.assertEqual("new-project-created", name, self.signals)

        name, args = self.signals[2]
        self.assertEqual("new-project-loaded", name, self.signals)

    def testMissingUriForwarded(self):
        mainloop = common.create_main_loop()

        def missingUriCb(self, project, error, clip_asset, result):
            result[0] = True
            mainloop.quit()

        result = [False]
        self.manager.connect("missing-uri", missingUriCb, result)

        with common.created_project_file() as uri:
            self.assertTrue(self.manager.loadProject(uri))
            mainloop.run()
        self.assertTrue(result[0], "missing-uri has not been emitted")

    def testLoaded(self):
        mainloop = common.create_main_loop()

        def new_project_loaded_cb(project_manager, project):
            mainloop.quit()

        self.manager.connect("new-project-loaded", new_project_loaded_cb)

        asset_uri = common.get_sample_uri("flat_colour1_640x480.png")
        with common.created_project_file(asset_uri=asset_uri) as uri:
            self.assertTrue(self.manager.loadProject(uri))
            mainloop.run()

        project = self.manager.current_project
        self.assertFalse(project.at_least_one_asset_missing)
        self.assertTrue(project.loaded)
        self.assertFalse(project.hasUnsavedModifications())

    def testCloseRunningProjectNoProject(self):
        self.assertTrue(self.manager.closeRunningProject())
        self.assertFalse(self.signals)

    def testCloseRunningProjectRefuseFromSignal(self):
        def closing(manager, project):
            return False

        self.manager.current_project = mock.Mock()
        self.manager.current_project.uri = "file:///ciao"
        self.manager.connect("closing-project", closing)

        self.assertFalse(self.manager.closeRunningProject())
        self.assertEqual(1, len(self.signals))
        name, args = self.signals[0]
        self.assertEqual("closing-project", name)
        project = args[0]
        self.assertTrue(project is self.manager.current_project)

    def testCloseRunningProject(self):
        current = mock.Mock()
        current.uri = None
        self.manager.current_project = current
        self.assertTrue(self.manager.closeRunningProject())
        self.assertEqual(2, len(self.signals))

        name, args = self.signals[0]
        self.assertEqual("closing-project", name)
        project = args[0]
        self.assertTrue(project is current)

        name, args = self.signals[1]
        self.assertEqual("project-closed", name)
        project = args[0]
        self.assertTrue(project is current)

        self.assertTrue(self.manager.current_project is None)

    def testNewBlankProjectCantCloseCurrent(self):
        def closing(manager, project):
            return False

        self.manager.current_project = mock.Mock()
        self.manager.current_project.uri = "file:///ciao"
        self.manager.connect("closing-project", closing)
        self.assertFalse(self.manager.newBlankProject())
        self.assertEqual(1, len(self.signals))
        signal, args = self.signals[0]
        self.assertEqual("closing-project", signal)

    def testNewBlankProject(self):
        self.assertTrue(self.manager.newBlankProject())
        self.assertEqual(3, len(self.signals))

        name, args = self.signals[0]
        self.assertEqual("new-project-loading", name)
        project = args[0]
        self.assertTrue(project.get_uri() is None)

        name, args = self.signals[1]
        self.assertEqual("new-project-created", name)
        project = args[0]
        self.assertEqual(project.get_uri(), project.uri)

        name, args = self.signals[2]
        self.assertEqual("new-project-loaded", name)
        project = args[0]
        self.assertTrue(project is self.manager.current_project)

    def testSaveProject(self):
        self.assertTrue(self.manager.newBlankProject())

        unused, path = tempfile.mkstemp(suffix=".xges")
        unused, path2 = tempfile.mkstemp(suffix=".xges")
        try:
            uri = "file://" + os.path.abspath(path)
            uri2 = "file://" + os.path.abspath(path2)

            # Save the project.
            self.assertTrue(self.manager.saveProject(uri=uri, backup=False))
            self.assertTrue(os.path.isfile(path))

            # Wait a bit.
            time.sleep(0.1)

            # Save the project at a new location.
            self.assertTrue(self.manager.saveProject(uri2, backup=False))
            self.assertTrue(os.path.isfile(path2))

            # Make sure the old path and the new path have different mtimes.
            mtime = os.path.getmtime(path)
            mtime2 = os.path.getmtime(path2)
            self.assertLess(mtime, mtime2)

            # Wait a bit more.
            time.sleep(0.1)

            # Save project again under the new path (by omitting uri arg)
            self.assertTrue(self.manager.saveProject(backup=False))

            # regression test for bug 594396
            # make sure we didn't save to the old URI
            self.assertEqual(mtime, os.path.getmtime(path))
            # make sure we did save to the new URI
            self.assertLess(mtime2, os.path.getmtime(path2))
        finally:
            os.remove(path)
            os.remove(path2)

    def testMakeBackupUri(self):
        uri = "file:///tmp/x.xges"
        self.assertEqual(uri + "~", self.manager._makeBackupURI(uri))

    def testBackupProject(self):
        self.manager.newBlankProject()

        # Assign an uri to the project where it's saved by default.
        unused, xges_path = tempfile.mkstemp(suffix=".xges")
        uri = "file://" + os.path.abspath(xges_path)
        self.manager.current_project.uri = uri
        # This is where the automatic backup file is saved.
        backup_uri = self.manager._makeBackupURI(uri)

        # Save the backup
        self.assertTrue(self.manager.saveProject(
            self.manager.current_project, backup=True))
        self.assertTrue(os.path.isfile(path_from_uri(backup_uri)))

        self.manager.closeRunningProject()
        self.assertFalse(os.path.isfile(path_from_uri(backup_uri)),
                         "Backup file not deleted when project closed")
예제 #8
0
class Pitivi(Gtk.Application, Loggable):
    """
    Pitivi's application.

    @type effects: L{EffectsManager}
    @ivar gui: The main window of the app.
    @type gui: L{PitiviMainWindow}
    @ivar project_manager: The project manager object used in the application
    @type project_manager: L{ProjectManager}
    @ivar settings: Application-wide settings.
    @type settings: L{GlobalSettings}.
    """

    __gsignals__ = {
        "version-info-received": (GObject.SIGNAL_RUN_LAST, None, (object, ))
    }

    def __init__(self):
        Gtk.Application.__init__(self,
                                 application_id="org.pitivi",
                                 flags=Gio.ApplicationFlags.HANDLES_OPEN)
        Loggable.__init__(self)

        self.settings = None
        self.threads = None
        self.effects = None
        self.system = None
        self.project_manager = ProjectManager(self)

        self.action_log = UndoableActionLog(self)
        self.timeline_log_observer = None
        self.project_log_observer = None
        self._last_action_time = Gst.util_get_timestamp()

        self.gui = None
        self.welcome_wizard = None

        self._version_information = {}

        self._scenario_file = None
        self._first_action = True

        self.connect("startup", self._startupCb)
        self.connect("activate", self._activateCb)
        self.connect("open", self.openCb)

    def write_action(self, action, properties={}):
        if self._first_action:
            self._scenario_file.write(
                "description, seek=true, handles-states=true\n")
            self._first_action = False

        now = Gst.util_get_timestamp()
        if now - self._last_action_time > 0.05 * Gst.SECOND:
            # We need to make sure that the waiting time was more than 50 ms.
            st = Gst.Structure.new_empty("wait")
            st["duration"] = float((now - self._last_action_time) / Gst.SECOND)
            self._scenario_file.write(st.to_string() + "\n")
            self._last_action_time = now

        if not isinstance(action, Gst.Structure):
            structure = Gst.Structure.new_empty(action)

            for key, value in properties.items():
                structure[key] = value

            action = structure

        self._scenario_file.write(action.to_string() + "\n")
        self._scenario_file.flush()

    def _startupCb(self, unused_app):
        # Init logging as early as possible so we can log startup code
        enable_color = not os.environ.get('PITIVI_DEBUG_NO_COLOR',
                                          '0') in ('', '1')
        # Let's show a human-readable Pitivi debug output by default, and only
        # show a crazy unreadable mess when surrounded by gst debug statements.
        enable_crack_output = "GST_DEBUG" in os.environ
        log.init('PITIVI_DEBUG', enable_color, enable_crack_output)

        self.info('starting up')
        self.settings = GlobalSettings()
        self.threads = ThreadMaster()
        self.effects = EffectsManager()
        self.system = getSystem()

        self.action_log.connect("commit", self._actionLogCommit)
        self.action_log.connect("undo", self._actionLogUndo)
        self.action_log.connect("redo", self._actionLogRedo)
        self.action_log.connect("cleaned", self._actionLogCleaned)
        self.timeline_log_observer = TimelineLogObserver(self.action_log)
        self.project_log_observer = ProjectLogObserver(self.action_log)

        self.project_manager.connect("new-project-loading",
                                     self._newProjectLoadingCb)
        self.project_manager.connect("new-project-loaded",
                                     self._newProjectLoaded)
        self.project_manager.connect("project-closed", self._projectClosed)

        self._createActions()
        self._checkVersion()

    def _createActions(self):
        self.undo_action = Gio.SimpleAction.new("undo", None)
        self.undo_action.connect("activate", self._undoCb)
        self.add_action(self.undo_action)
        self.add_accelerator("<Control>z", "app.undo", None)

        self.redo_action = Gio.SimpleAction.new("redo", None)
        self.redo_action.connect("activate", self._redoCb)
        self.add_action(self.redo_action)
        self.add_accelerator("<Control><Shift>z", "app.redo", None)

        self.quit_action = Gio.SimpleAction.new("quit", None)
        self.quit_action.connect("activate", self._quitCb)
        self.add_action(self.quit_action)
        self.add_accelerator("<Control>q", "app.quit", None)

    def _activateCb(self, unused_app):
        if self.gui:
            # The app is already started and the window already created.
            # Present the already existing window.
            try:
                # TODO: Use present() instead of present_with_time() when
                # https://bugzilla.gnome.org/show_bug.cgi?id=688830 is fixed.
                from gi.repository import GdkX11
                x11_server_time = GdkX11.x11_get_server_time(
                    self.gui.get_window())
                self.gui.present_with_time(x11_server_time)
            except ImportError:
                # On Wayland or Quartz (Mac OS X) backend there is no GdkX11,
                # so just use present() directly here.
                self.gui.present()
            # No need to show the welcome wizard.
            return
        self.createMainWindow()
        self.welcome_wizard = StartUpWizard(self)
        self.welcome_wizard.show()

    def createMainWindow(self):
        if self.gui:
            return
        self.gui = PitiviMainWindow(self)
        self.add_window(self.gui)
        # We might as well show it.
        self.gui.show()

    def openCb(self, unused_app, giofiles, unused_count, unused_hint):
        assert giofiles
        self.createMainWindow()
        if len(giofiles) > 1:
            self.warning(
                "Can open only one project file at a time. Ignoring the rest!")
        project_file = giofiles[0]
        self.project_manager.loadProject(quote_uri(project_file.get_uri()))
        return True

    def shutdown(self):
        """
        Close Pitivi.

        @return: C{True} if Pitivi was successfully closed, else C{False}.
        @rtype: C{bool}
        """
        self.debug("shutting down")
        # Refuse to close if we are not done with the current project.
        if not self.project_manager.closeRunningProject():
            self.warning(
                "Not closing since running project doesn't want to close")
            return False
        if self.welcome_wizard:
            self.welcome_wizard.hide()
        if self.gui:
            self.gui.destroy()
        self.threads.stopAllThreads()
        self.settings.storeSettings()
        self.quit()
        return True

        self._first_action = True

    def _setScenarioFile(self, uri):
        if 'PITIVI_SCENARIO_FILE' in os.environ:
            uri = quote_uri(os.environ['PITIVI_SCENARIO_FILE'])
        else:
            cache_dir = get_dir(os.path.join(xdg_cache_home(), "scenarios"))
            scenario_name = str(time.strftime("%Y%m%d-%H%M%S"))
            project_path = None
            if uri:
                project_path = path_from_uri(uri)
                scenario_name += os.path.splitext(
                    project_path.replace(os.sep, "_"))[0]

            uri = os.path.join(cache_dir, scenario_name + ".scenario")
            uri = quote_uri(uri)

        self._scenario_file = open(path_from_uri(uri), "w")

        if project_path:
            f = open(project_path)
            content = f.read()
            if not uri.endswith(".scenario"):
                self.write_action(
                    "load-project",
                    {"serialized-content": "%s" % content.replace("\n", "")})
            f.close()

    def _newProjectLoadingCb(self, unused_project_manager, uri):
        self._setScenarioFile(uri)

    def _newProjectLoaded(self, unused_project_manager, project,
                          unused_fully_loaded):
        self.action_log.clean()

        self.timeline_log_observer.startObserving(project.timeline)
        self.project_log_observer.startObserving(project)

    def _projectClosed(self, unused_project_manager, project):
        self.project_log_observer.stopObserving(project)
        self.timeline_log_observer.stopObserving(project.timeline)

        if self._scenario_file:
            self.write_action("stop")
            self._scenario_file.close()
            self._scenario_file = None

    def _checkVersion(self):
        """
        Check online for release versions information.
        """
        self.info("Requesting version information async")
        giofile = Gio.File.new_for_uri(RELEASES_URL)
        giofile.load_contents_async(None, self._versionInfoReceivedCb, None)

    def _versionInfoReceivedCb(self, giofile, result, user_data):
        try:
            raw = giofile.load_contents_finish(result)[1]
            if not isinstance(raw, str):
                raw = raw.decode()
            raw = raw.split("\n")
            # Split line at '=' if the line is not empty or a comment line
            data = [
                element.split("=") for element in raw
                if element and not element.startswith("#")
            ]

            # search newest version and status
            status = "UNSUPPORTED"
            current_version = None
            for version, version_status in data:
                if VERSION == version:
                    status = version_status
                if version_status.upper() == "CURRENT":
                    # This is the latest.
                    current_version = version
                    self.info("Latest software version is %s", current_version)

            VERSION_split = [int(i) for i in VERSION.split(".")]
            current_version_split = [
                int(i) for i in current_version.split(".")
            ]
            if VERSION_split > current_version_split:
                status = "CURRENT"
                self.info(
                    "Running version %s, which is newer than the latest known version. Considering it as the latest current version.",
                    VERSION)
            elif status is "UNSUPPORTED":
                self.warning("Using an outdated version of Pitivi (%s)",
                             VERSION)

            self._version_information["current"] = current_version
            self._version_information["status"] = status
            self.emit("version-info-received", self._version_information)
        except Exception as e:
            self.warning("Version info could not be read: %s", e)

    def isLatest(self):
        """
        Whether the app's version is the latest as far as we know.
        """
        status = self._version_information.get("status")
        return status is None or status.upper() == "CURRENT"

    def getLatest(self):
        """
        Get the latest version of the app or None.
        """
        return self._version_information.get("current")

    def _quitCb(self, unused_action, unused_param):
        self.shutdown()

    def _undoCb(self, unused_action, unused_param):
        self.action_log.undo()

    def _redoCb(self, unused_action, unused_param):
        self.action_log.redo()

    def _actionLogCommit(self, action_log, unused_stack, nested):
        if nested:
            return
        self._syncDoUndo(action_log)

    def _actionLogUndo(self, action_log, unused_stack):
        self._syncDoUndo(action_log)

    def _actionLogRedo(self, action_log, unused_stack):
        self._syncDoUndo(action_log)

    def _actionLogCleaned(self, action_log):
        self._syncDoUndo(action_log)

    def _syncDoUndo(self, action_log):
        can_undo = bool(action_log.undo_stacks)
        self.undo_action.set_enabled(can_undo)

        can_redo = bool(action_log.redo_stacks)
        self.redo_action.set_enabled(can_redo)

        dirty = action_log.dirty()
        self.project_manager.current_project.setModificationState(dirty)
        # In the tests we do not want to create any gui
        if self.gui is not None:
            self.gui.showProjectStatus()
예제 #9
0
class Pitivi(Gtk.Application, Loggable):

    """
    Pitivi's application.

    @type effects: L{EffectsManager}
    @ivar gui: The main window of the app.
    @type gui: L{PitiviMainWindow}
    @ivar project_manager: The project manager object used in the application
    @type project_manager: L{ProjectManager}
    @ivar settings: Application-wide settings.
    @type settings: L{GlobalSettings}.
    """

    __gsignals__ = {
        "version-info-received": (GObject.SIGNAL_RUN_LAST, None, (object,))
    }

    def __init__(self):
        Gtk.Application.__init__(self,
                                 application_id="org.pitivi",
                                 flags=Gio.ApplicationFlags.HANDLES_OPEN)
        Loggable.__init__(self)

        self.settings = None
        self.threads = None
        self.effects = None
        self.system = None
        self.project_manager = ProjectManager(self)

        self.action_log = UndoableActionLog(self)
        self.timeline_log_observer = None
        self.project_log_observer = None
        self._last_action_time = Gst.util_get_timestamp()

        self.gui = None
        self.welcome_wizard = None

        self._version_information = {}

        self._scenario_file = None
        self._first_action = True

        self.connect("startup", self._startupCb)
        self.connect("activate", self._activateCb)
        self.connect("open", self.openCb)

    def write_action(self, action, properties={}):
        if self._first_action:
            self._scenario_file.write(
                "description, seek=true, handles-states=true\n")
            self._first_action = False

        now = Gst.util_get_timestamp()
        if now - self._last_action_time > 0.05 * Gst.SECOND:
            # We need to make sure that the waiting time was more than 50 ms.
            st = Gst.Structure.new_empty("wait")
            st["duration"] = float((now - self._last_action_time) / Gst.SECOND)
            self._scenario_file.write(st.to_string() + "\n")
            self._last_action_time = now

        if not isinstance(action, Gst.Structure):
            structure = Gst.Structure.new_empty(action)

            for key, value in properties.items():
                structure[key] = value

            action = structure

        self._scenario_file.write(action.to_string() + "\n")
        self._scenario_file.flush()

    def _startupCb(self, unused_app):
        # Init logging as early as possible so we can log startup code
        enable_color = not os.environ.get(
            'PITIVI_DEBUG_NO_COLOR', '0') in ('', '1')
        # Let's show a human-readable Pitivi debug output by default, and only
        # show a crazy unreadable mess when surrounded by gst debug statements.
        enable_crack_output = "GST_DEBUG" in os.environ
        log.init('PITIVI_DEBUG', enable_color, enable_crack_output)

        self.info('starting up')
        self.settings = GlobalSettings()
        self.threads = ThreadMaster()
        self.effects = EffectsManager()
        self.system = getSystem()

        self.action_log.connect("commit", self._actionLogCommit)
        self.action_log.connect("undo", self._actionLogUndo)
        self.action_log.connect("redo", self._actionLogRedo)
        self.action_log.connect("cleaned", self._actionLogCleaned)
        self.timeline_log_observer = TimelineLogObserver(self.action_log)
        self.project_log_observer = ProjectLogObserver(self.action_log)

        self.project_manager.connect(
            "new-project-loading", self._newProjectLoadingCb)
        self.project_manager.connect(
            "new-project-loaded", self._newProjectLoaded)
        self.project_manager.connect("project-closed", self._projectClosed)

        self._createActions()
        self._checkVersion()

    def _createActions(self):
        self.undo_action = Gio.SimpleAction.new("undo", None)
        self.undo_action.connect("activate", self._undoCb)
        self.add_action(self.undo_action)
        self.add_accelerator("<Control>z", "app.undo", None)

        self.redo_action = Gio.SimpleAction.new("redo", None)
        self.redo_action.connect("activate", self._redoCb)
        self.add_action(self.redo_action)
        self.add_accelerator("<Control><Shift>z", "app.redo", None)

        self.quit_action = Gio.SimpleAction.new("quit", None)
        self.quit_action.connect("activate", self._quitCb)
        self.add_action(self.quit_action)
        self.add_accelerator("<Control>q", "app.quit", None)

    def _activateCb(self, unused_app):
        if self.gui:
            # The app is already started and the window already created.
            # Present the already existing window.
            try:
                # TODO: Use present() instead of present_with_time() when
                # https://bugzilla.gnome.org/show_bug.cgi?id=688830 is fixed.
                from gi.repository import GdkX11
                x11_server_time = GdkX11.x11_get_server_time(self.gui.get_window())
                self.gui.present_with_time(x11_server_time)
            except ImportError:
                # On Wayland or Quartz (Mac OS X) backend there is no GdkX11,
                # so just use present() directly here.
                self.gui.present()
            # No need to show the welcome wizard.
            return
        self.createMainWindow()
        self.welcome_wizard = StartUpWizard(self)
        self.welcome_wizard.show()

    def createMainWindow(self):
        if self.gui:
            return
        self.gui = PitiviMainWindow(self)
        self.add_window(self.gui)
        # We might as well show it.
        self.gui.show()

    def openCb(self, unused_app, giofiles, unused_count, unused_hint):
        assert giofiles
        self.createMainWindow()
        if len(giofiles) > 1:
            self.warning(
                "Can open only one project file at a time. Ignoring the rest!")
        project_file = giofiles[0]
        self.project_manager.loadProject(quote_uri(project_file.get_uri()))
        return True

    def shutdown(self):
        """
        Close Pitivi.

        @return: C{True} if Pitivi was successfully closed, else C{False}.
        @rtype: C{bool}
        """
        self.debug("shutting down")
        # Refuse to close if we are not done with the current project.
        if not self.project_manager.closeRunningProject():
            self.warning(
                "Not closing since running project doesn't want to close")
            return False
        if self.welcome_wizard:
            self.welcome_wizard.hide()
        if self.gui:
            self.gui.destroy()
        self.threads.stopAllThreads()
        self.settings.storeSettings()
        self.quit()
        return True

        self._first_action = True

    def _setScenarioFile(self, uri):
        if 'PITIVI_SCENARIO_FILE' in os.environ:
            uri = quote_uri(os.environ['PITIVI_SCENARIO_FILE'])
        else:
            cache_dir = get_dir(os.path.join(xdg_cache_home(), "scenarios"))
            scenario_name = str(time.strftime("%Y%m%d-%H%M%S"))
            project_path = None
            if uri:
                project_path = path_from_uri(uri)
                scenario_name += os.path.splitext(project_path.replace(os.sep, "_"))[0]

            uri = os.path.join(cache_dir, scenario_name + ".scenario")
            uri = quote_uri(uri)

        self._scenario_file = open(path_from_uri(uri), "w")

        if project_path:
            f = open(project_path)
            content = f.read()
            if not project_path.endswith(".scenario"):
                self.write_action("load-project",
                                  {"serialized-content":
                                   "%s" % content.replace("\n", "")})
            f.close()

    def _newProjectLoadingCb(self, unused_project_manager, uri):
        self._setScenarioFile(uri)

    def _newProjectLoaded(self, unused_project_manager, project, unused_fully_loaded):
        self.action_log.clean()

        self.timeline_log_observer.startObserving(project.timeline)
        self.project_log_observer.startObserving(project)

    def _projectClosed(self, unused_project_manager, project):
        self.project_log_observer.stopObserving(project)
        self.timeline_log_observer.stopObserving(project.timeline)

        if self._scenario_file:
            self.write_action("stop")
            self._scenario_file.close()
            self._scenario_file = None

    def _checkVersion(self):
        """
        Check online for release versions information.
        """
        self.info("Requesting version information async")
        giofile = Gio.File.new_for_uri(RELEASES_URL)
        giofile.load_contents_async(None, self._versionInfoReceivedCb, None)

    def _versionInfoReceivedCb(self, giofile, result, user_data):
        try:
            raw = giofile.load_contents_finish(result)[1]
            if not isinstance(raw, str):
                raw = raw.decode()
            raw = raw.split("\n")
            # Split line at '=' if the line is not empty or a comment line
            data = [element.split("=") for element in raw
                    if element and not element.startswith("#")]

            # search newest version and status
            status = "UNSUPPORTED"
            current_version = None
            for version, version_status in data:
                if VERSION == version:
                    status = version_status
                if version_status.upper() == "CURRENT":
                    # This is the latest.
                    current_version = version
                    self.info("Latest software version is %s", current_version)

            VERSION_split = [int(i) for i in VERSION.split(".")]
            current_version_split = [int(i)
                                     for i in current_version.split(".")]
            if VERSION_split > current_version_split:
                status = "CURRENT"
                self.info(
                    "Running version %s, which is newer than the latest known version. Considering it as the latest current version.", VERSION)
            elif status is "UNSUPPORTED":
                self.warning(
                    "Using an outdated version of Pitivi (%s)", VERSION)

            self._version_information["current"] = current_version
            self._version_information["status"] = status
            self.emit("version-info-received", self._version_information)
        except Exception as e:
            self.warning("Version info could not be read: %s", e)

    def isLatest(self):
        """
        Whether the app's version is the latest as far as we know.
        """
        status = self._version_information.get("status")
        return status is None or status.upper() == "CURRENT"

    def getLatest(self):
        """
        Get the latest version of the app or None.
        """
        return self._version_information.get("current")

    def _quitCb(self, unused_action, unused_param):
        self.shutdown()

    def _undoCb(self, unused_action, unused_param):
        self.action_log.undo()

    def _redoCb(self, unused_action, unused_param):
        self.action_log.redo()

    def _actionLogCommit(self, action_log, unused_stack, nested):
        if nested:
            return
        self._syncDoUndo(action_log)

    def _actionLogUndo(self, action_log, unused_stack):
        self._syncDoUndo(action_log)

    def _actionLogRedo(self, action_log, unused_stack):
        self._syncDoUndo(action_log)

    def _actionLogCleaned(self, action_log):
        self._syncDoUndo(action_log)

    def _syncDoUndo(self, action_log):
        can_undo = bool(action_log.undo_stacks)
        self.undo_action.set_enabled(can_undo)

        can_redo = bool(action_log.redo_stacks)
        self.redo_action.set_enabled(can_redo)

        dirty = action_log.dirty()
        self.project_manager.current_project.setModificationState(dirty)
        # In the tests we do not want to create any gui
        if self.gui is not None:
            self.gui.showProjectStatus()
예제 #10
0
파일: application.py 프로젝트: brion/pitivi
class Pitivi(Gtk.Application, Loggable):
    """
    Pitivi's application.

    @type effects: L{EffectsManager}
    @ivar gui: The main window of the app.
    @type gui: L{PitiviMainWindow}
    @ivar project_manager: The project manager object used in the application
    @type project_manager: L{ProjectManager}
    @ivar settings: Application-wide settings.
    @type settings: L{GlobalSettings}.
    """

    __gsignals__ = {
        "version-info-received": (GObject.SIGNAL_RUN_LAST, None, (object,))
    }

    def __init__(self):
        Gtk.Application.__init__(self,
                                 application_id="org.pitivi",
                                 flags=Gio.ApplicationFlags.HANDLES_OPEN)
        Loggable.__init__(self)

        self.settings = None
        self.threads = None
        self.effects = None
        self.system = None
        self.project_manager = ProjectManager(self)

        self.action_log = UndoableActionLog()
        self.timeline_log_observer = None
        self.project_log_observer = None

        self.gui = None
        self.welcome_wizard = None

        self._version_information = {}

        self.connect("startup", self._startupCb)
        self.connect("activate", self._activateCb)
        self.connect("open", self.openCb)

    def _startupCb(self, unused_app):
        # Init logging as early as possible so we can log startup code
        enable_color = not os.environ.get('PITIVI_DEBUG_NO_COLOR', '0') in ('', '1')
        # Let's show a human-readable Pitivi debug output by default, and only
        # show a crazy unreadable mess when surrounded by gst debug statements.
        enable_crack_output = "GST_DEBUG" in os.environ
        log.init('PITIVI_DEBUG', enable_color, enable_crack_output)

        self.info('starting up')
        self.settings = GlobalSettings()
        self.threads = ThreadMaster()
        self.effects = EffectsManager()
        self.system = getSystem()

        self.action_log.connect("commit", self._actionLogCommit)
        self.action_log.connect("undo", self._actionLogUndo)
        self.action_log.connect("redo", self._actionLogRedo)
        self.action_log.connect("cleaned", self._actionLogCleaned)
        self.timeline_log_observer = TimelineLogObserver(self.action_log)
        self.project_log_observer = ProjectLogObserver(self.action_log)

        self.project_manager.connect("new-project-loaded", self._newProjectLoaded)
        self.project_manager.connect("project-closed", self._projectClosed)

        self._createActions()
        self._checkVersion()

    def _createActions(self):
        self.undo_action = Gio.SimpleAction.new("undo", None)
        self.undo_action.connect("activate", self._undoCb)
        self.add_action(self.undo_action)
        self.add_accelerator("<Control>z", "app.undo", None)

        self.redo_action = Gio.SimpleAction.new("redo", None)
        self.redo_action.connect("activate", self._redoCb)
        self.add_action(self.redo_action)
        self.add_accelerator("<Control><Shift>z", "app.redo", None)

        self.quit_action = Gio.SimpleAction.new("quit", None)
        self.quit_action.connect("activate", self._quitCb)
        self.add_action(self.quit_action)
        self.add_accelerator("<Control>q", "app.quit", None)

    def _activateCb(self, unused_app):
        if self.gui:
            # The app is already started and the window already created.
            # Present the already existing window.
            # TODO: Use present() instead of present_with_time() when
            # https://bugzilla.gnome.org/show_bug.cgi?id=688830 is fixed.
            x11_server_time = GdkX11.x11_get_server_time(self.gui.get_window())
            self.gui.present_with_time(x11_server_time)
            # No need to show the welcome wizard.
            return
        self.createMainWindow()
        self.welcome_wizard = StartUpWizard(self)
        self.welcome_wizard.show()

    def createMainWindow(self):
        if self.gui:
            return
        self.gui = PitiviMainWindow(self)
        self.add_window(self.gui)
        # We might as well show it.
        self.gui.show()

    def openCb(self, unused_app, giofiles, unused_count, unused_hint):
        assert giofiles
        self.createMainWindow()
        if len(giofiles) > 1:
            self.warning("Can open only one project file at a time. Ignoring the rest!")
        project_file = giofiles[0]
        self.project_manager.loadProject(quote_uri(project_file.get_uri()))
        return True

    def shutdown(self):
        """
        Close Pitivi.

        @return: C{True} if Pitivi was successfully closed, else C{False}.
        @rtype: C{bool}
        """
        self.debug("shutting down")
        # Refuse to close if we are not done with the current project.
        if not self.project_manager.closeRunningProject():
            self.warning("Not closing since running project doesn't want to close")
            return False
        if self.welcome_wizard:
            self.welcome_wizard.hide()
        if self.gui:
            self.gui.destroy()
        self.threads.stopAllThreads()
        self.settings.storeSettings()
        self.quit()
        return True

    def _newProjectLoaded(self, unused_project_manager, project, unused_fully_loaded):
        self.action_log.clean()
        self.timeline_log_observer.startObserving(project.timeline)
        self.project_log_observer.startObserving(project)

    def _projectClosed(self, unused_project_manager, project):
        self.project_log_observer.stopObserving(project)
        self.timeline_log_observer.stopObserving(project.timeline)

    def _checkVersion(self):
        """
        Check online for release versions information.
        """
        self.info("Requesting version information async")
        giofile = Gio.File.new_for_uri(RELEASES_URL)
        giofile.load_contents_async(None, self._versionInfoReceivedCb, None)

    def _versionInfoReceivedCb(self, giofile, result, user_data):
        try:
            raw = giofile.load_contents_finish(result)[1]
            if not isinstance(raw, str):
                raw = raw.decode()
            raw = raw.split("\n")
            # Split line at '=' if the line is not empty or a comment line
            data = [element.split("=") for element in raw
                    if element and not element.startswith("#")]

            # search newest version and status
            status = "UNSUPPORTED"
            current_version = None
            for version, version_status in data:
                if VERSION == version:
                    status = version_status
                if version_status.upper() == "CURRENT":
                    # This is the latest.
                    current_version = version
                    self.info("Latest software version is %s", current_version)

            VERSION_split = [int(i) for i in VERSION.split(".")]
            current_version_split = [int(i) for i in current_version.split(".")]
            if VERSION_split > current_version_split:
                status = "CURRENT"
                self.info("Running version %s, which is newer than the latest known version. Considering it as the latest current version.", VERSION)
            elif status is "UNSUPPORTED":
                self.warning("Using an outdated version of Pitivi (%s)", VERSION)

            self._version_information["current"] = current_version
            self._version_information["status"] = status
            self.emit("version-info-received", self._version_information)
        except Exception as e:
            self.warning("Version info could not be read: %s", e)

    def isLatest(self):
        """
        Whether the app's version is the latest as far as we know.
        """
        status = self._version_information.get("status")
        return status is None or status.upper() == "CURRENT"

    def getLatest(self):
        """
        Get the latest version of the app or None.
        """
        return self._version_information.get("current")

    def _quitCb(self, unused_action, unused_param):
        self.shutdown()

    def _undoCb(self, unused_action, unused_param):
        self.action_log.undo()

    def _redoCb(self, unused_action, unused_param):
        self.action_log.redo()

    def _actionLogCommit(self, action_log, unused_stack, nested):
        if nested:
            return
        self._syncDoUndo(action_log)

    def _actionLogUndo(self, action_log, unused_stack):
        self._syncDoUndo(action_log)

    def _actionLogRedo(self, action_log, unused_stack):
        self._syncDoUndo(action_log)

    def _actionLogCleaned(self, action_log):
        self._syncDoUndo(action_log)

    def _syncDoUndo(self, action_log):
        can_undo = bool(action_log.undo_stacks)
        self.undo_action.set_enabled(can_undo)

        can_redo = bool(action_log.redo_stacks)
        self.redo_action.set_enabled(can_redo)

        dirty = action_log.dirty()
        self.project_manager.current_project.setModificationState(dirty)
        # In the tests we do not want to create any gui
        if self.gui is not None:
            self.gui.showProjectStatus()