Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

selfstats improving

  • Loading branch information...
commit 2d1d39bff12f3616449464658eba8d9b5c5505cc 1 parent 113bada
David Fendrich authored
Showing with 187 additions and 54 deletions.
  1. +7 −28 activity_store.py
  2. +49 −4 models.py
  3. +3 −2 selfspy.py
  4. +128 −20 selfstats.py
View
35 activity_store.py
@@ -1,5 +1,3 @@
-import zlib
-import json
import time
from datetime import datetime
NOW = datetime.now
@@ -15,19 +13,12 @@
#Mouse buttons: left button: 1, middle: 2, right: 3, scroll up: 4, down:5
-def pad(s, padnum):
- ls = len(s)
- if ls % padnum == 0:
- return s
- return s + '\0' * (padnum - (ls % padnum))
-
class ActivityStore:
def __init__(self, db_name, encrypter=None):
self.session_maker = models.initialize(db_name)
self.session = None
- if encrypter:
- self.encrypter = encrypter
+ models.ENCRYPTER = encrypter
self.nrmoves = 0
self.latestx = 0
@@ -36,6 +27,7 @@ def __init__(self, db_name, encrypter=None):
self.specials_in_row = 0
self.curtext = u""
+ self.keys = []
self.timings = []
self.last_key_time = time.time()
@@ -59,16 +51,6 @@ def close(self):
self.sniffer.cancel()
self.store_keys()
- def maybe_encrypt(self, s):
- if self.encrypter:
- s = pad(s, 8)
- s = self.encrypter.encrypt(s)
- return s
-
- def timings_to_str(self):
- z = zlib.compress(json.dumps(self.timings))
- return self.maybe_encrypt(z)
-
def store_window(self):
cur_window = self.session.query(Window).filter_by(title=self.cur_name.decode('latin1'), process_id=self.cur_process_id).scalar()
if cur_window is None:
@@ -89,7 +71,7 @@ def maybe_end_specials(self):
def store_click(self, button, press):
if press:
print 'mouse', button, self.nrmoves
- self.session.add(Click(button, press, self.latestx, self.latesty, self.nrmoves, self.cur_win_id, self.cur_geo_id))
+ self.session.add(Click(button, press, self.latestx, self.latesty, self.nrmoves, self.cur_process_id, self.cur_win_id, self.cur_geo_id))
self.session.commit()
self.nrmoves = 0
@@ -97,17 +79,13 @@ def store_keys(self):
if self.timings:
self.maybe_end_specials()
- enc_timings = self.timings_to_str()
- enc_curtext = self.maybe_encrypt(self.curtext.encode('utf8'))
-
- self.session.add(Keys(enc_curtext, enc_timings, self.started, self.cur_win_id, self.cur_geo_id))
+ self.session.add(Keys(self.curtext.encode('utf8'), self.keys, self.timings, self.started, self.cur_process_id, self.cur_win_id, self.cur_geo_id))
self.session.commit()
- print 'keys', len(self.timings), len(cPickle.dumps(self.timings, 2)), len(enc_timings)
-
self.started = NOW()
self.curtext = u""
self.timings = []
+ self.keys = []
self.last_key_time = time.time()
def get_cur_window(self):
@@ -200,7 +178,8 @@ def got_key(self, keycode, state, s, press):
self.specials_in_row += 1
self.lastspecial = s
if self.specials_in_row < 2:
- self.timings.append((s, now - self.last_key_time))
+ self.keys.append(s)
+ self.timings.append(now - self.last_key_time)
self.last_key_time = now
def got_mouse_click(self, button, press):
View
53 models.py
@@ -1,3 +1,6 @@
+import zlib
+import json
+
import datetime
from sqlalchemy.ext.declarative import declarative_base, declared_attr
@@ -9,6 +12,7 @@ def initialize(fname):
Base.metadata.create_all(engine)
return sessionmaker(bind=engine)
+ENCRYPTER = None
Base = declarative_base()
@@ -69,45 +73,86 @@ class Click(SpookMixin, Base):
y = Column(Integer, nullable=False)
nrmoves = Column(Integer, nullable=False)
+ process_id = Column(Integer, ForeignKey('process.id'), nullable=False, index=True)
+ process = relationship("Process", backref=backref('clicks'))
+
window_id = Column(Integer, ForeignKey('window.id'), nullable=False)
window = relationship("Window", backref=backref('clicks'))
geometry_id = Column(Integer, ForeignKey('geometry.id'), nullable=False)
geometry = relationship("Geometry", backref=backref('clicks'))
- def __init__(self, button, press, x, y, nrmoves, window_id, geometry_id):
+ def __init__(self, button, press, x, y, nrmoves, process_id, window_id, geometry_id):
self.button = button
self.press = press
self.x = x
self.y = y
self.nrmoves = nrmoves
+ self.process_id = process_id
self.window_id = window_id
self.geometry_id = geometry_id
def __repr__(self):
return "<Click (%d, %d), (%d, %d, %d)>" % (self.xpos, self.ypos, self.button, self.press, self.nrmoves)
+def pad(s, padnum):
+ ls = len(s)
+ if ls % padnum == 0:
+ return s
+ return s + '\0' * (padnum - (ls % padnum))
+
+def maybe_encrypt(s):
+ if ENCRYPTER:
+ s = pad(s, 8)
+ s = ENCRYPTER.encrypt(s)
+ return s
+
+def maybe_decrypt(s):
+ if ENCRYPTER:
+ s = ENCRYPTER.decrypt(s)
+ return s
+
class Keys(SpookMixin, Base):
text = Column(Binary, nullable=False)
started = Column(DateTime, nullable=False)
+ process_id = Column(Integer, ForeignKey('process.id'), nullable=False, index=True)
+ process = relationship("Process", backref=backref('keys'))
+
window_id = Column(Integer, ForeignKey('window.id'), nullable=False)
window = relationship("Window", backref=backref('keys'))
geometry_id = Column(Integer, ForeignKey('geometry.id'), nullable=False)
geometry = relationship("Geometry", backref=backref('keys'))
+ nrkeys = Column(Integer, index=True)
+
+ keys = Column(Binary)
timings = Column(Binary)
- def __init__(self, text, timings, started, window_id, geometry_id):
- self.text = text
- self.timings = timings
+ def __init__(self, text, keys, timings, started, process_id, window_id, geometry_id):
+ ztimings = zlib.compress(json.dumps(timings))
+ zkeys = maybe_encrypt(zlib.compress(json.dumps(timings)))
+ ztext = maybe_encrypt(text)
+
+ self.text = ztext
+ self.keys = zkeys
+ self.nrkeys = len(keys)
+ self.timings = ztimings
self.started = started
+ self.process_id = process_id
self.window_id = window_id
self.geometry_id = geometry_id
+ def decrypt_text(self):
+ return maybe_decrypt(self.text)
+
+ def decrypt_keys(self):
+ keys = maybe_decrypt(self.keys)
+ return json.loads(zlib.decompress(keys))
+
def __repr__(self):
return "<Keys %s>" % self.text
View
5 selfspy.py
@@ -28,16 +28,17 @@
allow not-text argument to avoid storing text at all. This makes the program never ask for passwords
remove guid and uid flags
- periodic emails from selfspy (or perhaps just have note in the README on how this can be accomplished with cron, mail and selfstats?)
-
README
+
+ periodic emails from selfspy (or perhaps just have note in the README on how this can be accomplished with cron, mail and selfstats?)
--
test map switch
general testing
no printing
remove stdout and stderr from DaemonContext
remove cPickle
-
+ requirements file
---Later
code documentation, unittests, pychecker ;)
View
148 selfstats.py
@@ -2,6 +2,8 @@
import os
import sys
+import re
+import datetime
import argparse
import ConfigParser
@@ -13,35 +15,92 @@
from password_dialog import get_password
import check_password
+import models
+
"""
- add database fields to allow more of the summaries without a password
- note in the help which commands require password
+ interpret period
+
+ decompression
- add decryption and unpacking (and packing) to models.py
- add lazy iterator to models.py
+ check_needs
- add filter to models.py
+ get_password
- do_filter
+ print rows
+ print rows + text
+ if is_summary: print len(lines)
+
+ test
--
- check_need_text
- check_is_summary
- get password
+ argument groups
+
+ prettier printing
- print listing
+ mouse stuff
+
+ warn for bad regexp
calc summary
print summary
+
+ test
"""
+
+def make_time_string(dates, clock):
+ now = datetime.datetime.now()
+ now2 = datetime.datetime.now()
+
+ if dates is None: dates = []
+
+ if len(dates) > 3:
+ print 'Max three arguments to date', dates
+ sys.exit(1)
+
+ try:
+ if len(dates) == 3: now.replace(year=dates[0])
+ if len(dates) >= 2: now.replace(month=dates[0])
+ if len(dates) >= 1: now.replace(day=dates[0])
+
+ if len(dates) == 2:
+ if now > now2:
+ now.replace(year=now.year - 1)
+
+ if len(dates) == 1:
+ if now > now2:
+ m = now.month - 1:
+ if m:
+ now.replace(month=m)
+ else:
+ now.replace(year=now.year - 1, month=12)
+ except ValueError:
+ print 'Malformed date', dates
+ sys.exit(1)
+
+ if clock:
+ try:
+ hour, minute = clock.split(':')
+ except ValueError:
+ print 'Malformed clock', clock
+ sys.exit(1)
+
+ if now > now2:
+ now -= datetime.timedelta(days=1)
+
+ return now.strftime('%Y %m %d %H:%M')
+
+
class Selfstats:
- def __init__(self, args):
+ def __init__(self, db_name, args):
self.args = args
+ self.session_maker = models.initialize(db_name)
- self.check_need_text()
- if self.need_text:
+ self.check_needs()
+ if self.need_text or self.need_keys:
pass #get password
- self.do_filter()
+
+ self.filter_keys()
+ self.filter_mouse()
if self.check_is_summary():
self.calc_summary()
@@ -49,8 +108,57 @@ def __init__(self, args):
else:
self.show_rows()
- def do_filter(self):
- pass
+ def maybe_reg_filter(self, q, name, names, table, source_prop, target_prop):
+ if self.args[name] is not None:
+ ids = []
+ try:
+ reg = re.compile(self.args[name])
+ except re.error, e:
+ print 'Error in regular expression', str(e)
+ sys.exit(1)
+
+ for x in session.query(table).all():
+ if reg.match(x[source_prop]):
+ ids += x['id']
+ print len(ids), '%s matched' % names
+ q = q.filter(target_prop.in_(ids))
+ return q
+
+ def filter_keys(self):
+ session = session_maker()
+ Keys = models.Keys
+ props = [Keys.created_at, Keys.started, Keys.nrkeys]
+ if self.need_text: props += Keys.text
+ if self.need_keys: props += Keys.keys
+ if self.need_timings: props += Keys.timings
+
+ q = session.query(*props)
+
+ if self.args['time'] or self.args['clock']:
+ s = make_time_string(self.args['time'], self.args['clock'])
+ q = q.filter(Keys.created_at >= s)
+ elif self.args['id'] is not None:
+ q = q.filter(Keys.id >= self.args['id'])
+
+ if self.args['period'] is not None:
+ q = make_period(q, self.args['period'])
+
+ if self.args['min_keys'] is not None:
+ q = q.filter(Keys.nrkeys >= self.args['min_keys'])
+
+ q = maybe_reg_filter(q, 'process', 'process(es)', models.Process, 'name', Keys.process_id,)
+ q = maybe_reg_filter(q, 'title', 'title(s)', models.Window, 'title', Keys.window_id)
+
+ if args['body']:
+ bodrex = re.compile(args['body'])
+ for x in q.all():
+ body = models.maybe_decrypt(x.text)
+ if bodrex.match(body):
+ yield x
+ else:
+ for x in q.all():
+ yield x
+
def show_rows(self):
#tabulate data
@@ -105,9 +213,9 @@ def parse_config():
parser.add_argument('-T', '--title', type=str, metavar='regexp', help='Only allow entries where the title matches this <regexp>')
parser.add_argument('-P', '--process', type=str, metavar='regexp', help='Only allow entries where the process name matches this <regexp>')
- parser.add_argument('-B', '--body', type=str, metavar='regexp', help='Only allow entries where the body matches this <regexp>')
+ parser.add_argument('-B', '--body', type=str, metavar='regexp', help='Only allow entries where the body matches this <regexp>. Requires password.')
- parser.add_argument('-s', '--showtext', nargs=0, help='Also show the text column. This switch is ignored if at lesat one of the summary options are used.')
+ parser.add_argument('-s', '--showtext', nargs=0, help='Also show the text column. This switch is ignored if at lesat one of the summary options are used. Requires password.')
parser.add_argument('--kcratio', nargs=0, help='Summarize the ratio between keystrokes and clicks (not scroll up or down) in the given period.')
parser.add_argument('--karatio', nargs=0, help='Summarize the ratio between keystrokes and time active in the given period.')
@@ -115,7 +223,7 @@ def parse_config():
parser.add_argument('--keystrokes', nargs=0, help='Summarize number of keystrokes')
parser.add_argument('--clicks', nargs=0, help='Summarize number of mouse button clicks for all buttons.')
- parser.add_argument('--key-freqs', nargs=0, help='Summarize a table of absolute and relative number of keystrokes for each used key during the time period.')
+ parser.add_argument('--key-freqs', nargs=0, help='Summarize a table of absolute and relative number of keystrokes for each used key during the time period. Requires password.')
parser.add_argument('--active', type=int, metavar='seconds', nargs='?', const=ACTIVE_SECONDS, help='Summarize total time spent active during the period. The optional argument gives how many seconds after each mouse click (including scroll up or down) or keystroke that you are considered active. Default is %d' % ACTIVE_SECONDS)
parser.add_argument('--periods', type=int, metavar='seconds', nargs='?', const=ACTIVE_SECONDS, help='List active time periods. Optional argument works same as for --active')
@@ -146,5 +254,5 @@ def parse_config():
print 'Password failed'
sys.exit(1)
-
+ Selfstats(os.path.join(args['data_dir'], DBNAME)
Please sign in to comment.
Something went wrong with that request. Please try again.