def test_when_activity_has_not_finished(self, mock_is_dir_res, mock_open_res) -> None:
        files_provider = FilesProvider(Path('/root_dir/'))
        self.assertEqual(4, mock_is_dir_res.call_count)

        writer = ActivityWriter(files_provider)

        not_finished_activity = Activity('wm_class1',
                                         'window_name1',
                                         datetime(2020, 7, 21, 20, 30, 0),
                                         is_work_time=True)

        handle_new_day_event = Mock()

        writer.event.on(ActivityWriter.NEW_DAY_EVENT, handle_new_day_event)

        with self.assertRaisesRegex(
                ValueError,
                expected_regex='Activity \\[2020-07-21 20:30:00\tNone\tNone\twm_class1\twindow_name1\tTrue\n\\] '
                               'should be finished!'):
            writer.write(not_finished_activity)

        mock_open_res.assert_not_called()

        mock_file = mock_open_res.return_value

        mock_file.write.assert_not_called()
        mock_file.flush.assert_not_called()
        mock_file.close.assert_not_called()
        handle_new_day_event.assert_not_called()
    def test_when_tree_day_activity(self, mock_is_dir_res, mock_open_res) -> None:
        files_provider = FilesProvider(Path('/root_dir/'))
        self.assertEqual(4, mock_is_dir_res.call_count)

        writer = ActivityWriter(files_provider)

        handle_new_day_event = Mock()

        writer.event.on(ActivityWriter.NEW_DAY_EVENT, handle_new_day_event)

        three_days_activity = Activity('wm_class2',
                                       'window_name2',
                                       datetime(2020, 7, 31, 12, 5),
                                       is_work_time=True).set_end_time(datetime(2020, 8, 2, 11, 30))

        writer.write(three_days_activity)

        mock_open_res.assert_has_calls([
            call('/root_dir/dest/2020-07-31_speaking_eye_raw_data.tsv', 'a'),
            call().write('2020-07-31 12:05:00\t2020-07-31 23:59:59.999999\t'
                         '11:54:59.999999\twm_class2\twindow_name2\tTrue\n'),
            call().flush(),
            call().close(),
            call('/root_dir/dest/2020-08-01_speaking_eye_raw_data.tsv', 'a'),
            call().write('2020-08-01 00:00:00\t2020-08-01 23:59:59.999999\t'
                         '23:59:59.999999\twm_class2\twindow_name2\tTrue\n'),
            call().flush(),
            call().close(),
            call('/root_dir/dest/2020-08-02_speaking_eye_raw_data.tsv', 'a'),
            call().write('2020-08-02 00:00:00\t2020-08-02 11:30:00\t'
                         '11:30:00\twm_class2\twindow_name2\tTrue\n'),
            call().flush()
        ])

        self.assertEqual(2, handle_new_day_event.call_count)
    def test_try_write_when_file_is_not_opened(self, mock_is_dir_res, mock_open_res) -> None:
        handle_new_day_event = Mock()

        with self.assertRaisesRegex(
                Exception,
                expected_regex='current_file should be opened!'):
            files_provider = FilesProvider(Path('/root_dir/'))
            self.assertEqual(4, mock_is_dir_res.call_count)

            writer = ActivityWriter(files_provider)

            writer.event.on(ActivityWriter.NEW_DAY_EVENT, handle_new_day_event)
            writer.write(self.activity)

        mock_open_res.assert_called_once_with('/root_dir/dest/2020-07-21_speaking_eye_raw_data.tsv', 'a')
        handle_new_day_event.assert_not_called()
    def test_when_new_day_started(self, mock_is_dir_res, mock_open_res) -> None:
        files_provider = FilesProvider(Path('/root_dir/'))
        self.assertEqual(4, mock_is_dir_res.call_count)

        writer = ActivityWriter(files_provider)

        handle_new_day_event = Mock()

        writer.event.on(ActivityWriter.NEW_DAY_EVENT, handle_new_day_event)

        writer.write(self.activity)

        mock_open_res.assert_called_once_with('/root_dir/dest/2020-07-21_speaking_eye_raw_data.tsv', 'a')

        mock_file = mock_open_res.return_value

        mock_file.write.assert_called_once_with(
            '2020-07-21 20:30:00.000001\t2020-07-21 21:30:00.000002\t1:00:00.000001\twm_class1\twindow_name1\tTrue\n')
        mock_file.flush.assert_called_once()
        mock_file.close.assert_not_called()

        second_activity = Activity('wm_class2',
                                   'window_name2',
                                   datetime(2020, 7, 22, 0, 0, 0, 2),
                                   is_work_time=True).set_end_time(datetime(2020, 7, 22, 0, 30, 0, 8))

        writer.write(second_activity)

        self.assertEqual(4, mock_is_dir_res.call_count)
        handle_new_day_event.assert_called_once()
        mock_file.close.assert_called_once()

        self.assertEqual(call('/root_dir/dest/2020-07-22_speaking_eye_raw_data.tsv', 'a'), mock_open_res.call_args)
        self.assertEqual(2, mock_file.write.call_count)
        self.assertEqual(
            call('2020-07-22 00:00:00.000002\t2020-07-22 00:30:00.000008\t'
                 '0:30:00.000006\twm_class2\twindow_name2\tTrue\n'),
            mock_file.write.call_args)
        self.assertEqual(2, mock_file.flush.call_count)
 def test_when_output_dir_does_not_exist(self) -> None:
     with self.assertRaisesRegex(
             ValueError,
             expected_regex='Path \\[/non_existent_output_dir\\] does not exist or it is not a dir!'):
         files_provider = FilesProvider(Path('/non_existent_output_dir/'))
         ActivityWriter(files_provider)
예제 #6
0
def main():
    src_dir = os.path.dirname(os.path.abspath(__file__))
    config_full_path = os.path.join(src_dir, '../config/config_example.yaml')

    parser = argparse.ArgumentParser(
        description=f'[{APP_ID}] Track & analyze your computer activity')
    parser.add_argument('--log-level',
                        type=str,
                        choices=['debug', 'info', 'warning', 'error'],
                        default='debug',
                        metavar='',
                        help='debug/info/warning/error')
    parser.add_argument('-c',
                        '--config',
                        type=str,
                        default=config_full_path,
                        metavar='',
                        help='config path')
    args = parser.parse_args()

    log_level_map = {
        'debug': logging.DEBUG,
        'info': logging.INFO,
        'warning': logging.WARNING,
        'error': logging.ERROR
    }

    coloredlogs.install(log_level_map[args.log_level])
    logger = logging.getLogger(APP_ID)

    pid_file = f'{tempfile.gettempdir()}/{APP_ID}.pid'
    fp = open(pid_file, 'w')

    try:
        fcntl.lockf(fp, fcntl.LOCK_EX | fcntl.LOCK_NB)
    except IOError:
        app_exit(logger, msg='Another instance is already running!')

    config = None
    error_config_msg = 'Speaking Eye does not work without config. Bye baby!'

    try:
        with open(args.config) as config_file:
            config = yaml.safe_load(config_file)
    except Exception:
        logger.exception(f'Config [{args.config}] is not correct')
        app_exit(logger, error_config_msg)

    if not config:
        logger.error(f'Config [{args.config}] is empty')
        app_exit(logger, error_config_msg)

    application_info_reader = ApplicationInfoReader()
    config_reader = ConfigReader(application_info_reader, config)
    detailed_app_infos = config_reader.try_read_application_info_list(
        ConfigReader.ConfigKey.DETAILED_NODE)

    # NOTE: Implicitly add LockScreen to monitor Break Time activity
    #       to not to add this info at user detailed apps config
    break_time_activity_info = ApplicationInfo(
        SpecialApplicationInfoTitle.BREAK_TIME.value,
        SpecialWmClass.LOCK_SCREEN.value,
        tab_re='',
        is_distracting=False)
    detailed_app_infos.append(break_time_activity_info)

    distracting_app_infos = config_reader.try_read_application_info_list(
        ConfigReader.ConfigKey.DISTRACTING_NODE)
    application_info_matcher = ApplicationInfoMatcher(detailed_app_infos,
                                                      distracting_app_infos)
    activity_reader = ActivityReader(logger, application_info_matcher)

    current_file_dir = Path(os.path.dirname(os.path.abspath(__file__)))
    app_root_dir = current_file_dir / '..'
    files_provider = FilesProvider(app_root_dir)

    dash_server_thread = threading.Thread(target=dash_report_server_main,
                                          kwargs={
                                              'config_reader': config_reader,
                                              'logger': logger,
                                              'activity_reader':
                                              activity_reader,
                                              'files_provider': files_provider,
                                          },
                                          daemon=True)
    dash_server_thread.start()

    app = SpeakingEyeApp(APP_ID, config, config_reader, logger,
                         application_info_matcher, activity_reader,
                         files_provider)
    app.run()
    app.start_main_loop()