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)
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()