This file is indexed.

/usr/share/pyshared/hamster/client.py is in hamster-applet 2.91.3+git20120514.b9fec3e1-1ubuntu1.

This file is owned by root:root, with mode 0o644.

The actual contents of the file can be viewed below.

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
# - coding: utf-8 -

# Copyright (C) 2007 Patryk Zawadzki <patrys at pld-linux.org>
# Copyright (C) 2007-2009 Toms Baugis <toms.baugis@gmail.com>

# This file is part of Project Hamster.

# Project Hamster is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.

# Project Hamster is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.

# You should have received a copy of the GNU General Public License
# along with Project Hamster.  If not, see <http://www.gnu.org/licenses/>.


import datetime as dt
from calendar import timegm
import dbus, dbus.mainloop.glib
import gobject
from lib import stuff, trophies



def from_dbus_fact(fact):
    """unpack the struct into a proper dict"""
    return stuff.Fact(fact[4],
                      start_time  = dt.datetime.utcfromtimestamp(fact[1]),
                      end_time = dt.datetime.utcfromtimestamp(fact[2]) if fact[2] else None,
                      description = fact[3],
                      activity_id = fact[5],
                      category = fact[6],
                      tags = fact[7],
                      date = dt.datetime.utcfromtimestamp(fact[8]).date(),
                      delta = dt.timedelta(days = fact[9] // (24 * 60 * 60),
                                           seconds = fact[9] % (24 * 60 * 60)),
            id = fact[0]
            )

class Storage(gobject.GObject):
    """Hamster client class, communicating to hamster storage daemon via d-bus.
       Subscribe to the `tags-changed`, `facts-changed` and `activities-changed`
       signals to be notified when an appropriate factoid of interest has been
       changed.

       In storage a distinguishment is made between the classificator of
       activities and the event in tracking log.
       When talking about the event we use term 'fact'. For the classificator
       we use term 'activity'.
       The relationship is - one activity can be used in several facts.
       The rest is hopefully obvious. But if not, please file bug reports!
    """
    __gsignals__ = {
        "tags-changed": (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, ()),
        "facts-changed": (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, ()),
        "activities-changed": (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, ()),
        "toggle-called": (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, ()),
    }

    def __init__(self):
        gobject.GObject.__init__(self)

        dbus.mainloop.glib.DBusGMainLoop(set_as_default=True)
        self.bus = dbus.SessionBus()
        self._connection = None # will be initiated on demand

        self.bus.add_signal_receiver(self._on_tags_changed, 'TagsChanged', 'org.gnome.Hamster')
        self.bus.add_signal_receiver(self._on_facts_changed, 'FactsChanged', 'org.gnome.Hamster')
        self.bus.add_signal_receiver(self._on_activities_changed, 'ActivitiesChanged', 'org.gnome.Hamster')
        self.bus.add_signal_receiver(self._on_toggle_called, 'ToggleCalled', 'org.gnome.Hamster')

        self.bus.add_signal_receiver(self._on_dbus_connection_change, 'NameOwnerChanged',
                                     'org.freedesktop.DBus', arg0='org.gnome.Hamster')
    @staticmethod
    def _to_dict(columns, result_list):
        return [dict(zip(columns, row)) for row in result_list]

    @property
    def conn(self):
        if not self._connection:
            self._connection = dbus.Interface(self.bus.get_object('org.gnome.Hamster',
                                                              '/org/gnome/Hamster'),
                                              dbus_interface='org.gnome.Hamster')
        return self._connection

    def _on_dbus_connection_change(self, name, old, new):
        self._connection = None

    def _on_tags_changed(self):
        self.emit("tags-changed")

    def _on_facts_changed(self):
        self.emit("facts-changed")

    def _on_activities_changed(self):
        self.emit("activities-changed")

    def _on_toggle_called(self):
        self.emit("toggle-called")

    def toggle(self):
        """toggle visibility of the main application window if any"""
        self.conn.Toggle()

    def get_todays_facts(self):
        """returns facts of the current date, respecting hamster midnight
           hamster midnight is stored in gconf, and presented in minutes
        """
        return [from_dbus_fact(fact) for fact in self.conn.GetTodaysFacts()]

    def get_facts(self, date, end_date = None, search_terms = ""):
        """Returns facts for the time span matching the optional filter criteria.
           In search terms comma (",") translates to boolean OR and space (" ")
           to boolean AND.
           Filter is applied to tags, categories, activity names and description
        """
        date = timegm(date.timetuple())
        end_date = end_date or 0
        if end_date:
            end_date = timegm(end_date.timetuple())

        return [from_dbus_fact(fact) for fact in self.conn.GetFacts(date,
                                                                    end_date,
                                                                    search_terms)]

    def get_activities(self, search = ""):
        """returns list of activities name matching search criteria.
           results are sorted by most recent usage.
           search is case insensitive
        """
        return self._to_dict(('name', 'category'), self.conn.GetActivities(search))

    def get_categories(self):
        """returns list of categories"""
        return self._to_dict(('id', 'name'), self.conn.GetCategories())

    def get_tags(self, only_autocomplete = False):
        """returns list of all tags. by default only those that have been set for autocomplete"""
        return self._to_dict(('id', 'name', 'autocomplete'), self.conn.GetTags(only_autocomplete))


    def get_tag_ids(self, tags):
        """find tag IDs by name. tags should be a list of labels
           if a requested tag had been removed from the autocomplete list, it
           will be ressurrected. if tag with such label does not exist, it will
           be created.
           on database changes the `tags-changed` signal is emitted.
        """
        return self._to_dict(('id', 'name', 'autocomplete'), self.conn.GetTagIds(tags))

    def update_autocomplete_tags(self, tags):
        """update list of tags that should autocomplete. this list replaces
           anything that is currently set"""
        self.conn.SetTagsAutocomplete(tags)

    def get_fact(self, id):
        """returns fact by it's ID"""
        return from_dbus_fact(self.conn.GetFact(id))

    def add_fact(self, fact, temporary_activity = False):
        """Add fact. activity name can use the
        `[-]start_time[-end_time] activity@category, description #tag1 #tag2`
        syntax, or params can be stated explicitly.
        Params will take precedence over the derived values.
        start_time defaults to current moment.
        """
        if not fact.activity:
            return None

        serialized = fact.serialized_name()

        start_timestamp = timegm((fact.start_time or dt.datetime.now()).timetuple())

        end_timestamp = fact.end_time or 0
        if end_timestamp:
            end_timestamp = timegm(end_timestamp.timetuple())

        new_id = self.conn.AddFact(serialized,
                                   start_timestamp,
                                   end_timestamp,
                                   temporary_activity)

        # TODO - the parsing should happen just once and preferably here
        # we should feed (serialized_activity, start_time, end_time) into AddFact and others
        if new_id:
            trophies.checker.check_fact_based(fact)
        return new_id

    def stop_tracking(self, end_time = None):
        """Stop tracking current activity. end_time can be passed in if the
        activity should have other end time than the current moment"""
        end_time = timegm((end_time or dt.datetime.now()).timetuple())
        return self.conn.StopTracking(end_time)

    def remove_fact(self, fact_id):
        "delete fact from database"
        self.conn.RemoveFact(fact_id)

    def update_fact(self, fact_id, fact, temporary_activity = False):
        """Update fact values. See add_fact for rules.
        Update is performed via remove/insert, so the
        fact_id after update should not be used anymore. Instead use the ID
        from the fact dict that is returned by this function"""


        start_time = timegm((fact.start_time or dt.datetime.now()).timetuple())

        end_time = fact.end_time or 0
        if end_time:
            end_time = timegm(end_time.timetuple())

        new_id =  self.conn.UpdateFact(fact_id,
                                       fact.serialized_name(),
                                       start_time,
                                       end_time,
                                       temporary_activity)

        trophies.checker.check_update_based(fact_id, new_id, fact)
        return new_id


    def get_category_activities(self, category_id = None):
        """Return activities for category. If category is not specified, will
        return activities that have no category"""
        category_id = category_id or -1
        return self._to_dict(('id', 'name', 'category_id', 'category'), self.conn.GetCategoryActivities(category_id))

    def get_category_id(self, category_name):
        """returns category id by name"""
        return self.conn.GetCategoryId(category_name)

    def get_activity_by_name(self, activity, category_id = None, resurrect = True):
        """returns activity dict by name and optionally filtering by category.
           if activity is found but is marked as deleted, it will be resurrected
           unless told otherwise in the resurrect param
        """
        category_id = category_id or 0
        return self.conn.GetActivityByName(activity, category_id, resurrect)

    # category and activity manipulations (normally just via preferences)
    def remove_activity(self, id):
        self.conn.RemoveActivity(id)

    def remove_category(self, id):
        self.conn.RemoveCategory(id)

    def change_category(self, id, category_id):
        return self.conn.ChangeCategory(id, category_id)

    def update_activity(self, id, name, category_id):
        return self.conn.UpdateActivity(id, name, category_id)

    def add_activity(self, name, category_id = -1):
        return self.conn.AddActivity(name, category_id)

    def update_category(self, id, name):
        return self.conn.UpdateCategory(id, name)

    def add_category(self, name):
        return self.conn.AddCategory(name)