def __init__(self): self.dirs = dirs user_config = configuration.Config(self.dirs.config_file) # Apply defaults where no custom values have been set for key, value in default_config.items(): if key not in user_config: user_config[key] = value self.config = user_config self.config.save_state() logging.info("Running in portable mode: %s" % self.dirs.portable) self.month = None self.date = None self.months = {} # The dir name is the title self.title = "" # show instructions at first start self.is_first_start = self.config.read("firstStart") self.config["firstStart"] = 0 logging.info("First Start: %s" % bool(self.is_first_start)) logging.info("RedNotebook version: %s" % info.version) logging.info(filesystem.get_platform_info()) self.actual_date = self.get_start_date() # Let components check if the MainWindow has been created self.frame = None self.frame = MainWindow(self) journal_path = self.get_journal_path() if not self.dirs.is_valid_journal_path(journal_path): logging.error( "Invalid directory: %s. Using default journal." % journal_path ) self.show_message( _("You cannot use this directory for your journal:") + " %s" % journal_path + ". " + _("Opening default journal."), error=True, ) journal_path = self.dirs.default_data_dir self.open_journal(journal_path) self.archiver = backup.Archiver(self) GObject.idle_add(self.archiver.check_last_backup_date) # Check for a new version if self.config.read("checkForNewVersion") == 1: utils.check_new_version(self, info.version, startup=True) # Automatically save the content after a period of time GObject.timeout_add_seconds(600, self.save_to_disk)
def __init__(self): self.dirs = dirs user_config = configuration.Config(self.dirs.config_file) # Apply defaults where no custom values have been set for key, value in default_config.items(): if key not in user_config: user_config[key] = value self.config = user_config self.config.save_state() logging.info('Running in portable mode: %s' % self.dirs.portable) self.month = None self.date = None self.months = {} # The dir name is the title self.title = '' # show instructions at first start self.is_first_start = self.config.read('firstStart') self.config['firstStart'] = 0 logging.info('First Start: %s' % bool(self.is_first_start)) logging.info('RedNotebook version: %s' % info.version) logging.info(filesystem.get_platform_info()) utils.set_environment_variables(self.config) self.actual_date = self.get_start_date() # Let components check if the MainWindow has been created self.frame = None self.frame = MainWindow(self) journal_path = self.get_journal_path() if not self.dirs.is_valid_journal_path(journal_path): logging.error('Invalid directory: %s. Using default journal.' % journal_path) self.show_message(_('You cannot use this directory for your journal:') + ' %s' % journal_path + '. ' + _('Opening default journal.'), error=True) journal_path = self.dirs.default_data_dir self.open_journal(journal_path) self.archiver = backup.Archiver(self) # TODO: Enable backup check. # self.archiver.check_last_backup_date() # Check for a new version if self.config.read('checkForNewVersion') == 1: utils.check_new_version(self, info.version, startup=True) # Automatically save the content after a period of time gobject.timeout_add_seconds(600, self.save_to_disk)
class Journal: def __init__(self): self.dirs = dirs user_config = configuration.Config(self.dirs.config_file) # Apply defaults where no custom values have been set for key, value in default_config.items(): if key not in user_config: user_config[key] = value self.config = user_config self.config.save_state() logging.info('Running in portable mode: %s' % self.dirs.portable) self.month = None self.date = None self.months = {} self.search_index = index.Index() # The dir name is the title self.title = '' # show instructions at first start self.is_first_start = self.config.read('firstStart') self.config['firstStart'] = 0 logging.info('First Start: %s' % bool(self.is_first_start)) logging.info('RedNotebook version: %s' % info.version) logging.info(filesystem.get_platform_info()) self.actual_date = self.get_start_date() # Let components check if the MainWindow has been created self.frame = None self.frame = MainWindow(self) journal_path = self.get_journal_path() if not self.dirs.is_valid_journal_path(journal_path): logging.error('Invalid directory: %s. Using default journal.' % journal_path) self.show_message( _('You cannot use this directory for your journal:') + ' %s' % journal_path + '. ' + _('Opening default journal.'), error=True) journal_path = self.dirs.default_data_dir self.open_journal(journal_path) self.archiver = backup.Archiver(self) GObject.idle_add(self.archiver.check_last_backup_date) # Check for a new version if self.config.read('checkForNewVersion') == 1: utils.check_new_version(self, info.version, startup=True) # Automatically save the content after a period of time GObject.timeout_add_seconds(600, self.save_to_disk) def get_journal_path(self): ''' Retrieve the path from optional args or return standard value if args not present ''' if not args.journal: data_dir = self.config.read('dataDir', self.dirs.data_dir) if not os.path.isabs(data_dir): data_dir = os.path.join(self.dirs.app_dir, data_dir) data_dir = os.path.normpath(data_dir) return data_dir # path_arg can be e.g. data (under .rednotebook), data (elsewhere), # or an absolute path /home/username/myjournal # Try to find the journal under the standard location or at the given # absolute or relative location path_arg = args.journal logging.debug('Trying to find journal "%s"' % path_arg) paths_to_check = [ path_arg, os.path.join(self.dirs.journal_user_dir, path_arg) ] for path in paths_to_check: if os.path.exists(path): if os.path.isdir(path): return path else: logging.warning('To open a journal you must specify a ' 'directory, not a file.') logging.error('The path "%s" is not a valid journal directory. ' 'Execute "rednotebook -h" for instructions' % path_arg) sys.exit(2) def get_start_date(self): ''' Retrieve the date from optional args or otherwise return 'today' ''' if not args.start_date: return datetime.date.today() try: return dates.get_date_from_date_string(args.start_date) except ValueError: logging.error('Invalid date: %s (required format: YYYY-MM-DD).' % args.start_date) sys.exit(2) def exit(self): self.frame.add_values_to_config() # Make it possible to stop the program from exiting # e.g. if the journal could not be saved self.is_allowed_to_exit = True self.save_to_disk(exit_imminent=True) if self.is_allowed_to_exit: logging.info('Goodbye!') # Informs the logging system to perform an orderly shutdown by # flushing and closing all handlers. logging.shutdown() Gtk.main_quit() def convert(self, text, target, headers=None, options=None): options = options or {} options['font'] = self.config.read('previewFont') return markup.convert(text, target, self.dirs.data_dir, headers=headers, options=options) def save_to_disk(self, exit_imminent=False, changing_journal=False, saveas=False): self.save_old_day() try: filesystem.make_directory(self.dirs.data_dir) except (OSError, IOError) as err: logging.error('Creating journal directory failed: {}'.format(err)) self.frame.show_save_error_dialog(exit_imminent) return True try: something_saved = storage.save_months_to_disk( self.months, self.dirs.data_dir, exit_imminent, saveas) except (IOError, OSError) as err: logging.error('Saving month files failed: {}'.format(err)) self.frame.show_save_error_dialog(exit_imminent) something_saved = None if something_saved: self.show_message(_('The content has been saved to %s') % self.dirs.data_dir, error=False) logging.info('The content has been saved to %r' % self.dirs.data_dir) elif something_saved is None: # Don't display this as an error, because we already show a dialog. self.show_message(_('The journal could not be saved'), error=False) else: self.show_message(_('Nothing to save'), error=False) self.config.save_to_disk() if not (exit_imminent or changing_journal) and something_saved: # Update cloud self.frame.cloud.update(force_update=True) # tell gobject to keep saving the content in regular intervals return True def open_journal(self, data_dir): if not os.path.exists(data_dir): logging.warning( 'The dir %s does not exist. Select a different dir.' % data_dir) return if self.months: self.save_to_disk(changing_journal=True) logging.info('Opening journal at %r' % data_dir) self.dirs.data_dir = data_dir self.month = None self.months.clear() self.frame.search_box.clear() self.search_index.clear() self.months = storage.load_all_months_from_disk(data_dir) # Nothing to save before first day change self.load_day(self.actual_date) if self.is_first_start and not os.listdir(data_dir) and not self.days: self.add_instruction_content() # We can't use self.days here since it uses self.save_old_day. for month in self.months.values(): for day in month.days.values(): self.search_index.add(day.date, day.get_indexed_words()) self.stats = Statistics(self) self.frame.cloud.update(force_update=True) self.frame.categories_tree_view.categories = self.categories # Add auto-completion for tag search self.frame.search_box.set_entries( ['#%s' % data.escape_tag(tag) for tag in self.categories]) self.title = filesystem.get_journal_title(data_dir) # Set frame title self.set_frame_title() # Save the folder for next start if not self.dirs.portable: self.config['dataDir'] = data_dir else: rel_data_dir = filesystem.get_relative_path( self.dirs.app_dir, data_dir) self.config['dataDir'] = rel_data_dir def set_frame_title(self): parts = ['RedNotebook'] if self.title != 'data': parts.append(self.title) parts.append(dates.format_date('%x', self.date)) self.frame.main_frame.set_title(' - '.join(parts)) def get_month(self, date): ''' Returns the corresponding month if it has previously been visited, otherwise a new month is created and returned ''' year_and_month = dates.get_year_and_month_from_date(date) # Selected month has not been loaded or created yet if year_and_month not in self.months: self.months[year_and_month] = Month(date.year, date.month) return self.months[year_and_month] def get_day(self, date): return self.get_month(date).get_day(date.day) def save_old_day(self): '''Order is important''' self.search_index.remove(self.day.date, self.day.get_indexed_words()) old_content = self.day.content new_content = self.frame.categories_tree_view.get_day_content() new_content['text'] = self.frame.get_day_text() self.day.content = new_content self.search_index.add(self.day.date, self.day.get_indexed_words()) content_changed = (old_content != new_content) if content_changed: self.month.edited = True self.frame.calendar.set_day_edited(self.date.day, not self.day.empty) def load_day(self, new_date): old_date = self.date self.date = new_date if not dates.same_month(new_date, old_date) or self.month is None: self.month = self.get_month(self.date) self.frame.set_date(self.month, self.date, self.day) self.set_frame_title() def merge_days(self, days): ''' Method used by importers ''' self.save_old_day() for new_day in days: date = new_day.date month = self.get_month(date) old_day = month.get_day(date.day) old_day.merge(new_day) month.edited = True @property def day(self): return self.month.get_day(self.date.day) def change_date(self, new_date): if new_date < datetime.date(1900, 1, 1): self.show_message('Only dates after 1900 are supported.', title='Too Early', error=True) return if new_date == self.date: return self.save_old_day() self.load_day(new_date) def go_to_next_day(self): next_date = self.date + dates.one_day following_edited_days = self.get_days_in_date_range( start_date=next_date) if following_edited_days: next_date = following_edited_days[0].date self.change_date(next_date) def go_to_prev_day(self): prev_date = self.date - dates.one_day previous_edited_days = self.get_days_in_date_range(end_date=prev_date) if previous_edited_days: prev_date = previous_edited_days[-1].date self.change_date(prev_date) def show_message(self, msg, title=None, error=False): if error and not title: title = _('Error') if error: msg_type = Gtk.MessageType.ERROR log_level = logging.ERROR else: msg_type = Gtk.MessageType.INFO log_level = logging.INFO self.frame.show_message(title, msg, msg_type) logging.log(log_level, '%s. %s' % (title, msg) if title else msg) @property def categories(self): return list( sorted(set( itertools.chain.from_iterable(day.categories for day in self.days)), key=locale.strxfrm)) def get_entries(self, category): entries = set() for day in self.days: entries |= set(day.get_entries(category)) return sorted(entries) def search(self, text, tags): results = [] # TODO: Allow using index with a configuration option? use_index = False if use_index: words = data.get_indexed_words(text) words.extend('#{}'.format(tag) for tag in tags) if not words: return [] dates = self.search_index.find(words[0]) for word in words[1:]: dates &= self.search_index.find(word) for date in sorted(dates, reverse=True): for word in words: results.append(self.get_day(date).search(word, tags)) else: days = self.get_days_with_tags(tags) for day in reversed(days): results.append(day.search(text, tags)) return results def get_days_with_tags(self, tags): if not tags: return self.days days = [] for day in self.days: day_tags = set(data.escape_tag(tag) for tag in day.categories) if all(tag in day_tags for tag in tags): days.append(day) return days def get_word_count_dict(self): """ Return a dictionary mapping the words to their number of appearance. """ word_dict = defaultdict(int) for day in self.days: words = day.get_words() for word in words: word_dict[word.lower()] += 1 return word_dict @property def days(self): ''' Returns all edited days ordered by their date ''' # The day being edited counts too if self.frame: self.save_old_day() days = [] for month in self.months.values(): # Filter out days without content. days_in_month = [ day for day in month.days.values() if not day.empty ] days.extend(days_in_month) # Sort days days = sorted(days, key=lambda day: day.date) return days def get_days_in_date_range(self, start_date=None, end_date=None): if not start_date: start_date = datetime.date.min if not end_date: end_date = datetime.date.max start_date, end_date = sorted([start_date, end_date]) assert start_date <= end_date days_in_date_range = [] for day in self.days: if day.date < start_date: continue elif start_date <= day.date <= end_date: days_in_date_range.append(day) elif day.date > end_date: break return days_in_date_range def add_instruction_content(self): self.change_date(datetime.date.today()) current_date = self.date logging.info('Adding example content on %s' % current_date) for example_day in info.example_content: self.day.content = example_day self.frame.set_date(self.month, self.date, self.day) self.search_index.add(self.day.date, self.day.get_indexed_words()) self.go_to_next_day() self.change_date(current_date)
class Journal: def __init__(self): self.dirs = dirs user_config = configuration.Config(self.dirs.config_file) # Apply defaults where no custom values have been set for key, value in default_config.items(): if key not in user_config: user_config[key] = value self.config = user_config self.config.save_state() logging.info('Running in portable mode: %s' % self.dirs.portable) self.month = None self.date = None self.months = {} # The dir name is the title self.title = '' # show instructions at first start self.is_first_start = self.config.read('firstStart', 1) self.config['firstStart'] = 0 logging.info('First Start: %s' % bool(self.is_first_start)) logging.info('RedNotebook version: %s' % info.version) logging.info(filesystem.get_platform_info()) utils.set_environment_variables(self.config) self.actual_date = self.get_start_date() # Let components check if the MainWindow has been created self.frame = None self.frame = MainWindow(self) journal_path = self.get_journal_path() if not self.dirs.is_valid_journal_path(journal_path): logging.error('Invalid directory: %s. Using default journal.' % journal_path) self.show_message(_('You cannot use this directory for your journal:') + ' %s' % journal_path + '. ' + _('Opening default journal.'), error=True) journal_path = self.dirs.default_data_dir self.open_journal(journal_path) self.archiver = backup.Archiver(self) #self.archiver.check_last_backup_date() # Check for a new version if self.config.read('checkForNewVersion', 0) == 1: utils.check_new_version(self, info.version, startup=True) # Automatically save the content after a period of time gobject.timeout_add_seconds(600, self.save_to_disk) def get_journal_path(self): ''' Retrieve the path from optional args or return standard value if args not present ''' if not args.journal: data_dir = self.config.read('dataDir', self.dirs.data_dir) if not os.path.isabs(data_dir): data_dir = os.path.join(self.dirs.app_dir, data_dir) data_dir = os.path.normpath(data_dir) return data_dir # path_arg can be e.g. data (under .rednotebook), data (elsewhere), # or an absolute path /home/username/myjournal # Try to find the journal under the standard location or at the given # absolute or relative location path_arg = args.journal logging.debug('Trying to find journal "%s"' % path_arg) paths_to_check = [path_arg, os.path.join(self.dirs.journal_user_dir, path_arg)] for path in paths_to_check: if os.path.exists(path): if os.path.isdir(path): return path else: logging.warning('To open a journal you must specify a ' 'directory, not a file.') logging.error('The path "%s" is not a valid journal directory. ' 'Execute "rednotebook -h" for instructions' % path_arg) sys.exit(1) def get_start_date(self): ''' Retrieve the date from optional args or otherwise return 'today' ''' if not args.start_date: return datetime.date.today() try: return dates.get_date_from_date_string(args.start_date) except ValueError: logging.error('Invalid date: %s (required format: YYYY-MM-DD).' % args.start_date) sys.exit(2) def exit(self): self.frame.add_values_to_config() # Make it possible to stop the program from exiting # e.g. if the journal could not be saved self.is_allowed_to_exit = True self.save_to_disk(exit_imminent=True) if self.is_allowed_to_exit: logging.info('Goodbye!') # Informs the logging system to perform an orderly shutdown by # flushing and closing all handlers. logging.shutdown() gtk.main_quit() def convert(self, text, target, headers=None, options=None): options = options or {} options['font'] = self.config.read('previewFont', 'Ubuntu, sans-serif') return markup.convert(text, target, self.dirs.data_dir, headers=headers, options=options) def save_to_disk(self, exit_imminent=False, changing_journal=False, saveas=False): self.save_old_day() try: filesystem.make_directory(self.dirs.data_dir) except (OSError, IOError): self.frame.show_save_error_dialog(exit_imminent) return True try: something_saved = storage.save_months_to_disk( self.months, self.dirs.data_dir, exit_imminent, saveas) except (IOError, OSError): self.frame.show_save_error_dialog(exit_imminent) something_saved = None if something_saved: self.show_message(_('The content has been saved to %s') % self.dirs.data_dir, error=False) logging.info('The content has been saved to %r' % self.dirs.data_dir) elif something_saved is None: # Don't display this as an error, because we already show a dialog. self.show_message(_('The journal could not be saved'), error=False) else: self.show_message(_('Nothing to save'), error=False) self.config.save_to_disk() if not (exit_imminent or changing_journal) and something_saved: # Update cloud self.frame.cloud.update(force_update=True) # tell gobject to keep saving the content in regular intervals return True def open_journal(self, data_dir): if not os.path.exists(data_dir): logging.warning('The dir %s does not exist. Select a different dir.' % data_dir) return if self.months: self.save_to_disk(changing_journal=True) logging.info('Opening journal at %r' % data_dir) self.dirs.data_dir = data_dir self.month = None self.months.clear() self.months = storage.load_all_months_from_disk(data_dir) # Nothing to save before first day change self.load_day(self.actual_date) self.stats = Statistics(self) if self.is_first_start and not os.listdir(data_dir) and len(self.days) == 0: self.add_instruction_content() self.frame.cloud.update(force_update=True) # Reset Search self.frame.search_box.clear() self.frame.categories_tree_view.categories = self.categories # Add auto-completion for tag search self.frame.search_box.set_entries([u'#%s' % self.normalize_tag(tag) for tag in self.categories]) self.title = filesystem.get_journal_title(data_dir) # Set frame title self.set_frame_title() # Save the folder for next start if not self.dirs.portable: self.config['dataDir'] = data_dir else: rel_data_dir = filesystem.get_relative_path(self.dirs.app_dir, data_dir) self.config['dataDir'] = rel_data_dir def set_frame_title(self): parts = ['RedNotebook'] if self.title != 'data': parts.append(self.title) parts.append(dates.format_date('%x', self.date)) self.frame.main_frame.set_title(' - '.join(parts)) def get_month(self, date): ''' Returns the corresponding month if it has previously been visited, otherwise a new month is created and returned ''' year_and_month = dates.get_year_and_month_from_date(date) # Selected month has not been loaded or created yet if year_and_month not in self.months: self.months[year_and_month] = Month(date.year, date.month) return self.months[year_and_month] def save_old_day(self): '''Order is important''' old_content = self.day.content self.day.content = self.frame.categories_tree_view.get_day_content() self.day.text = self.frame.get_day_text() content_changed = (old_content != self.day.content) if content_changed: self.month.edited = True self.frame.calendar.set_day_edited(self.date.day, not self.day.empty) def load_day(self, new_date): old_date = self.date self.date = new_date if not Month.same_month(new_date, old_date) or self.month is None: self.month = self.get_month(self.date) self.frame.set_date(self.month, self.date, self.day) self.set_frame_title() def merge_days(self, days): ''' Method used by importers ''' self.save_old_day() for new_day in days: date = new_day.date month = self.get_month(date) old_day = month.get_day(date.day) old_day.merge(new_day) month.edited = True @property def day(self): return self.month.get_day(self.date.day) def change_date(self, new_date): if new_date < datetime.date(1900, 1, 1): self.show_message('Only dates after 1900 are supported.', title='Too Early', error=True) return if new_date == self.date: return self.save_old_day() self.load_day(new_date) def go_to_next_day(self): next_date = self.date + dates.one_day following_edited_days = self.get_days_in_date_range(start_date=next_date) if following_edited_days: next_date = following_edited_days[0].date self.change_date(next_date) def go_to_prev_day(self): prev_date = self.date - dates.one_day previous_edited_days = self.get_days_in_date_range(end_date=prev_date) if previous_edited_days: prev_date = previous_edited_days[-1].date self.change_date(prev_date) def show_message(self, msg, title=None, error=False): if error and not title: title = _('Error') if error: msg_type = gtk.MESSAGE_ERROR log_level = logging.ERROR else: msg_type = gtk.MESSAGE_INFO log_level = logging.INFO self.frame.show_message(title, msg, msg_type) logging.log(log_level, '%s. %s' % (title, msg) if title else msg) @property def categories(self): return list(sorted(set(itertools.chain.from_iterable( day.categories for day in self.days)), cmp=locale.strcoll)) def normalize_tag(self, tag): return tag.replace(' ', '_').lower() def get_entries(self, category): entries = set() for day in self.days: entries |= set(day.get_entries(category)) return sorted(entries) def search(self, text, tags): days = self.get_days_with_tags(tags) results = [] for day in reversed(days): results.append(day.search(text, tags)) return results def get_days_with_tags(self, tags): if not tags: return self.days days = [] for day in self.days: day_tags = set(data.escape_tag(tag) for tag in day.categories) if all(tag in day_tags for tag in tags): days.append(day) return days def get_word_count_dict(self): ''' Returns a dictionary mapping the words to their number of appearance ''' # TODO: Use collections.Counter in Python2.7 # TODO: Check if concatenating all text and using a regex is faster. word_dict = defaultdict(int) for day in self.days: words = day.get_words() for word in words: word_dict[word.lower()] += 1 return word_dict @property def days(self): ''' Returns all edited days ordered by their date ''' # The day being edited counts too if self.frame: self.save_old_day() days = [] for month in self.months.values(): days_in_month = month.days.values() # Filter out days without content days_in_month = [day for day in days_in_month if not day.empty] days.extend(days_in_month) # Sort days days = sorted(days, key=lambda day: day.date) return days def get_days_in_date_range(self, start_date=None, end_date=None): if not start_date: start_date = datetime.date.min if not end_date: end_date = datetime.date.max start_date, end_date = sorted([start_date, end_date]) assert start_date <= end_date days_in_date_range = [] for day in self.days: if day.date < start_date: continue elif start_date <= day.date <= end_date: days_in_date_range.append(day) elif day.date > end_date: break return days_in_date_range def add_instruction_content(self): self.change_date(datetime.date.today()) current_date = self.date logging.info('Adding example content on %s' % current_date) for example_day in info.example_content: self.day.content = example_day self.frame.set_date(self.month, self.date, self.day) self.go_to_next_day() self.change_date(current_date)
def __init__(self): self.dirs = dirs user_config = configuration.Config(self.dirs.config_file) # Apply defaults where no custom values have been set for key, value in default_config.items(): if key not in user_config: user_config[key] = value self.config = user_config self.config.save_state() self.warn_if_second_instance() logging.info('Running in portable mode: %s' % self.dirs.portable) self.testing = False if options.debug: self.testing = True logging.debug('Debug Mode is on') # Allow starting minimized to tray # When we start minimized we have to set the tray icon visible self.start_minimized = options.minimized if self.start_minimized: self.config['closeToTray'] = 1 self.month = None self.date = None self.months = {} # The dir name is the title self.title = '' # show instructions at first start self.is_first_start = self.config.read('firstStart', 1) self.config['firstStart'] = 0 logging.info('First Start: %s' % bool(self.is_first_start)) logging.info('RedNotebook version: %s' % info.version) logging.info(filesystem.get_platform_info()) utils.set_environment_variables(self.config) self.actual_date = datetime.date.today() # Let components check if the MainWindow has been created self.frame = None self.frame = MainWindow(self) self.open_journal(self.get_journal_path()) self.archiver = backup.Archiver(self) #self.archiver.check_last_backup_date() # Check for a new version if self.config.read('checkForNewVersion', 0) == 1: utils.check_new_version(self, info.version, startup=True) # Automatically save the content after a period of time if not self.testing: gobject.timeout_add_seconds(600, self.save_to_disk)
class Journal: def __init__(self): self.dirs = dirs user_config = configuration.Config(self.dirs.config_file) # Apply defaults where no custom values have been set for key, value in default_config.items(): if key not in user_config: user_config[key] = value self.config = user_config self.config.save_state() self.warn_if_second_instance() logging.info('Running in portable mode: %s' % self.dirs.portable) self.testing = False if options.debug: self.testing = True logging.debug('Debug Mode is on') # Allow starting minimized to tray # When we start minimized we have to set the tray icon visible self.start_minimized = options.minimized if self.start_minimized: self.config['closeToTray'] = 1 self.month = None self.date = None self.months = {} # The dir name is the title self.title = '' # show instructions at first start self.is_first_start = self.config.read('firstStart', 1) self.config['firstStart'] = 0 logging.info('First Start: %s' % bool(self.is_first_start)) logging.info('RedNotebook version: %s' % info.version) logging.info(filesystem.get_platform_info()) utils.set_environment_variables(self.config) self.actual_date = datetime.date.today() # Let components check if the MainWindow has been created self.frame = None self.frame = MainWindow(self) self.open_journal(self.get_journal_path()) self.archiver = backup.Archiver(self) #self.archiver.check_last_backup_date() # Check for a new version if self.config.read('checkForNewVersion', 0) == 1: utils.check_new_version(self, info.version, startup=True) # Automatically save the content after a period of time if not self.testing: gobject.timeout_add_seconds(600, self.save_to_disk) def warn_if_second_instance(self): '''Show warning when a second instance is started''' running = self.config.read('running', 0) if running: logging.warning('RedNotebook instance already running') text1 = _('RedNotebook appears to be already running.') text2 = _('This can happen if the application is hidden in the ' 'system tray or was not shut down correctly.') text3 = _('Starting a second RedNotebook instance can lead to data ' 'loss and it might be best to see if there is another ' 'instance first.') text4 = _('Do you want to start a new RedNotebook instance nevertheless?') dialog = gtk.MessageDialog(type=gtk.MESSAGE_WARNING, buttons=gtk.BUTTONS_YES_NO, message_format=text1) dialog.format_secondary_text('%s %s\n\n%s' % (text2, text3, text4)) answer = dialog.run() dialog.hide() if not answer == gtk.RESPONSE_YES: sys.exit() self.config['running'] = 1 self.config.save_to_disk() def get_journal_path(self): ''' Retrieve the path from optional args or return standard value if args not present ''' if not args: data_dir = self.config.read('dataDir', self.dirs.data_dir) if not os.path.isabs(data_dir): data_dir = os.path.join(self.dirs.app_dir, data_dir) data_dir = os.path.normpath(data_dir) return data_dir # path_arg can be e.g. data (under .rednotebook), data (elsewhere), # or an absolute path /home/username/myjournal # Try to find the journal under the standard location or at the given # absolute or relative location path_arg = args[0] logging.debug('Trying to find journal "%s"' % path_arg) paths_to_check = [path_arg, os.path.join(self.dirs.journal_user_dir, path_arg)] for path in paths_to_check: if os.path.exists(path): if os.path.isdir(path): return path else: logging.warning('To open a journal you must specify a ' 'directory, not a file.') logging.error('The path "%s" is no valid journal directory. ' 'Execute "rednotebook -h" for instructions' % path_arg) sys.exit(1) def exit(self): self.frame.add_values_to_config() self.config['running'] = 0 # Make it possible to stop the program from exiting # e.g. if the journal could not be saved self.is_allowed_to_exit = True self.save_to_disk(exit_imminent=True) if self.is_allowed_to_exit: logging.info('Goodbye!') gtk.main_quit() def save_to_disk(self, exit_imminent=False, changing_journal=False, saveas=False): #logging.info('Trying to save the journal') self.save_old_day() try: filesystem.make_directory(self.dirs.data_dir) except OSError: self.frame.show_save_error_dialog(exit_imminent) return True if not os.path.exists(self.dirs.data_dir): logging.error('Save path does not exist') self.frame.show_save_error_dialog(exit_imminent) return True something_saved = storage.save_months_to_disk(self.months, self.dirs.data_dir, self.frame, exit_imminent, changing_journal, saveas) if something_saved: self.show_message(_('The content has been saved to %s') % self.dirs.data_dir, error=False) logging.info('The content has been saved to %s' % self.dirs.data_dir) else: self.show_message(_('Nothing to save'), error=False) self.config.save_to_disk() if not (exit_imminent or changing_journal) and something_saved: # Update cloud self.frame.cloud.update(force_update=True) # tell gobject to keep saving the content in regular intervals return True def open_journal(self, data_dir, load_files=True): if self.months: self.save_to_disk(changing_journal=True) # Password Protection #password = self.config.read('password', '') logging.info('Opening journal at %s' % data_dir) if not os.path.exists(data_dir): logging.warning('The data dir %s does not exist. Select a different dir.' % data_dir) self.frame.show_dir_chooser('open', dir_not_found=True) return data_dir_empty = not os.listdir(data_dir) if not load_files and not data_dir_empty: msg_part1 = _('The selected folder is not empty.') msg_part2 = _('To prevent you from overwriting data, the folder content has been imported into the new journal.') self.show_message('%s %s' % (msg_part1, msg_part2), error=False) elif load_files and data_dir_empty: self.show_message(_('The selected folder is empty. A new journal has been created.'), error=False) self.dirs.data_dir = data_dir self.month = None self.months.clear() # We always want to load all files if load_files or True: self.months = storage.load_all_months_from_disk(data_dir) # Nothing to save before first day change self.load_day(self.actual_date) self.stats = Statistics(self) self.frame.categories_tree_view.categories = self.categories if self.is_first_start: self.add_instruction_content() # Notebook is only on page 1 here, if we are opening a journal the second time old_page = self.frame.search_notebook.get_current_page() new_page = self.config.read('cloudTabActive', 0) # 0 -> 0: search is cleared later # 0 -> 1: change to cloud, update automatically # 1 -> 0: change to search # 1 -> 1: update cloud # At tab change, cloud is updated automatically self.frame.search_notebook.set_current_page(new_page) if new_page == old_page: # Without tab change, force update self.frame.cloud.update(force_update=True) # Reset Search self.frame.search_box.clear() self.title = filesystem.get_journal_title(data_dir) # Set frame title if self.title == 'data': frame_title = 'RedNotebook' else: frame_title = 'RedNotebook - ' + self.title self.frame.main_frame.set_title(frame_title) # Save the folder for next start if not self.dirs.portable: self.config['dataDir'] = data_dir else: rel_data_dir = filesystem.get_relative_path(self.dirs.app_dir, data_dir) self.config['dataDir'] = rel_data_dir def get_month(self, date): ''' Returns the corresponding month if it has previously been visited, otherwise a new month is created and returned ''' year_and_month = dates.get_year_and_month_from_date(date) # Selected month has not been loaded or created yet if not year_and_month in self.months: self.months[year_and_month] = Month(date.year, date.month) return self.months[year_and_month] def save_old_day(self): '''Order is important''' old_content = self.day.content self.day.content = self.frame.categories_tree_view.get_day_content() self.day.text = self.frame.get_day_text() content_changed = not (old_content == self.day.content) if content_changed: self.month.edited = True self.frame.calendar.set_day_edited(self.date.day, not self.day.empty) def load_day(self, new_date): old_date = self.date self.date = new_date if not Month.same_month(new_date, old_date) or self.month is None: self.month = self.get_month(self.date) #self.month.visited = True self.frame.set_date(self.month, self.date, self.day) def merge_days(self, days): ''' Method used by importers ''' self.save_old_day() for new_day in days: date = new_day.date month = self.get_month(date) old_day = month.get_day(date.day) old_day.merge(new_day) month.edited = True @property def day(self): return self.month.get_day(self.date.day) def change_date(self, new_date): if new_date == self.date: return self.save_old_day() self.load_day(new_date) def go_to_next_day(self): next_date = self.date + dates.one_day following_edited_days = self.get_days_in_date_range(start_date=next_date) if following_edited_days: next_date = following_edited_days[0].date self.change_date(next_date) def go_to_prev_day(self): prev_date = self.date - dates.one_day previous_edited_days = self.get_days_in_date_range(end_date=prev_date) if previous_edited_days: prev_date = previous_edited_days[-1].date self.change_date(prev_date) def show_message(self, message_text, error=False, countdown=True): self.frame.statusbar.show_text(message_text, error, countdown) @property def categories(self): return list(sorted(set(itertools.chain.from_iterable( day.categories for day in self.days)), key=utils.sort_asc)) @property def tags(self): return self.get_entries('Tags') def get_entries(self, category): entries = set() for day in self.days: entries |= set(day.get_entries(category)) return sorted(entries) def search(self, text=None, category=None, tag=None): results = [] for day in reversed(self.days): result = None if text: result = day.search_text(text) elif category: result = day.search_category(category) elif tag: result = day.search_tag(tag) if result: if category: results.extend(result) else: results.append(result) return results @property def days(self): ''' Returns all edited days ordered by their date ''' # The day being edited counts too if self.frame: self.save_old_day() days = [] for month in self.months.values(): days_in_month = month.days.values() # Filter out days without content days_in_month = [day for day in days_in_month if not day.empty] days.extend(days_in_month) # Sort days days = sorted(days, key=lambda day: day.date) return days def get_word_count_dict(self, type): ''' Returns a dictionary mapping the words to their number of appearance ''' word_dict = collections.defaultdict(int) for day in self.days: if type == 'word': words = day.get_words() if type == 'category': words = day.categories if type == 'tag': words = day.tags for word in words: word_dict[word.lower()] += 1 return word_dict def get_days_in_date_range(self, start_date=None, end_date=None): if not start_date: start_date = datetime.date.min if not end_date: end_date = datetime.date.max start_date, end_date = sorted([start_date, end_date]) assert start_date <= end_date days_in_date_range = [] for day in self.days: if day.date < start_date: continue elif start_date <= day.date <= end_date: days_in_date_range.append(day) elif day.date > end_date: break return days_in_date_range def go_to_first_empty_day(self): if len(self.days) == 0: return datetime.date.today() last_edited_day = self.days[-1] first_empty_date = last_edited_day.date + dates.one_day self.change_date(first_empty_date) def add_instruction_content(self): self.go_to_first_empty_day() current_date = self.date logging.info('Adding example content on %s' % current_date) for example_day in info.example_content: self.day.content = example_day self.frame.set_date(self.month, self.date, self.day) self.go_to_next_day() self.change_date(current_date)
def __init__(self): self.dirs = dirs user_config = configuration.Config(self.dirs.config_file) # Apply defaults where no custom values have been set for key, value in default_config.items(): if key not in user_config: user_config[key] = value self.config = user_config self.config.save_state() logging.info('Running in portable mode: %s' % self.dirs.portable) # Allow starting minimized to tray # When we start minimized we have to set the tray icon visible self.start_minimized = filesystem.HAS_TRAY and args.minimized if not filesystem.HAS_TRAY: self.config['closeToTray'] = 0 elif self.start_minimized: self.config['closeToTray'] = 1 self.month = None self.date = None self.months = {} # The dir name is the title self.title = '' # show instructions at first start self.is_first_start = self.config.read('firstStart', 1) self.config['firstStart'] = 0 logging.info('First Start: %s' % bool(self.is_first_start)) logging.info('RedNotebook version: %s' % info.version) logging.info(filesystem.get_platform_info()) utils.set_environment_variables(self.config) self.actual_date = self.get_start_date() # Let components check if the MainWindow has been created self.frame = None self.frame = MainWindow(self) journal_path = self.get_journal_path() if not self.dirs.is_valid_journal_path(journal_path): logging.error('Invalid directory: %s. Using default journal.' % journal_path) self.show_message( _('You cannot use this directory for your journal:') + ' %s' % journal_path + '. ' + _('Opening default journal.'), error=True) journal_path = self.dirs.default_data_dir self.open_journal(journal_path) self.archiver = backup.Archiver(self) #self.archiver.check_last_backup_date() # Check for a new version if self.config.read('checkForNewVersion', 0) == 1: utils.check_new_version(self, info.version, startup=True) # Automatically save the content after a period of time gobject.timeout_add_seconds(600, self.save_to_disk)