This file is indexed.

/usr/share/pyshared/gplugs/projecttracker.py is in gozerbot 0.99.1-2.

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
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
# gozerplugs/projecttracker.py
#
# Hans van Kranenburg <hans@knorrie.org>


""" track hours spent working on projects

Persitent data is stored in a gozerbot Persist object, 'projects'

user is owner of a project:
   projects.data[username][project] exists
   contrib.data[username][project] MUST NOT exist

   projects.data[username][project]['hours'] may exist
       dict: {user: total hours, ...}
   projects.data[username][project]['desc'] may exist
       string: project description
   projects.data[username][project]['share'] may exist
       list: other contributing users


user contributes to a project of another user:
   contrib.data[username][project] exists
       string: project owner username
   projects.data[username][project] MUST NOT exist


"""

__copyright__ = 'this file is in the public domain'
__gendocfirst__ = ['pt-add', ]
__gendoclast__ = ['pt-del', ]

from gozerbot.utils.generic import convertpickle
from gozerbot.commands import cmnds
from gozerbot.examples import examples
from gozerbot.users import users
from gozerbot.datadir import datadir
from gozerbot.persist.persist import PlugPersist
from gozerbot.plughelp import plughelp
from gozerbot.tests import tests

plughelp.add('projecttracker', 'track hours spent working on projects')

## UPGRADE PART

def upgrade():
    convertpickle(datadir + os.sep + 'old' + os.sep + 'pt-projects', \
datadir + os.sep + 'plugs' + os.sep + 'projecttracker' + os.sep + 'pt-projects')
    convertpickle(datadir + os.sep + 'old' + os.sep + 'pt-contrib', \
datadir + os.sep + 'plugs' + os.sep + 'projecttracker' + os.sep + 'pt-contrib')

## END UPGRADE PART

import os

projects = PlugPersist('pt-projects')
contrib  = PlugPersist('pt-contrib') 
if not projects.data:
    projects = PlugPersist('pt-projects')
    if not projects.data:
        projects.data = {}
if not contrib.data:
    contrib.data = {}

def size():
    """ return number of projects """
    size = 0
    for userprojects in projects.data:
        size += len(userprojects)
    return size

#
# projects we pwn
#

def addproject(username, project, desc):
    """ add project to projects of username """
    if not projects.data.has_key(username):
        projects.data[username] = {}
    if not projects.data[username].has_key(project):
        projects.data[username][project] = {}
    if desc:
        projects.data[username][project]['desc'] = desc
    projects.save()

def hasproject(username, project):
    """ look if this user owns a project """
    if projects.data.has_key(username) and \
    projects.data[username].has_key(project):
        return True
    return False

def delproject(username, project):
    """ delete a project """
    try:
        if projects.data[username].has_key(project):
            # delete contributions
            if projects.data[username][project].has_key('share'):
                for otheruser in \
                projects.data[username][project]['share']:
                    try:
                        del contrib.data[otheruser][project]
                    except KeyError:
                        pass
            # delete project
            del projects.data[username][project]
    except KeyError:
        pass
    projects.save()
    contrib.save()

def getprojects(username):
    """ return projects owned by username """
    try:
        return projects.data[username].keys()
    except KeyError:
        return []

def setdesc(username, project, desc):
    """ alter project description """
    try:
        projects.data[username][project]['desc'] = desc
	projects.save()
    except KeyError:
        pass

def getdesc(username, project):
    """ return description of project """
    try:
        return projects.data[username][project]['desc']
    except KeyError:
        return None

#
# project sharing
#

def addshare(username, project, otheruser):
    """ share our project with another user
    make sure that 1) we own the project and 2) the other user has
    no project with this name and does not contribute to a project
    with the same name before calling this function    
    """
    # add other user to the sharelist of the project
    if not projects.data[username][project].has_key('share'):
        projects.data[username][project]['share'] = [otheruser, ]
    else:
        projects.data[username][project]['share'].append(otheruser)
    projects.save()
    # add project and project owner to otherusers contrib-list
    if not contrib.data.has_key(otheruser):
        contrib.data[otheruser] = {project: username}
    else:
        contrib.data[otheruser][project] = username
    contrib.save()


def delshare(username, project, otheruser):
    """ stop sharing our project with another user """
    # remove other user from the sharelist
    try:
        projects.data[username][project]['share'].remove(otheruser)
        projects.save()
    except KeyError:
        pass
    try:
        del contrib.data[otheruser][project]
        contrib.save()
    except KeyError:
        pass

def hascontrib(username, project):
    """ does this user contribute to a project with this name? """
    try:
        return contrib.data[username].has_key(project)
    except KeyError:
        return False

def getowner(username, project):
    """ get owner of a project we contribute to """
    if hascontrib(username, project):
        return contrib.data[username][project]
    return None

def getcontrib(username):
    """ get names of projects we contribute to """
    try:
        return contrib.data[username].keys()
    except KeyError:
        return []

def delcontrib(username, project):
    """ stop contributing to a project of someone else """
    if hascontrib(username, project):
        owner = getowner(username, project)
        delshare(owner, project, username)

def getsharelist(username, project):
    """ get users sharing a project with """
    if projects.data[username][project].has_key('share'):
        return projects.data[username][project]['share']
    else:
        return []

#
# do some work... actually ;]
#

def addhours(owner, project, username, hours):
    """ add work hours to a project """

    if not projects.data[owner][project].has_key('hours'):
        projects.data[owner][project]['hours'] = {}
    if not projects.data[owner][project]['hours'].has_key(username):
        projects.data[owner][project]['hours'][username] = hours
    else:
        projects.data[owner][project]['hours'][username] += hours
    projects.save()
    return projects.data[owner][project]['hours'][username]

def gethourslist(username, project):
    """ get list of contributors and hours of work
        for a project
    """
    result = []
    if hasproject(username, project):
        owner = username
    elif hascontrib(username, project):
        owner = getowner(username, project)
    else:
        result.append("unknown project %s" % project)
        return result
    if projects.data[owner][project].has_key('hours'):
        # (h)ours(l)ist
        hl = projects.data[owner][project]['hours']
        if hl:
            # (c)ontributor to the project
            for c in hl:
                if hl[c] != 0:
                    result.append('%s (%s)' % (c, hl[c]))
    if not result:
        result.append("no work done yet")
    return result

#
# command handlers
#

def handle_projectadd(bot, ievent):
    """ pt-add <projectname> <description> .. add project """
    if not ievent.rest:
        ievent.missing('<projectname> [<description>]')
        return
    username = users.getname(ievent.userhost)
    try:
        project, desc = ievent.rest.split(' ', 1)
    except ValueError:
        project = ievent.rest
        desc = None
    project = project.strip().lower()
    if hasproject(username, project) or hascontrib(username, project):
        ievent.reply('project %s already exists' % project)
        return
    if desc:
        desc = desc.strip()
    addproject(username, project, desc)
    ievent.reply('project %s added' % (project))

cmnds.add('pt-add', handle_projectadd, 'USER')
examples.add('pt-add', 'pt-add <projectname> [<description>] .. \
add a project to the project tracker', 'pt-add gc Gozerbot coden ;]')
tests.add('pt-add gozerbot coden', ' added|already')

def handle_projectlist(bot, ievent):
    """ show available projects """
    username = users.getname(ievent.userhost)
    result = []
    l = getprojects(username)
    for project in l:
        desc = getdesc(username, project)
        if desc:
            result.append('%s (%s)' % (desc, project))
        else:
            result.append(project)
    l = getcontrib(username)
    for project in l:
        owner = getowner(username, project)
        desc = getdesc(owner, project)
        if desc:
            result.append('%s (%s, owner=%s)' % (desc, project, \
            owner))
        else:
            result.append('%s, owner=%s' % (project, owner))
    if result:
        ievent.reply('', result, dot=True)
    else:
        ievent.reply('no projects found')

cmnds.add('pt-list', handle_projectlist, 'USER')
examples.add('pt-list', 'list available projects', 'pt-list')
tests.add('pt-list', 'gozerbot')

def handle_projectdel(bot, ievent):
    """ delete a project or stop contributing to it """
    try:
        project = ievent.rest.strip().lower()
    except ValueError:
        ievent.missing('<projectname>')
        return
    username = users.getname(ievent.userhost)
    if hasproject(username, project):
        delproject(username, project)
        ievent.reply('project %s deleted' % (project))
    elif hascontrib(username, project):
        delcontrib(username, project)
        ievent.reply('no longer contributing to %s' % (project))
    else:
        ievent.reply('no project %s' % project)

cmnds.add('pt-del', handle_projectdel, 'USER')
examples.add('pt-del', 'delete a project from the project tracker or \
stop contibuting to it', 'pt-del mekker')
tests.add('pt-del mekker')

def handle_addhours(bot, ievent):
    """ add hours to a project """
    username = users.getname(ievent.userhost)
    try:
        project, rest = ievent.rest.split(' ', 1)
    except ValueError:
        ievent.missing('<projectname> <hours> [<comment>]')
        return
    project = project.strip().lower()

    if hasproject(username, project):
        owner = username
    elif hascontrib(username, project):
        owner = getowner(username, project)
    else:
        ievent.reply('no project %s' % project)
        return
    try:
        hours, comment = rest.split(' ', 1)
    except ValueError:
        hours = rest
        comment = ''
    try:
        hours = float(hours)
    except ValueError:
        ievent.reply('%s is not a number' % hours)
        return
    total = addhours(owner, project, username, hours)
    desc = getdesc(owner, project) or project
#    h = 'hours'
#    if abs(hours) == 1:
#        h = 'hour'
#    if hours < 0:
#        ievent.reply('removed %s %s from %s' % (hours, h, project))
#        return
#    ievent.reply('added %s %s to %s' % (hours, h, project))
     
    ievent.reply('hours spent on %s is now: %s' % (desc, total))

cmnds.add('pt', handle_addhours, 'USER')
examples.add('pt', 'track time spent on a project', 'pt gc 4')
tests.add('pt gozerbot coden 4')

def handle_report(bot, ievent):
    """ display project report """
    username = users.getname(ievent.userhost)
    if not ievent.rest:
        result = []
        l = getprojects(username)
        for project in l:
            desc = getdesc(username, project) or project
            report = gethourslist(username, project)
            result.append('%s: %s' % (desc, ' '.join(report)))
        l = getcontrib(username)
        for project in l:
            owner = getowner(username, project)
            desc = getdesc(owner, project) or project
            report = gethourslist(owner, project)
            result.append('%s: %s' % (desc, ' '.join(report)))
        if result:
            ievent.reply('', result, dot=True)
        else:
            ievent.reply('no projects found')
    else:
        project = ievent.rest.strip().lower()
        if hasproject(username, project):
            owner = username
        elif hascontrib(username, project):
            owner = getowner(username, project)
        else:
            ievent.reply('no project %s' % project)
            return
        desc = getdesc(owner, project) or project
        report = gethourslist(owner, project)
        ievent.reply('%s: ' % desc, report, dot=True)

cmnds.add('pt-report', handle_report, 'USER')
examples.add('pt-report', 'report hours of work on a project', \
'1) pt-report 2) pt-report gc')
tests.add('pt-report gozerbot coden')

def handle_addshare(bot, ievent):
    """ share a project with another user """
    username = users.getname(ievent.userhost)
    try:
        project, otheruser = ievent.rest.split(' ', 1)
    except ValueError:
        ievent.missing('<projectname> <user>')
        return
    project = project.strip().lower()
    if hascontrib(username, project):
        ievent.reply('only project owner can share a project')
        return
    if not hasproject(username, project):
        ievent.reply('no project %s' % project)
        return
    if not users.exist(otheruser):
        ievent.reply('unknown user %s' % otheruser)
        return
    if hasproject(otheruser, project) or \
    hascontrib(otheruser, project):
        ievent.reply('%s already has a project named %s' \
        % (otheruser, project))
        return
    addshare(username, project, otheruser)
    desc = getdesc(username, project) or project
    ievent.reply('sharing %s with %s' % (desc, otheruser))

cmnds.add('pt-share', handle_addshare, 'USER')
examples.add('pt-share', 'share a project with another user', \
'pt-share myproject knorrie')
tests.add('pt-share gozerbot coden dunker')

def handle_sharelist(bot, ievent):
    """ get a list of users we share a project with """
    username = users.getname(ievent.userhost)
    if not ievent.rest:
        ievent.missing('<projectname>')
        return
    project = ievent.rest.strip().lower()
    if hasproject(username, project):
        l = getsharelist(username, project)
        desc = getdesc(username, project) or project
	if not l:
	    ievent.reply('not sharing %s' % desc)
	else:
            ievent.reply('sharing %s with: ' % desc, l)
    elif hascontrib(username, project):
        owner = getowner(username, project)
        desc = getdesc(owner, project) or project
        l = getsharelist(owner, project)
        ievent.reply('owner is %s, sharing with: '\
        % owner, l)
    else:
        ievent.reply('no project %s' % project)

cmnds.add('pt-sharelist', handle_sharelist, 'USER')
examples.add('pt-sharelist', 'get a list of users we share a project \
with', 'pt-sharelist myproject')
tests.add('pt-sharelist gozerbot coden')

def handle_delshare(bot, ievent):
    """ stop sharing a project with another user """
    username = users.getname(ievent.userhost)
    try:
        project, otheruser = ievent.rest.split(' ', 1)
    except ValueError:
        ievent.missing('<projectname> <user>')
        return
    project = project.strip().lower()
    if not hasproject(username, project):
        ievent.reply('no project %s' % project)
        return
    if not hascontrib(otheruser, project) or \
    getowner(otheruser, project) != username:
        ievent.reply('project not shared with %s' % otheruser)
        return
    delshare(username, project, otheruser)
    desc = getdesc(username, project) or project
    ievent.reply('stopped sharing %s with %s' % (desc, otheruser))

cmnds.add('pt-delshare', handle_delshare, 'USER')
examples.add('pt-delshare', 'stop sharing a project with another \
user', 'pt-delshare myproject somebody')
tests.add('pt-delshare gozerbot coden mekker')