Skip to content

Commit 145e59f

Browse files
committed
- difflib merge_strings refactored - if there is an external update, diff calculated only for the active section, other sections are reloaded from the disk
- minor adjustments
1 parent c074f74 commit 145e59f

File tree

10 files changed

+330
-157
lines changed

10 files changed

+330
-157
lines changed

notes_app.svg

Lines changed: 104 additions & 87 deletions
Loading

notes_app/controller/notes_controller.py

Lines changed: 5 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
from notes_app.diff import merge_strings
21
from notes_app.view.notes_view import NotesView
32

43

@@ -56,21 +55,10 @@ def read_file_data(self, file_path=None):
5655
f.close()
5756
return s
5857

59-
def save_file_data(self, data):
58+
def save_file_data(self, data) -> None:
6059
"""
61-
save_file_data saves provided data to the file with location set in model.file_path,
62-
if the file was updated by another instance of the notes app, check if model property external_update
63-
evaluates to True, the code-path goes through difflib merge_string to save the merged string data to the file
60+
save_file_data saves provided data to the file with location set in model.file_path
6461
"""
65-
if self.model.external_update:
66-
f = open(self.model.file_path, "r")
67-
# in case deleted file content fallback to empty string
68-
before = f.read() or ""
69-
f.close()
70-
71-
# TODO try merge strings one section loaded from file at a time
72-
data = merge_strings(before=before, after=data)
73-
7462
f = open(self.model.file_path, "w")
7563
f.write(data)
7664
f.close()
@@ -79,5 +67,7 @@ def save_file_data(self, data):
7967
self.model.dump()
8068

8169
def get_screen(self):
82-
"""The method creates get the view."""
70+
"""
71+
The method creates get the view.
72+
"""
8373
return self.view

notes_app/file.py

Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ def __init__(self, file_path, controller, defaults):
5151
] = self._get_section_identifiers_from_raw_data_content()
5252

5353
self._data_by_sections: Dict[
54-
SectionIdentifier, AnyStr
54+
AnyStr, AnyStr
5555
] = self._transform_raw_data_content_to_data_by_sections()
5656

5757
@staticmethod
@@ -101,7 +101,8 @@ def default_section_identifier(self) -> SectionIdentifier:
101101
return self._section_identifiers[0]
102102

103103
@property
104-
def section_identifiers(self) -> List[SectionIdentifier]:
104+
def section_identifiers_sorted_by_name(self) -> List[SectionIdentifier]:
105+
self._section_identifiers.sort(key=lambda x: x.section_name)
105106
return self._section_identifiers
106107

107108
def add_section_identifier(self, section_file_separator) -> None:
@@ -136,26 +137,30 @@ def rename_section(
136137
) -> None:
137138
"""
138139
rename_section method abstracts all section renaming actions
139-
by renaming the section identifier in the _section_identifiers list
140+
by replacing the section identifier in the _section_identifiers list
140141
and by replacing the key in the _data_by_sections dict
141142
"""
142143
idx = 0
143144
for idx, section_identifier in enumerate(self._section_identifiers):
144145
if section_identifier.section_file_separator == old_section_file_separator:
145146
break
146147

147-
self._section_identifiers[idx] = SectionIdentifier(
148-
defaults=self.defaults, section_file_separator=new_section_file_separator
148+
# need to preserve the order of the _section_identifiers list item
149+
# and the _data_by_sections dict items so that new items are placed at the end
150+
self._section_identifiers.pop(idx)
151+
self._section_identifiers.append(
152+
SectionIdentifier(
153+
defaults=self.defaults,
154+
section_file_separator=new_section_file_separator,
155+
)
149156
)
150157

151158
self._data_by_sections[new_section_file_separator] = self._data_by_sections[
152159
old_section_file_separator
153160
]
154161
del self._data_by_sections[old_section_file_separator]
155162

156-
def _transform_raw_data_content_to_data_by_sections(
157-
self,
158-
) -> Dict[SectionIdentifier, AnyStr]:
163+
def _transform_raw_data_content_to_data_by_sections(self) -> Dict[AnyStr, AnyStr]:
159164
dict_data = dict()
160165
for item in zip(
161166
self._section_identifiers,
@@ -165,7 +170,6 @@ def _transform_raw_data_content_to_data_by_sections(
165170
)[1:],
166171
):
167172
dict_data[item[0].section_file_separator] = item[1]
168-
169173
return dict_data
170174

171175
def transform_data_by_sections_to_raw_data_content(self) -> AnyStr:

notes_app/model/notes_model.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,8 @@
66
# method). For this, observers must be descendants of an abstract class,
77
# inheriting which, the `notify_model_is_changed` method must be overridden.
88
import json
9-
from os import linesep, path
109
import time
10+
from os import linesep, path
1111
from typing import AnyStr
1212

1313
GENERAL_DATE_TIME_FORMAT = "%Y-%m-%d %H:%M:%S"
@@ -126,7 +126,9 @@ def formatted(self):
126126
@property
127127
def external_update(self):
128128
return (
129-
get_file_updated_timestamp_as_epoch(self.file_path) > self.last_updated_on
129+
get_file_updated_timestamp_as_epoch(self.file_path)
130+
> self.last_updated_on
131+
> 0
130132
)
131133

132134
def add_observer(self, observer):

notes_app/search.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,7 @@ def search_for_occurrences(self, pattern, file, current_section_identifier):
8383
found_occurrences = dict()
8484

8585
if self.search_all_sections:
86-
sections_to_search_in = file.section_identifiers
86+
sections_to_search_in = file.section_identifiers_sorted_by_name
8787
else:
8888
sections_to_search_in = [current_section_identifier]
8989

notes_app/view/notes_view.py

Lines changed: 51 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
from kivymd.uix.snackbar import BaseSnackbar
2525

2626
from notes_app import __version__
27+
from notes_app.diff import merge_strings
2728
from notes_app.observer.notes_observer import Observer
2829

2930
from notes_app.color import (
@@ -192,7 +193,9 @@ def __init__(self, **kw):
192193
)
193194
self.current_section_identifier = self.file.default_section_identifier
194195
self.filter_data_split_by_section()
195-
self.set_drawer_items(section_identifiers=self.file.section_identifiers)
196+
self.set_drawer_items(
197+
section_identifiers=self.file.section_identifiers_sorted_by_name
198+
)
196199

197200
@property
198201
def is_unsaved_change(self):
@@ -388,7 +391,9 @@ def execute_open_file(self, file_path):
388391
controller=self.controller,
389392
defaults=self.defaults,
390393
)
391-
self.set_drawer_items(section_identifiers=self.file.section_identifiers)
394+
self.set_drawer_items(
395+
section_identifiers=self.file.section_identifiers_sorted_by_name
396+
)
392397
self.filter_data_split_by_section(
393398
section_identifier=self.file.default_section_identifier
394399
)
@@ -510,7 +515,8 @@ def execute_add_section(self, *args):
510515
not section_name
511516
or len(section_name) < SECTION_FILE_NAME_MINIMAL_CHAR_COUNT
512517
or section_name.isspace()
513-
or section_name in [si.section_name for si in self.file.section_identifiers]
518+
or section_name
519+
in [si.section_name for si in self.file.section_identifiers_sorted_by_name]
514520
):
515521
self.dialog.content_cls.add_section_result_message = "Invalid name"
516522
return
@@ -529,7 +535,9 @@ def execute_add_section(self, *args):
529535

530536
self.filter_data_split_by_section(section_identifier=section_identifier)
531537

532-
self.set_drawer_items(section_identifiers=self.file.section_identifiers)
538+
self.set_drawer_items(
539+
section_identifiers=self.file.section_identifiers_sorted_by_name
540+
)
533541

534542
self.cancel_dialog()
535543

@@ -541,7 +549,7 @@ def execute_edit_section(self, *args):
541549
or len(new_section_name) < SECTION_FILE_NAME_MINIMAL_CHAR_COUNT
542550
or new_section_name.isspace()
543551
or new_section_name
544-
in [si.section_name for si in self.file.section_identifiers]
552+
in [si.section_name for si in self.file.section_identifiers_sorted_by_name]
545553
or old_section_name == new_section_name
546554
):
547555
self.dialog.content_cls.edit_section_result_message = "Invalid name"
@@ -562,7 +570,11 @@ def execute_edit_section(self, *args):
562570

563571
self.filter_data_split_by_section(section_identifier=new_section_identifier)
564572

565-
self.set_drawer_items(section_identifiers=self.file.section_identifiers)
573+
self.set_drawer_items(
574+
section_identifiers=self.file.section_identifiers_sorted_by_name
575+
)
576+
577+
self.current_section_identifier = new_section_identifier
566578

567579
self.cancel_dialog()
568580

@@ -574,22 +586,46 @@ def cancel_dialog(self, *args):
574586
self.dialog = MDDialog()
575587

576588
def save_current_section_to_file(self):
589+
merged_current_section_text_data = None
590+
591+
if self.model.external_update:
592+
self.file.reload()
593+
try:
594+
current_section_text_before = self.file.get_section_content(
595+
section_file_separator=self.text_section_view.section_file_separator
596+
)
597+
# KeyError raised if the current section was removed or renamed by a external update
598+
except KeyError:
599+
# merge_strings prioritizes current_section_text_after over current_section_text_before
600+
# so empty string placeholder is set to current_section_text_before
601+
current_section_text_before = ""
602+
# self.file.reload() will remove the current section identifier from self.file.section_identifiers
603+
# in case it was deleted or renamed so the current section identifier is added back
604+
self.file.add_section_identifier(
605+
section_file_separator=self.text_section_view.section_file_separator
606+
)
607+
608+
current_section_text_after = self.text_section_view.text
609+
610+
merged_current_section_text_data = merge_strings(
611+
before=current_section_text_before, after=current_section_text_after
612+
)
613+
614+
self.text_section_view.text = merged_current_section_text_data
615+
self.set_drawer_items(
616+
section_identifiers=self.file.section_identifiers_sorted_by_name
617+
)
618+
577619
self.file.set_section_content(
578620
section_file_separator=self.text_section_view.section_file_separator,
579-
section_content=self.text_section_view.text,
621+
section_content=merged_current_section_text_data
622+
or self.text_section_view.text,
580623
)
581624

582625
text_data = self.file.transform_data_by_sections_to_raw_data_content()
583626

584627
self.controller.save_file_data(data=text_data)
585628

586-
if self.model.external_update:
587-
self.file.reload()
588-
self.text_section_view.text = self.file.get_section_content(
589-
section_file_separator=self.text_section_view.section_file_separator
590-
)
591-
self.set_drawer_items(section_identifiers=self.file.section_identifiers)
592-
593629
def press_menu_item_save_file(self, *args):
594630
self.save_current_section_to_file()
595631

@@ -656,7 +692,7 @@ def press_edit_section(self, section_item):
656692
self.dialog.open()
657693

658694
def press_delete_section(self, section_item):
659-
if len(self.file.section_identifiers) == 1:
695+
if len(self.file.section_identifiers_sorted_by_name) == 1:
660696
self.show_error_bar(error_message="Cannot delete last section")
661697
return
662698

notes_app_recording.gif

-825 KB
Loading

tests/test_unit_file.py

Lines changed: 20 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -89,34 +89,47 @@ def test__get_section_identifiers_from_raw_data_content(self, get_file):
8989
def test_default_section_identifier(self, get_file):
9090
assert isinstance(get_file.default_section_identifier, SectionIdentifier)
9191

92-
def test_section_identifiers(self, get_file):
92+
def test_section_identifiers_sorted_by_name(self, get_file):
9393
assert all(
94-
[isinstance(x, SectionIdentifier) for x in get_file.section_identifiers]
94+
[
95+
isinstance(x, SectionIdentifier)
96+
for x in get_file.section_identifiers_sorted_by_name
97+
]
9598
)
9699

100+
assert [
101+
x.section_name for x in get_file.section_identifiers_sorted_by_name
102+
] == ["first", "second"]
103+
97104
def test_add_section_identifier(self, get_file):
98105
assert (
99106
get_file.add_section_identifier(section_file_separator="<section=a> ")
100107
is None
101108
)
102-
assert len(get_file.section_identifiers) == 3
109+
assert len(get_file.section_identifiers_sorted_by_name) == 3
103110
assert all(
104-
[isinstance(x, SectionIdentifier) for x in get_file.section_identifiers]
111+
[
112+
isinstance(x, SectionIdentifier)
113+
for x in get_file.section_identifiers_sorted_by_name
114+
]
105115
)
106116

107117
def test_delete_section_identifier(self, get_file):
108118
assert (
109119
get_file.delete_section_identifier(section_file_separator="<section=a> ")
110120
is None
111121
)
112-
assert len(get_file.section_identifiers) == 2
122+
assert len(get_file.section_identifiers_sorted_by_name) == 2
113123
assert all(
114-
[isinstance(x, SectionIdentifier) for x in get_file.section_identifiers]
124+
[
125+
isinstance(x, SectionIdentifier)
126+
for x in get_file.section_identifiers_sorted_by_name
127+
]
115128
)
116129

117130
def test_delete_all_section_identifiers(self, get_file):
118131
assert get_file.delete_all_section_identifiers() is None
119-
assert get_file.section_identifiers == []
132+
assert get_file.section_identifiers_sorted_by_name == []
120133

121134
def test_set_get_section_content(self, get_file):
122135
assert not get_file.set_section_content(

tests/test_unit_model.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,10 @@ def test_external_update(self, get_model):
7979
get_model._last_updated_on = time.time()
8080
assert get_model.external_update is False
8181

82+
# now set model.last_updated_on to 0
83+
get_model._last_updated_on = 0
84+
assert get_model.external_update is False
85+
8286
def test_set_get_observers(self, get_model):
8387
observer = dict()
8488
get_model.add_observer(observer=observer)

0 commit comments

Comments
 (0)