예제 #1
0
    def __init__(self):
        # Create a global client we can use to do VCS related stuff
        self.vcs_client = VCS()

        self.status_checker = StatusChecker()

        self.status_checker.assert_version(EXT_VERSION)

        self.items_cache = {}
예제 #2
0
    def __init__(self):
        # Create a global client we can use to do VCS related stuff
        self.vcs_client = VCS()

        self.status_checker = StatusChecker()
        
        self.status_checker.assert_version(EXT_VERSION)
        
        self.items_cache = {}
예제 #3
0
    def __init__(self):
        factory = Gtk.IconFactory()

        rabbitvcs_icons = [
            "scalable/actions/rabbitvcs-settings.svg",
            "scalable/actions/rabbitvcs-export.svg",
            "scalable/actions/rabbitvcs-properties.svg",
            "scalable/actions/rabbitvcs-show_log.svg",
            "scalable/actions/rabbitvcs-delete.svg",
            "scalable/actions/rabbitvcs-run.svg",
            "scalable/actions/rabbitvcs-unlock.svg",
            "scalable/actions/rabbitvcs-dbus.svg",
            "scalable/actions/rabbitvcs-rename.svg",
            "scalable/actions/rabbitvcs-help.svg",
            "scalable/actions/rabbitvcs-update.svg",
            "scalable/actions/rabbitvcs-diff.svg",
            "scalable/actions/rabbitvcs-resolve.svg",
            "scalable/actions/rabbitvcs-about.svg",
            "scalable/actions/rabbitvcs-add.svg",
            "scalable/actions/rabbitvcs-changes.svg",
            "scalable/actions/rabbitvcs-createpatch.svg",
            "scalable/actions/rabbitvcs-merge.svg",
            "scalable/actions/rabbitvcs-drive.svg",
            "scalable/actions/rabbitvcs-stop.svg",
            "scalable/actions/rabbitvcs-checkout.svg",
            "scalable/actions/rabbitvcs-import.svg",
            "scalable/actions/rabbitvcs-branch.svg",
            "scalable/actions/rabbitvcs-refresh.svg",
            "scalable/actions/rabbitvcs-editconflicts.svg",
            "scalable/actions/rabbitvcs-monkey.svg",
            "scalable/actions/rabbitvcs-applypatch.svg",
            "scalable/actions/rabbitvcs-switch.svg",
            "scalable/actions/rabbitvcs-lock.svg",
            "scalable/actions/rabbitvcs-annotate.svg",
            "scalable/actions/rabbitvcs-compare.svg",
            "scalable/actions/rabbitvcs-revert.svg",
            "scalable/actions/rabbitvcs-bug.svg",
            "scalable/actions/rabbitvcs-cleanup.svg",
            "scalable/actions/rabbitvcs-clear.svg",
            "scalable/actions/rabbitvcs-unstage.svg",
            "scalable/actions/rabbitvcs-emblems.svg",
            "scalable/actions/rabbitvcs-relocate.svg",
            "scalable/actions/rabbitvcs-reset.svg",
            "scalable/actions/rabbitvcs-asynchronous.svg",
            "scalable/actions/rabbitvcs-commit.svg",
            "scalable/actions/rabbitvcs-checkmods.svg",
            "scalable/apps/rabbitvcs.svg", "scalable/apps/rabbitvcs-small.svg",
            "16x16/actions/rabbitvcs-push.png"
        ]

        rabbitvcs_icon_path = get_icon_path()
        for rel_icon_path in rabbitvcs_icons:
            icon_path = "%s/%s" % (rabbitvcs_icon_path, rel_icon_path)
            file = os.path.basename(rel_icon_path)
            (root, ext) = os.path.splitext(file)

            pixbuf = GdkPixbuf.Pixbuf.new_from_file(icon_path)
            iconset = Gtk.IconSet.new_from_pixbuf(pixbuf)
            factory.add(root, iconset)

        factory.add_default()

        # Create a global client we can use to do VCS related stuff
        self.vcs_client = VCS()

        self.status_checker = StatusChecker()

        self.status_checker.assert_version(EXT_VERSION)

        self.items_cache = {}
예제 #4
0
class RabbitVCS(Nemo.InfoProvider, Nemo.MenuProvider, Nemo.ColumnProvider,
                Nemo.PropertyPageProvider, Nemo.NameAndDescProvider,
                GObject.GObject):
    """
    This is the main class that implements all of our awesome features.

    """

    #: This is our lookup table for C{NemoVFSFile}s which we need for attaching
    #: emblems. This is mostly a workaround for not being able to turn a path/uri
    #: into a C{NemoVFSFile}. It looks like:::
    #:
    #:     nemoVFSFile_table = {
    #:        "/foo/bar/baz": <NemoVFSFile>
    #:
    #:     }
    #:
    #: Keeping track of C{NemoVFSFile}s is a little bit complicated because
    #: when an item is moved (renamed) C{update_file_info} doesn't get called. So
    #: we also add C{NemoVFSFile}s to this table from C{get_file_items} etc.
    # FIXME: this may be the source of the memory hogging seen in the extension
    # script itself.
    nemoVFSFile_table = {}

    #: This is in case we want to permanently enable invalidation of the status
    #: checker info.
    always_invalidate = True

    #: When we get the statuses from the callback, put them here for further
    #: use. This is of the form: [("path/to", {...status dict...}), ...]
    statuses_from_callback = []

    def get_local_path(self, path):
        return path.replace("file://", "")

    def __init__(self):
        factory = Gtk.IconFactory()

        rabbitvcs_icons = [
            "scalable/actions/rabbitvcs-settings.svg",
            "scalable/actions/rabbitvcs-export.svg",
            "scalable/actions/rabbitvcs-properties.svg",
            "scalable/actions/rabbitvcs-show_log.svg",
            "scalable/actions/rabbitvcs-delete.svg",
            "scalable/actions/rabbitvcs-run.svg",
            "scalable/actions/rabbitvcs-unlock.svg",
            "scalable/actions/rabbitvcs-dbus.svg",
            "scalable/actions/rabbitvcs-rename.svg",
            "scalable/actions/rabbitvcs-help.svg",
            "scalable/actions/rabbitvcs-update.svg",
            "scalable/actions/rabbitvcs-diff.svg",
            "scalable/actions/rabbitvcs-resolve.svg",
            "scalable/actions/rabbitvcs-about.svg",
            "scalable/actions/rabbitvcs-add.svg",
            "scalable/actions/rabbitvcs-changes.svg",
            "scalable/actions/rabbitvcs-createpatch.svg",
            "scalable/actions/rabbitvcs-merge.svg",
            "scalable/actions/rabbitvcs-drive.svg",
            "scalable/actions/rabbitvcs-stop.svg",
            "scalable/actions/rabbitvcs-checkout.svg",
            "scalable/actions/rabbitvcs-import.svg",
            "scalable/actions/rabbitvcs-branch.svg",
            "scalable/actions/rabbitvcs-refresh.svg",
            "scalable/actions/rabbitvcs-editconflicts.svg",
            "scalable/actions/rabbitvcs-monkey.svg",
            "scalable/actions/rabbitvcs-applypatch.svg",
            "scalable/actions/rabbitvcs-switch.svg",
            "scalable/actions/rabbitvcs-lock.svg",
            "scalable/actions/rabbitvcs-annotate.svg",
            "scalable/actions/rabbitvcs-compare.svg",
            "scalable/actions/rabbitvcs-revert.svg",
            "scalable/actions/rabbitvcs-bug.svg",
            "scalable/actions/rabbitvcs-cleanup.svg",
            "scalable/actions/rabbitvcs-clear.svg",
            "scalable/actions/rabbitvcs-unstage.svg",
            "scalable/actions/rabbitvcs-emblems.svg",
            "scalable/actions/rabbitvcs-relocate.svg",
            "scalable/actions/rabbitvcs-reset.svg",
            "scalable/actions/rabbitvcs-asynchronous.svg",
            "scalable/actions/rabbitvcs-commit.svg",
            "scalable/actions/rabbitvcs-checkmods.svg",
            "scalable/apps/rabbitvcs.svg", "scalable/apps/rabbitvcs-small.svg",
            "16x16/actions/rabbitvcs-push.png"
        ]

        rabbitvcs_icon_path = get_icon_path()
        for rel_icon_path in rabbitvcs_icons:
            icon_path = "%s/%s" % (rabbitvcs_icon_path, rel_icon_path)
            file = os.path.basename(rel_icon_path)
            (root, ext) = os.path.splitext(file)

            pixbuf = GdkPixbuf.Pixbuf.new_from_file(icon_path)
            iconset = Gtk.IconSet.new_from_pixbuf(pixbuf)
            factory.add(root, iconset)

        factory.add_default()

        # Create a global client we can use to do VCS related stuff
        self.vcs_client = VCS()

        self.status_checker = StatusChecker()

        self.status_checker.assert_version(EXT_VERSION)

        self.items_cache = {}

    def get_columns(self):
        """
        Return all the columns we support.

        """

        return (Nemo.Column(name="RabbitVCS::status_column",
                            attribute="status",
                            label=_("RVCS Status"),
                            description=""),
                Nemo.Column(name="RabbitVCS::revision_column",
                            attribute="revision",
                            label=_("RVCS Revision"),
                            description=""),
                Nemo.Column(name="RabbitVCS::author_column",
                            attribute="author",
                            label=_("RVCS Author"),
                            description=""),
                Nemo.Column(name="RabbitVCS::age_column",
                            attribute="age",
                            label=_("RVCS Age"),
                            description=""))

    def update_file_info(self, item):
        """

        C{update_file_info} is called only when:

          - When you enter a directory (once for each item but only when the
            item was modified since the last time it was listed)
          - When you refresh (once for each item visible)
          - When an item viewable from the current window is created or modified

        This is insufficient for our purpose because:

          - You're not notified about items you don't see (which is needed to
            keep the emblem for the directories above the item up-to-date)

        @type   item: NemoVFSFile
        @param  item:

        """
        enable_emblems = bool(int(settings.get("general", "enable_emblems")))
        enable_attrs = bool(int(settings.get("general", "enable_attributes")))

        if not (enable_emblems or enable_attrs):
            return Nemo.OperationResult.COMPLETE

        if not self.valid_uri(item.get_uri()):
            return Nemo.OperationResult.FAILED

        path = rabbitvcs.util.helper.unquote_url(
            self.get_local_path(item.get_uri()))

        # log.debug("update_file_info() called for %s" % path)

        invalidate = False
        if path in self.nemoVFSFile_table:
            invalidate = True

        # Always replace the item in the table with the one we receive, because
        # for example if an item is deleted and recreated the NemoVFSFile
        # we had before will be invalid (think pointers and such).
        self.nemoVFSFile_table[path] = item

        # This check should be pretty obvious :-)
        # TODO: how come the statuses for a few directories are incorrect
        # when we remove this line (detected as working copies, even though
        # they are not)? That shouldn't happen.
        is_in_a_or_a_working_copy = self.vcs_client.is_in_a_or_a_working_copy(
            path)
        if not is_in_a_or_a_working_copy: return Nemo.OperationResult.COMPLETE

        # Do our magic...

        # I have added extra logic in cb_status, using a list
        # (paths_from_callback) that should allow us to work around this for
        # now. But it'd be good to have an actual status monitor.

        found = False
        status = None
        # Could replace with (st for st in self.... if st.path ...).next()
        # Need to catch exception
        for idx in xrange(len(self.statuses_from_callback)):
            found = (self.statuses_from_callback[idx].path) == path
            if found: break

        if found:  # We're here because we were triggered by a callback
            status = self.statuses_from_callback[idx]
            del self.statuses_from_callback[idx]

        # Don't bother the checker if we already have the info from a callback
        if not found:
            status = \
                self.status_checker.check_status(path,
                                                 recurse=True,
                                                 summary=True,
                                                 callback=self.cb_status,
                                                 invalidate=invalidate)

        # FIXME: when did this get disabled?
        if enable_attrs: self.update_columns(item, path, status)
        if enable_emblems: self.update_status(item, path, status)

        return Nemo.OperationResult.COMPLETE

    def update_columns(self, item, path, status):
        """
        Update the columns (attributes) for a given Nemo item,
        filling them in with information from the version control
        server.

        """

        revision = ""
        if status.revision:
            revision = str(status.revision)

        age = ""
        if status.date:
            age = pretty_timedelta(
                datetime.datetime.fromtimestamp(status.date),
                datetime.datetime.now())

        author = ""
        if status.author:
            author = str(status.author)

        values = {
            "status": status.simple_content_status(),
            "revision": revision,
            "author": author,
            "age": age
        }

        for key, value in values.items():
            item.add_string_attribute(key, value)

    def update_status(self, item, path, status):
        if status.summary in rabbitvcs.ui.STATUS_EMBLEMS:
            item.add_emblem(rabbitvcs.ui.STATUS_EMBLEMS[status.summary])

    #~ @disable
    # @timeit
    # FIXME: this is a bottleneck. See generate_statuses() in
    # MainContextMenuConditions.
    def get_file_items_full(self, provider, window, items):
        """
        Menu activated with items selected. Nemo also calls this function
        when rendering submenus, even though this is not needed since the entire
        menu has already been returned.

        Note that calling C{nemoVFSFile.invalidate_extension_info()} will
        also cause get_file_items to be called.

        @type   window: NemoNavigationWindow
        @param  window:

        @type   items:  list of NemoVFSFile
        @param  items:

        @rtype:         list of MenuItems
        @return:        The context menu entries to add to the menu.

        """

        paths = []
        for item in items:
            if self.valid_uri(item.get_uri()):
                path = rabbitvcs.util.helper.unquote_url(
                    self.get_local_path(item.get_uri()))
                paths.append(path)
                self.nemoVFSFile_table[path] = item

        if len(paths) == 0: return []

        # log.debug("get_file_items_full() called")

        paths_str = "-".join(paths)

        conditions_dict = None
        if paths_str in self.items_cache:
            conditions_dict = self.items_cache[paths_str]
            if conditions_dict and conditions_dict != "in-progress" \
                    and hasattr(window, 'base_dir'):
                conditions = NemoMenuConditions(conditions_dict)
                menu = NemoMainContextMenu(self, window.base_dir, paths,
                                           conditions).get_menu()
                return menu

        if conditions_dict != "in-progress" and hasattr(window, 'base_dir'):
            self.status_checker.generate_menu_conditions_async(
                provider, window.base_dir, paths, self.update_file_items)
            self.items_cache[path] = "in-progress"

        return ()

    def get_file_items(self, window, items):
        paths = []
        for item in items:
            if self.valid_uri(item.get_uri()):
                path = rabbitvcs.util.helper.unquote_url(
                    self.get_local_path(item.get_uri()))
                paths.append(path)
                self.nemoVFSFile_table[path] = item

        if len(paths) == 0: return []

        # log.debug("get_file_items() called")

        return NemoMainContextMenu(self, window.base_dir, paths).get_menu()

    def update_file_items(self, provider, base_dir, paths, conditions_dict):
        paths_str = "-".join(paths)
        self.items_cache[paths_str] = conditions_dict
        Nemo.MenuProvider.emit_items_updated_signal(provider)

    #~ @disable
    # This is useful for profiling. Rename it to "get_background_items" and then
    # rename the real function "get_background_items_real".
    def get_background_items_profile(self, window, item):
        import cProfile
        import rabbitvcs.util.helper

        path = unicode(gnomevfs.get_local_path_from_uri(item.get_uri()),
                       "utf-8").replace("/", ":")

        profile_data_file = os.path.join(
            rabbitvcs.util.helper.get_home_folder(),
            "checkerservice_%s.stats" % path)

        prof = cProfile.Profile()
        retval = prof.runcall(self.get_background_items_real, window, item)
        prof.dump_stats(profile_data_file)
        log.debug("Dumped: %s" % profile_data_file)
        return retval

    def get_background_items_full(self, provider, window, item):
        """
        Menu activated on entering a directory. Builds context menu for File
        menu and for window background.

        @type   window: NemoNavigationWindow
        @param  window:

        @type   item:   NemoVFSFile
        @param  item:

        @rtype:         list of MenuItems
        @return:        The context menu entries to add to the menu.

        """

        if not self.valid_uri(item.get_uri()): return
        path = rabbitvcs.util.helper.unquote_url(
            self.get_local_path(item.get_uri()))
        self.nemoVFSFile_table[path] = item

        # log.debug("get_background_items_full() called")

        conditions_dict = None
        if path in self.items_cache:
            conditions_dict = self.items_cache[path]
            if conditions_dict and conditions_dict != "in-progress":
                conditions = NemoMenuConditions(conditions_dict)
                menu = NemoMainContextMenu(self, path, [path],
                                           conditions).get_menu()
                return menu

        window.base_dir = path

        if conditions_dict != "in-progress":
            self.status_checker.generate_menu_conditions_async(
                provider, path, [path], self.update_background_items)
            self.items_cache[path] = "in-progress"

        return ()

    def get_background_items(self, window, item):
        if not self.valid_uri(item.get_uri()): return
        path = rabbitvcs.util.helper.unquote_url(
            self.get_local_path(item.get_uri()))
        self.nemoVFSFile_table[path] = item

        # log.debug("get_background_items() called")

        window.base_dir = path

        return NemoMainContextMenu(self, path, [path]).get_menu()

    def update_background_items(self, provider, base_dir, paths,
                                conditions_dict):
        paths_str = "-".join(paths)
        conditions = NemoMenuConditions(conditions_dict)
        self.items_cache[paths_str] = conditions_dict
        Nemo.MenuProvider.emit_items_updated_signal(provider)

    #
    # Helper functions
    #

    def valid_uri(self, uri):
        """
        Check whether or not it's a good idea to have RabbitVCS do
        its magic for this URI. Some examples of URI schemes:

        x-nemo-desktop:/// # e.g. mounted devices on the desktop

        """

        if not uri.startswith("file://"): return False

        return True

    #
    # Some methods to help with keeping emblems up-to-date
    #

    def rescan_after_process_exit(self, proc, paths):
        def do_check():
            # We'll check the paths first (these were the paths that
            # were originally passed along to the context menu).
            #
            # This is needed among other things for:
            #
            #   - When a directory is normal and you add files inside it
            #
            for path in paths:
                # We're not interested in the result now, just the callback
                self.status_checker.check_status(path,
                                                 recurse=True,
                                                 invalidate=True,
                                                 callback=self.cb_status,
                                                 summary=True)

        self.execute_after_process_exit(proc, do_check)

    def execute_after_process_exit(self, proc, func=None):
        def is_process_still_alive():
            log.debug("is_process_still_alive() for pid: %i" % proc.pid)
            # First we need to see if the commit process is still running

            retval = proc.poll()

            log.debug("%s" % retval)

            still_going = (retval is None)

            if not still_going and callable(func):
                func()

            return still_going

        # Add our callback function on a 1 second timeout
        GObject.timeout_add_seconds(1, is_process_still_alive)

    #
    # Some other methods
    #

    def reload_settings(self, proc):
        """
        Used to re-load settings after the settings dialog has been closed.

        FIXME: This probably doesn't belong here, ideally the settings manager
        does this itself and make sure everything is reloaded properly
        after the settings dialogs saves.
        """
        def do_reload_settings():
            globals()["settings"] = SettingsManager()
            globals()["log"] = reload_log_settings()(
                "rabbitvcs.util.extensions.nemo")
            log.debug("Re-scanning settings")

        self.execute_after_process_exit(proc, do_reload_settings)

    #
    # Callbacks
    #

    def cb_status(self, status):
        """
        This is the callback that C{StatusMonitor} calls.

        @type   path:   string
        @param  path:   The path of the item something interesting happened to.

        @type   statuses: list of status objects
        @param  statuses: The statuses
        """
        if status.path in self.nemoVFSFile_table:
            item = self.nemoVFSFile_table[status.path]
            # We need to invalidate the extension info for only one reason:
            #
            # - Invalidating the extension info will cause Nemo to remove all
            #   temporary emblems we applied so we don't have overlay problems
            #   (with ourselves, we'd still have some with other extensions).
            #
            # After invalidating C{update_file_info} applies the correct emblem.
            # Since invalidation triggers an "update_file_info" call, we can
            # tell it NOT to invalidate the status checker path.
            self.statuses_from_callback.append(status)
            # NOTE! There is a call to "update_file_info" WITHIN the call to
            # invalidate_extension_info() - beware recursion!
            item.invalidate_extension_info()
            if status.path in self.items_cache:
                del self.items_cache[status.path]
        else:
            log.debug("Path [%s] not found in file table" % status.path)

    def get_property_pages(self, items):
        paths = []

        for item in items:
            if self.valid_uri(item.get_uri()):
                path = rabbitvcs.util.helper.unquote_url(
                    self.get_local_path(item.get_uri()))

                if self.vcs_client.is_in_a_or_a_working_copy(path):
                    paths.append(path)
                    self.nemoVFSFile_table[path] = item

        if len(paths) == 0: return []

        label = rabbitvcs.ui.property_page.PropertyPageLabel(
            claim_domain=False).get_widget()
        page = rabbitvcs.ui.property_page.PropertyPage(
            paths, claim_domain=False).get_widget()

        ppage = Nemo.PropertyPage(name='RabbitVCS::PropertyPage',
                                  label=label,
                                  page=page)

        return [ppage]

    def get_name_and_desc(self):
        return [("Nemo RabbitVCS:::Access RabbitVCS from the context menu")]
예제 #5
0
class RabbitVCS(nautilus.InfoProvider, nautilus.MenuProvider,
                 nautilus.ColumnProvider, nautilus.PropertyPageProvider):
    """
    This is the main class that implements all of our awesome features.

    """

    #: This is our lookup table for C{NautilusVFSFile}s which we need for attaching
    #: emblems. This is mostly a workaround for not being able to turn a path/uri
    #: into a C{NautilusVFSFile}. It looks like:::
    #:
    #:     nautilusVFSFile_table = {
    #:        "/foo/bar/baz": <NautilusVFSFile>
    #:
    #:     }
    #:
    #: Keeping track of C{NautilusVFSFile}s is a little bit complicated because
    #: when an item is moved (renamed) C{update_file_info} doesn't get called. So
    #: we also add C{NautilusVFSFile}s to this table from C{get_file_items} etc.
    # FIXME: this may be the source of the memory hogging seen in the extension
    # script itself.
    nautilusVFSFile_table = {}

    #: This is in case we want to permanently enable invalidation of the status
    #: checker info.
    always_invalidate = True

    #: When we get the statuses from the callback, put them here for further
    #: use. This is of the form: [("path/to", {...status dict...}), ...]
    statuses_from_callback = []

    def __init__(self):
        # Create a global client we can use to do VCS related stuff
        self.vcs_client = VCS()

        self.status_checker = StatusChecker()
        
        self.status_checker.assert_version(EXT_VERSION)
        
        self.items_cache = {}
        
    def get_columns(self):
        """
        Return all the columns we support.

        """

        return (
            nautilus.Column(
                "RabbitVCS::status_column",
                "status",
                _("RVCS Status"),
                ""
            ),
            nautilus.Column(
                "RabbitVCS::revision_column",
                "revision",
                _("RVCS Revision"),
                ""
            ),
            nautilus.Column(
                "RabbitVCS::author_column",
                "author",
                _("RVCS Author"),
                ""
            ),
            nautilus.Column(
                "RabbitVCS::age_column",
                "age",
                _("RVCS Age"),
                ""
            )
        )

    def update_file_info(self, item):
        """

        C{update_file_info} is called only when:

          - When you enter a directory (once for each item but only when the
            item was modified since the last time it was listed)
          - When you refresh (once for each item visible)
          - When an item viewable from the current window is created or modified

        This is insufficient for our purpose because:

          - You're not notified about items you don't see (which is needed to
            keep the emblem for the directories above the item up-to-date)

        @type   item: NautilusVFSFile
        @param  item:

        """
        enable_emblems = bool(int(settings.get("general", "enable_emblems")))
        enable_attrs = bool(int(settings.get("general", "enable_attributes")))
        
        if not (enable_emblems or enable_attrs): return nautilus.OPERATION_COMPLETE
                
        if not self.valid_uri(item.get_uri()): return nautilus.OPERATION_FAILED
        
        path = unicode(gnomevfs.get_local_path_from_uri(item.get_uri()), "utf-8")

        # log.debug("update_file_info() called for %s" % path)

        invalidate = False
        if path in self.nautilusVFSFile_table:
            invalidate = True

        # Always replace the item in the table with the one we receive, because
        # for example if an item is deleted and recreated the NautilusVFSFile
        # we had before will be invalid (think pointers and such).
        self.nautilusVFSFile_table[path] = item

        # This check should be pretty obvious :-)
        # TODO: how come the statuses for a few directories are incorrect
        # when we remove this line (detected as working copies, even though
        # they are not)? That shouldn't happen.
        is_in_a_or_a_working_copy = self.vcs_client.is_in_a_or_a_working_copy(path)
        if not is_in_a_or_a_working_copy: return nautilus.OPERATION_COMPLETE

        # Do our magic...

        # I have added extra logic in cb_status, using a list
        # (paths_from_callback) that should allow us to work around this for
        # now. But it'd be good to have an actual status monitor.

        found = False
        status = None
        # Could replace with (st for st in self.... if st.path ...).next()
        # Need to catch exception
        for idx in xrange(len(self.statuses_from_callback)):
            found = (self.statuses_from_callback[idx].path) == path
            if found: break

        if found: # We're here because we were triggered by a callback
            status = self.statuses_from_callback[idx]
            del self.statuses_from_callback[idx]

        # Don't bother the checker if we already have the info from a callback
        if not found:
            status = \
                self.status_checker.check_status(path,
                                                 recurse=True,
                                                 summary=True,
                                                 callback=self.cb_status,
                                                 invalidate=invalidate)

        # FIXME: when did this get disabled?
        if enable_attrs: self.update_columns(item, path, status)
        if enable_emblems: self.update_status(item, path, status)
        
        return nautilus.OPERATION_COMPLETE

    def update_columns(self, item, path, status):
        """
        Update the columns (attributes) for a given Nautilus item,
        filling them in with information from the version control
        server.

        """

        revision = ""
        if status.revision:
            revision = str(status.revision)

        age = ""
        if status.date:
            age = pretty_timedelta(
                datetime.datetime.fromtimestamp(status.date),
                datetime.datetime.now()
            )

        author = ""
        if status.author:
            author = str(status.author)

        values = {
            "status": status.simple_content_status(),
            "revision": revision,
            "author": author,
            "age": age
        }

        for key, value in values.items():
            item.add_string_attribute(key, value)

    def update_status(self, item, path, status):
        if status.summary in rabbitvcs.ui.STATUS_EMBLEMS:
            item.add_emblem(rabbitvcs.ui.STATUS_EMBLEMS[status.summary])

    #~ @disable
    # @timeit
    # FIXME: this is a bottleneck. See generate_statuses() in
    # MainContextMenuConditions.
    def get_file_items_full(self, provider, window, items):
        """
        Menu activated with items selected. Nautilus also calls this function
        when rendering submenus, even though this is not needed since the entire
        menu has already been returned.

        Note that calling C{nautilusVFSFile.invalidate_extension_info()} will
        also cause get_file_items to be called.

        @type   window: NautilusNavigationWindow
        @param  window:

        @type   items:  list of NautilusVFSFile
        @param  items:

        @rtype:         list of MenuItems
        @return:        The context menu entries to add to the menu.

        """

        paths = []
        for item in items:
            if self.valid_uri(item.get_uri()):
                path = unicode(gnomevfs.get_local_path_from_uri(item.get_uri()), "utf-8")
                paths.append(path)
                self.nautilusVFSFile_table[path] = item

        if len(paths) == 0: return []
        
        # log.debug("get_file_items_full() called")

        paths_str = "-".join(paths)
        
        conditions_dict = None
        if paths_str in self.items_cache:
            conditions_dict = self.items_cache[paths_str]
            if conditions_dict and conditions_dict != "in-progress":
                conditions = NautilusMenuConditions(conditions_dict)
                menu = NautilusMainContextMenu(self, window.get_data("base_dir"), paths, conditions).get_menu()
                return menu
        
        if conditions_dict != "in-progress":
            self.status_checker.generate_menu_conditions_async(provider, window.get_data("base_dir"), paths, self.update_file_items)        
            self.items_cache[path] = "in-progress"
            
        return ()

    def get_file_items(self, window, items):
        paths = []
        for item in items:
            if self.valid_uri(item.get_uri()):
                path = unicode(gnomevfs.get_local_path_from_uri(item.get_uri()), "utf-8")
                paths.append(path)
                self.nautilusVFSFile_table[path] = item

        if len(paths) == 0: return []
        
        # log.debug("get_file_items() called")
        
        return NautilusMainContextMenu(self, window.get_data("base_dir"), paths).get_menu()

    def update_file_items(self, provider, base_dir, paths, conditions_dict):
        paths_str = "-".join(paths)
        self.items_cache[paths_str] =  conditions_dict
        self.emit_items_updated_signal(provider)

    #~ @disable
    # This is useful for profiling. Rename it to "get_background_items" and then
    # rename the real function "get_background_items_real". 
    def get_background_items_profile(self, window, item):
        import cProfile
        import rabbitvcs.util.helper
        
        path = unicode(gnomevfs.get_local_path_from_uri(item.get_uri()),
                       "utf-8").replace("/", ":")
        
        profile_data_file = os.path.join(
                               rabbitvcs.util.helper.get_home_folder(),
                               "checkerservice_%s.stats" % path)
        
        prof = cProfile.Profile()
        retval = prof.runcall(self.get_background_items_real, window, item)
        prof.dump_stats(profile_data_file)
        log.debug("Dumped: %s" % profile_data_file)
        return retval
       
    def get_background_items_full(self, provider, window, item):
        """
        Menu activated on entering a directory. Builds context menu for File
        menu and for window background.

        @type   window: NautilusNavigationWindow
        @param  window:

        @type   item:   NautilusVFSFile
        @param  item:

        @rtype:         list of MenuItems
        @return:        The context menu entries to add to the menu.

        """

        if not self.valid_uri(item.get_uri()): return
        path = unicode(gnomevfs.get_local_path_from_uri(item.get_uri()), "utf-8")
        self.nautilusVFSFile_table[path] = item

        # log.debug("get_background_items_full() called")

        conditions_dict = None
        if path in self.items_cache:
            conditions_dict = self.items_cache[path]
            if conditions_dict and conditions_dict != "in-progress":
                conditions = NautilusMenuConditions(conditions_dict)
                menu = NautilusMainContextMenu(self, path, [path], conditions).get_menu()                
                return menu

        window.set_data("base_dir", path)

        if conditions_dict != "in-progress":
            self.status_checker.generate_menu_conditions_async(provider, path, [path], self.update_background_items)
            self.items_cache[path] = "in-progress"
                    
        return ()

    def get_background_items(self, window, item):
        if not self.valid_uri(item.get_uri()): return
        path = unicode(gnomevfs.get_local_path_from_uri(item.get_uri()), "utf-8")
        self.nautilusVFSFile_table[path] = item

        # log.debug("get_background_items() called")
        
        window.set_data("base_dir", path)
        
        return NautilusMainContextMenu(self, path, [path]).get_menu()

    def update_background_items(self, provider, base_dir, paths, conditions_dict):
        paths_str = "-".join(paths)
        conditions = NautilusMenuConditions(conditions_dict)
        self.items_cache[paths_str] =  conditions_dict
        self.emit_items_updated_signal(provider)

    #
    # Helper functions
    #

    def valid_uri(self, uri):
        """
        Check whether or not it's a good idea to have RabbitVCS do
        its magic for this URI. Some examples of URI schemes:

        x-nautilus-desktop:/// # e.g. mounted devices on the desktop

        """

        if not uri.startswith("file://"): return False

        return True

    #
    # Some methods to help with keeping emblems up-to-date
    #

    def rescan_after_process_exit(self, proc, paths):

        def do_check():
            # We'll check the paths first (these were the paths that
            # were originally passed along to the context menu).
            #
            # This is needed among other things for:
            #
            #   - When a directory is normal and you add files inside it
            #
            for path in paths:
                # We're not interested in the result now, just the callback
                self.status_checker.check_status(path,
                                                 recurse=True,
                                                 invalidate=True,
                                                 callback=self.cb_status,
                                                 summary=True)

        self.execute_after_process_exit(proc, do_check)

    def execute_after_process_exit(self, proc, func=None):

        def is_process_still_alive():
            log.debug("is_process_still_alive() for pid: %i" % proc.pid)
            # First we need to see if the commit process is still running

            retval = proc.poll()

            log.debug("%s" % retval)

            still_going = (retval is None)

            if not still_going and callable(func):
                func()

            return still_going

        # Add our callback function on a 1 second timeout
        gobject.timeout_add_seconds(1, is_process_still_alive)

    #
    # Some other methods
    #

    def reload_settings(self, proc):
        """
        Used to re-load settings after the settings dialog has been closed.

        FIXME: This probably doesn't belong here, ideally the settings manager
        does this itself and make sure everything is reloaded properly
        after the settings dialogs saves.
        """

        def do_reload_settings():
            globals()["settings"] = SettingsManager()
            globals()["log"] = reload_log_settings()("rabbitvcs.util.extensions.nautilus")
            log.debug("Re-scanning settings")

        self.execute_after_process_exit(proc, do_reload_settings)


    #
    # Callbacks
    #

    def cb_status(self, status):
        """
        This is the callback that C{StatusMonitor} calls.

        @type   path:   string
        @param  path:   The path of the item something interesting happened to.

        @type   statuses: list of status objects
        @param  statuses: The statuses
        """
        if status.path in self.nautilusVFSFile_table:
            item = self.nautilusVFSFile_table[status.path]
            # We need to invalidate the extension info for only one reason:
            #
            # - Invalidating the extension info will cause Nautilus to remove all
            #   temporary emblems we applied so we don't have overlay problems
            #   (with ourselves, we'd still have some with other extensions).
            #
            # After invalidating C{update_file_info} applies the correct emblem.
            # Since invalidation triggers an "update_file_info" call, we can
            # tell it NOT to invalidate the status checker path.
            self.statuses_from_callback.append(status)
            # NOTE! There is a call to "update_file_info" WITHIN the call to
            # invalidate_extension_info() - beware recursion!
            item.invalidate_extension_info()
            if status.path in self.items_cache:
                del self.items_cache[status.path]
        else:
            log.debug("Path [%s] not found in file table" % status.path)

    def get_property_pages(self, items):

        paths = []

        for item in items:
            if self.valid_uri(item.get_uri()):
                path = unicode(gnomevfs.get_local_path_from_uri(item.get_uri()), "utf-8")
                
                if self.vcs_client.is_in_a_or_a_working_copy(path):
                    paths.append(path)
                    self.nautilusVFSFile_table[path] = item

        if len(paths) == 0: return []

        label = rabbitvcs.ui.property_page.PropertyPageLabel(claim_domain=False).get_widget()
        page = rabbitvcs.ui.property_page.PropertyPage(paths, claim_domain=False).get_widget()

        ppage = nautilus.PropertyPage('RabbitVCS::PropertyPage',
            label,
            page)

        return [ppage]
예제 #6
0
    def __init__(self):
        factory = Gtk.IconFactory()

        rabbitvcs_icons = [
            "scalable/actions/rabbitvcs-settings.svg",
            "scalable/actions/rabbitvcs-export.svg",
            "scalable/actions/rabbitvcs-properties.svg",
            "scalable/actions/rabbitvcs-show_log.svg",
            "scalable/actions/rabbitvcs-delete.svg",
            "scalable/actions/rabbitvcs-run.svg",
            "scalable/actions/rabbitvcs-unlock.svg",
            "scalable/actions/rabbitvcs-dbus.svg",
            "scalable/actions/rabbitvcs-rename.svg",
            "scalable/actions/rabbitvcs-help.svg",
            "scalable/actions/rabbitvcs-update.svg",
            "scalable/actions/rabbitvcs-diff.svg",
            "scalable/actions/rabbitvcs-resolve.svg",
            "scalable/actions/rabbitvcs-about.svg",
            "scalable/actions/rabbitvcs-add.svg",
            "scalable/actions/rabbitvcs-changes.svg",
            "scalable/actions/rabbitvcs-createpatch.svg",
            "scalable/actions/rabbitvcs-merge.svg",
            "scalable/actions/rabbitvcs-drive.svg",
            "scalable/actions/rabbitvcs-stop.svg",
            "scalable/actions/rabbitvcs-checkout.svg",
            "scalable/actions/rabbitvcs-import.svg",
            "scalable/actions/rabbitvcs-branch.svg",
            "scalable/actions/rabbitvcs-refresh.svg",
            "scalable/actions/rabbitvcs-editconflicts.svg",
            "scalable/actions/rabbitvcs-monkey.svg",
            "scalable/actions/rabbitvcs-applypatch.svg",
            "scalable/actions/rabbitvcs-switch.svg",
            "scalable/actions/rabbitvcs-lock.svg",
            "scalable/actions/rabbitvcs-annotate.svg",
            "scalable/actions/rabbitvcs-compare.svg",
            "scalable/actions/rabbitvcs-revert.svg",
            "scalable/actions/rabbitvcs-bug.svg",
            "scalable/actions/rabbitvcs-cleanup.svg",
            "scalable/actions/rabbitvcs-clear.svg",
            "scalable/actions/rabbitvcs-unstage.svg",
            "scalable/actions/rabbitvcs-emblems.svg",
            "scalable/actions/rabbitvcs-relocate.svg",
            "scalable/actions/rabbitvcs-reset.svg",
            "scalable/actions/rabbitvcs-asynchronous.svg",
            "scalable/actions/rabbitvcs-commit.svg",
            "scalable/actions/rabbitvcs-checkmods.svg",
            "scalable/apps/rabbitvcs.svg",
            "scalable/apps/rabbitvcs-small.svg",
            "16x16/actions/rabbitvcs-push.png"
        ]
        
        rabbitvcs_icon_path = get_icon_path()
        for rel_icon_path in rabbitvcs_icons:
            icon_path = "%s/%s" % (rabbitvcs_icon_path, rel_icon_path)
            file = os.path.basename(rel_icon_path)
            (root, ext) = os.path.splitext(file)

            pixbuf = GdkPixbuf.Pixbuf.new_from_file(icon_path)
            iconset = Gtk.IconSet.new_from_pixbuf(pixbuf)
            factory.add(root, iconset)

        factory.add_default()
    
        # Create a global client we can use to do VCS related stuff
        self.vcs_client = VCS()

        self.status_checker = StatusChecker()
        
        self.status_checker.assert_version(EXT_VERSION)
        
        self.items_cache = {}