Об убийстве времени и его чудодейственном воскрешении

Время — это капитал работника умственного труда. (Бальзак)

Преступление

Обвиняемый, прошу встать. Нет, ну что я сразу так бесцеремонно… Привет всем лентяям, лентяйкам и другим труженикам софтверных полей. Я знаю, что вы подобно Алисе не раз думали над тем, как убить время. Священная инквизиция позавидовала бы вашему усердию и хладнокровию в этом преднамеренном убийстве. Да, мы все получаем от этого своего рода удовольствие. Вердикт — виновны.

Наказание

«Завтра дедлайн… Нет шансов успеть… Почему я не сделал этого вчера? Кто-нибудь! Поуправляйте моим временем.» Без паники. Обвиняемый приговаривается к ежедневному использованию инструментов для управления временем. Пожизненно.

Кандалы. Сделай сам.

Сегодня, милейшие мои лентяи и лентяйки, мы с вами сделаем себе кандалы — простенькую программку, которая бы помогла нам следить за нашим временем.

Что мы от неё хотим?

  • Она должна отсчитывать время, потраченное на полезную работу
  • Она должна отсчитывать время, потраченное на отдых
  • Она должна показывать статистику — сколько же я проработал и сколько провалял дурака
  • И самое главное, хочется все-таки видеть, каким делом я больше всего занимался, и поподробнее (не просто работа/отдых, а скажем, я пил чай по времени столько же сколько писал новый модуль для текущего проекта)

Да, я знаю, что таких программ существует масса, но свои кандалы прочнее. Поэтому приступим к увлекательному изготовлению еще одного велосипеда.

Задачка в принципе для новичков. В обеденный перерыв вложиться можно. Итак, запускаем Visual Studio, делаем проект MFC, на главную форму размещаем кнопки.. Что-что? Unix-way? Ах, да. Закрываем Visual Studio.

Нам нужно что-то простое и понятное, и чтобы его интегрировать можно было во что угодно. Хранить статистику по задачам можно в разных местах, но раз уж sqlite придумали, то давайте заставим его выполнять свою непосредственную задачу. Сам же трекер времени будем писать на Unix Shell — потому что это быстро, потому что он есть в любом Unix, потому что большего и не надо пока. Есть, конечно, одно «но» — я очень плохо разбираюсь в SQL и не особо люблю Shell, но тем интереснее будет.

Наш скрипт должен по сути делать следующее:

  1. Открывать базу и искать в ней незавершенную задачу
  2. Устанавливать ей длительность с момента старта до текущего времени
  3. Добавлять новую незавершенную задачу, время начала которой равно текущему.

Проще не придумаешь. Получается, что нам нужны следующие SQL-запросы:

# Создание таблицы - ключ, название, время начала, длительность, флажок работа/отдых
CREATE TABLE tasks(id integer primary key, task text, start long, duration long, busy int)

# Добавить новую задачу с именем $TASK, временем старта $NOW, неизвестной длительностью
INSERT INTO tasks (task, start, duration, busy) VALUES ('$TASK', $NOW, $DURATION_UNKNOWN, $BUSY)

# Обновить длительность текущей задачи (с ID=$CUR_ID)
UPDATE tasks SET duration = $duration WHERE id = $CUR_ID

# Просуммировать время, затраченное на определенную задачу с именем $t
SELECT SUM(duration) FROM tasks WHERE task == '$t' GROUP BY task

# Общее время работы/отдыха
SELECT SUM(duration) FROM tasks WHERE busy == 1
SELECT SUM(duration) FROM tasks WHERE busy == 0

Первый нужен для создания новой таблицы. Второй добавляет новую «неоконченную» задачу, а третий устанавливает задаче время завершения. Таблица содержит 5 колонок — индекс задачи, ее название, время начала (в секундах с начала нашей с вами эпохи), продолжительность в секундах и её вид (работа/отдых).

Sqlite прекрасно интегрируется с шелловскими скриптами — достаточно всего лишь указать файл базы данных и, собственно, запрос, например:

sqlite3 sample.db "SELECT * FROM tasks"

Для получения текущего времени будем использовать конечно же команду "date +%s", которая как раз и возвращает текущее время в секундах.

Ну вот и все почти. Текущее время знаем, как с базой работать — знаем. Приступаем к написанию.

Вот что у меня получилось в итоге:

#!/bin/sh

DURATION_UNKNOWN="-1"             # Shows that the task is not finished yet
NOW=$(date +%s)                   # Current time in seconds

# Get database name
if [ ! -n "$HATTER_DB" ]; then
  HATTER_DB="hatter.db"
fi

# Wrapper for sqlite
function SQL() {
  sqlite3 "$HATTER_DB" "$1"
}

# Create database if not created yet
if [ ! -f "$HATTER_DB" ]; then
  SQL "CREATE TABLE tasks(id integer primary key, task text, \
    start long, duration long, busy int)"
fi

# Get current task information
CUR_ID=$(SQL "SELECT id FROM tasks WHERE duration == $DURATION_UNKNOWN")
if [ -n "$CUR_ID" ]; then
  if [ $(echo $CUR_ID | wc -l) -gt 1 ]; then
    echo "You have inconsistent database!"
    exit 0
  fi
  CUR_TASK_NAME=$(SQL "SELECT task from tasks WHERE id = $CUR_ID")
  CUR_TASK_START=$(SQL "SELECT start FROM tasks WHERE id = $CUR_ID")
  CUR_TASK_WORK=$(SQL "SELECT busy from tasks WHERE id = $CUR_ID")
fi

# ===================================================================

function timefmt() {
  if [ -n "$1" ]; then sec=$1; else sec=0; fi
  min=$(($sec/60))
  hrs=$(($min/60))
  min=$(($min%60))
  sec=$(($sec%60))
  printf "%d:%02d:%02d" $hrs $min $sec
}

function hatter_stats() {
  for t in $(SQL "SELECT DISTINCT task FROM tasks"); do
    sec=$(SQL "SELECT SUM(duration) FROM tasks WHERE task == '$t' \
                        GROUP BY task")
    prefix=" "
    if [ "x$t" = "x$CUR_TASK_NAME" ]; then
      sec=$(($sec + $NOW - $CUR_TASK_START))
      prefix="*"
    fi
    printf "$prefix%-12s: $(timefmt $sec)\n" "$t"
  done
}

function hatter_short_stats() {
  work_sec=$(SQL "SELECT SUM(duration) FROM tasks WHERE busy == 1")
  relax_sec=$(SQL "SELECT SUM(duration) FROM tasks WHERE busy == 0")

  if [ "x$CUR_TASK_WORK" = "x1" ]; then
    work_sec=$(($work_sec + $NOW - $CUR_TASK_START))
  elif [ "x$CUR_TASK_WORK" = "x0" ]; then
    relax_sec=$(($relax_sec + $NOW - $CUR_TASK_START))
  fi

  echo "Work: $(timefmt $work_sec)"
  echo "Rest: $(timefmt $relax_sec)"
}

function helpmsg() {
  echo "$1 [-w|-r] <task name>  Start new task"
  echo "$1 -s|-S                Show full/brief statistics"
  echo "$1 -p                   Finish current job"
  echo "$1 -h                   Show this help"
  exit 0
}

function stoptask() {
  if [ -n "$CUR_ID" ]; then
    duration=$(($NOW - $CUR_TASK_START))
    SQL "UPDATE tasks SET duration = $duration WHERE id = $CUR_ID"
  fi
}

# ===================================================================

# Parse command line arguments
while true; do
  case $# in 0) break ;; esac
  case $1 in
    -h|--help) helpmsg $0;;
    -w|--word) shift; BUSY="1"; break;;
    -r|--rest) shift; BUSY="0"; break;;
    -s|--stats) hatter_stats; exit 0 ;;
    -S|--short-stats) hatter_short_stats; exit 0 ;;
    -p|--pause) stoptask; exit 0 ;;
    -*) echo "Invalid command argument"; exit 0 ;;
    *) break ;;
  esac
done

if [ ! -n $BUSY ]; then
  echo "You should specify either -b or -r option"
  exit 0
fi

if [ $# -ne 1 ]; then helpmsg; fi

TASK=$1

# Stop existing task
stoptask

# Start new task
SQL "INSERT INTO tasks (task, start, duration, busy) \
          VALUES ('$TASK', $NOW, $DURATION_UNKNOWN, $BUSY)"

Я сохранил этот скрипт в файле с именем hatter, в честь известного всем Шляпника, у которого тоже были нелады со временем. Сделал исполняемым и положил в /usr/local/bin.

Примеряем…

Как же этим пользоваться? Вот вам пример. Начинается новый день. Начинаем новое большое дело:

$ hatter -w Кодописание

Опция -w говорит о том, что мы собираемя работать. Для отдыха используем опцию -r:

$ hatter -r Чаепитие

$ hatter -r Новости

Посмотрим что у нас натикало:

$ hatter -s

Видим, какая задача сколько времени отняла, видим, чем мы сейчас занимаемся. Если же надо видеть только время работы/отдыха, то:

$ hatter -S

Все работает как часы, в прямом и переносном смысле.

Из личного опыта могу посоветовать использовать каждый день новую базу. Можно также использовать псевдонимы для работы/отдыха. Прописать в .bashrc или .zshrc такие строчки:

export HATTER_DB="hatter-$(date +%d%m%y).db"
alias work 'hatter -w'
alias rest 'hatter -r'

Еще одно неудобство в работе — если мы работаем так усердно, что забыли обо всем и выключили компьютер, а завтра посмотрим на вчерашнюю базу — то будем удивлены. Последняя задача осталась неоконченной. и её время работы покажет, что мы работали всю ночь. Избежать такого поведения можно, если перед выключением компьютера останавливать трекер командой:

hatter -p

Эту же команду можно использовать в течение дня, если вы не можете понять — работаете вы или отдыхаете.

Теперь точно все. Можно следить за временем, даже подло шпионить за ним. И все это в 88 строках (SLOC) одного скрипта. Дальше все в ваших руках. Можно показывать время в статусбаре вашего DWM или в conky. Теперь коварное время нас не проведет, но зато мы, я надеюсь, провели время с пользой.

Реклама

Добавить комментарий

Заполните поля или щелкните по значку, чтобы оставить свой комментарий:

Логотип WordPress.com

Для комментария используется ваша учётная запись WordPress.com. Выход / Изменить )

Фотография Twitter

Для комментария используется ваша учётная запись Twitter. Выход / Изменить )

Фотография Facebook

Для комментария используется ваша учётная запись Facebook. Выход / Изменить )

Google+ photo

Для комментария используется ваша учётная запись Google+. Выход / Изменить )

Connecting to %s