Skip to content

Commit 0db8148

Browse files
jorgeecardonaDavidy22
authored andcommitted
Create a FileManager and use it to load files and yaml with minimal cache capabilities
1 parent 23ac1c0 commit 0db8148

File tree

7 files changed

+146
-22
lines changed

7 files changed

+146
-22
lines changed

guake/guake_app.py

Lines changed: 9 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@
1919
Boston, MA 02110-1301 USA
2020
"""
2121
import json
22-
import yaml
2322
import logging
2423
import os
2524
import shutil
@@ -70,6 +69,7 @@
7069
from guake.theme import patch_gtk_theme
7170
from guake.theme import select_gtk_theme
7271
from guake.utils import BackgroundImageManager
72+
from guake.utils import FileManager
7373
from guake.utils import FullscreenManager
7474
from guake.utils import HidePrevention
7575
from guake.utils import RectCalculator
@@ -191,9 +191,8 @@ def load_schema():
191191
# FullscreenManager
192192
self.fullscreen_manager = FullscreenManager(self.settings, self.window, self)
193193

194-
# Hold a copy of guake_yaml
195-
self._guake_yml = {}
196-
self._guake_yml_load_monotonic = {}
194+
# Start the file manager (only used by guake.yml so far).
195+
self.fm = FileManager()
197196

198197
# Workspace tracking
199198
self.notebook_manager = NotebookManager(
@@ -258,7 +257,6 @@ def window_event(*args):
258257
Keybinder.init()
259258
self.hotkeys = Keybinder
260259
Keybindings(self)
261-
262260
self.load_config()
263261

264262
if self.settings.general.get_boolean("start-fullscreen"):
@@ -1111,20 +1109,13 @@ def load_cwd_guake_yaml(self, vte) -> dict:
11111109
return {}
11121110

11131111
cwd = Path(vte.get_current_directory())
1114-
guake_yml = cwd.joinpath(".guake.yml")
1115-
content = {}
1112+
filename = str(cwd.joinpath(".guake.yml"))
11161113

1117-
if self._guake_yml_load_monotonic.get(guake_yml, 0.0) + 1.0 > pytime.monotonic():
1118-
content = self._guake_yml.get(guake_yml, {})
1119-
else:
1120-
try:
1121-
if guake_yml.is_file():
1122-
with guake_yml.open(encoding="utf-8") as fd:
1123-
content = yaml.safe_load(fd)
1124-
self._guake_yml[guake_yml] = content
1125-
self._guake_yml_load_monotonic[guake_yml] = pytime.monotonic()
1126-
except PermissionError:
1127-
log.debug("PermissionError on accessing .guake.yml")
1114+
try:
1115+
content = self.fm.read_yaml(filename)
1116+
except Exception:
1117+
log.debug("Unexpected error reading %s.", filename, exc_info=True)
1118+
content = {}
11281119

11291120
if not isinstance(content, dict):
11301121
content = {}

guake/keybindings.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,10 @@
1717
Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
1818
Boston, MA 02110-1301 USA
1919
"""
20-
from collections import defaultdict
2120
import logging
2221

22+
from collections import defaultdict
23+
2324
import gi
2425

2526
gi.require_version("Gtk", "3.0")

guake/tests/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import builtins
2+
23
from locale import gettext
34

45
builtins.__dict__["_"] = gettext

guake/tests/test_guake.py

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -189,3 +189,55 @@ def test_guake_hide_tab_bar_if_one_tab(mocker, g, fs):
189189
g.settings.general.set_boolean("hide-tabs-if-one-tab", True)
190190
assert g.get_notebook().get_n_pages() == 1
191191
assert g.get_notebook().get_property("show-tabs") is False
192+
193+
194+
def test_load_cwd_guake_yml_not_found_error(g):
195+
vte = g.get_notebook().get_current_terminal()
196+
assert g.fm.read_yaml("/foo/.guake.yml") is None
197+
assert g.load_cwd_guake_yaml(vte) == {}
198+
199+
200+
def test_load_cwd_guake_yml_encoding_error(g, mocker, fs):
201+
vte = g.get_notebook().get_current_terminal()
202+
mocker.patch.object(vte, "get_current_directory", return_value="/foo/")
203+
fs.create_file("/foo/.guake.yml", contents=b"\xfe\xf0[\xb1\x0b\xc1\x18\xda")
204+
assert g.fm.read_yaml("/foo/.guake.yml") is None
205+
assert g.load_cwd_guake_yaml(vte) == {}
206+
207+
208+
def test_load_cwd_guake_yml_format_error(g, mocker, fs):
209+
vte = g.get_notebook().get_current_terminal()
210+
mocker.patch.object(vte, "get_current_directory", return_value="/foo/")
211+
fs.create_file("/foo/.guake.yml", contents=b"[[as]")
212+
assert g.fm.read_yaml("/foo/.guake.yml") is None
213+
assert g.load_cwd_guake_yaml(vte) == {}
214+
215+
216+
def test_load_cwd_guake_yml(mocker, g, fs):
217+
vte = g.get_notebook().get_current_terminal()
218+
mocker.patch.object(vte, "get_current_directory", return_value="/foo/")
219+
220+
f = fs.create_file("/foo/.guake.yml", contents="title: bar")
221+
assert g.load_cwd_guake_yaml(vte) == {"title": "bar"}
222+
223+
# Cache in action.
224+
f.set_contents("title: foo")
225+
assert g.load_cwd_guake_yaml(vte) == {"title": "bar"}
226+
g.fm.clear()
227+
assert g.load_cwd_guake_yaml(vte) == {"title": "foo"}
228+
229+
230+
def test_guake_compute_tab_title(mocker, g, fs):
231+
vte = g.get_notebook().get_current_terminal()
232+
mocker.patch.object(vte, "get_current_directory", return_value="/foo/")
233+
234+
# Original title.
235+
assert g.compute_tab_title(vte) == "Terminal"
236+
237+
# Change title.
238+
fs.create_file("/foo/.guake.yml", contents="title: bar")
239+
assert g.compute_tab_title(vte) == "bar"
240+
241+
# Avoid loading the guake.yml
242+
mocker.patch.object(g.settings.general, "get_boolean", return_value=False)
243+
assert g.compute_tab_title(vte) == "Terminal"

guake/tests/test_utils.py

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
# -*- coding: utf-8 -*-
2+
# pylint: disable=redefined-outer-name
3+
4+
from guake.utils import FileManager
5+
6+
7+
def test_file_manager(fs):
8+
fs.create_file("/foo/bar", contents="test")
9+
fm = FileManager()
10+
assert fm.read("/foo/bar") == "test"
11+
12+
13+
def test_file_manager_hit(fs):
14+
f = fs.create_file("/foo/bar", contents="test")
15+
16+
fm = FileManager(delta=9999)
17+
assert fm.read("/foo/bar") == "test"
18+
f.set_contents("changed")
19+
assert fm.read("/foo/bar") == "test"
20+
21+
22+
def test_file_manager_miss(fs):
23+
f = fs.create_file("/foo/bar", contents="test")
24+
25+
fm = FileManager(delta=0.0)
26+
assert fm.read("/foo/bar") == "test"
27+
f.set_contents("changed")
28+
assert fm.read("/foo/bar") == "changed"
29+
30+
31+
def test_file_manager_clear(fs):
32+
f = fs.create_file("/foo/bar", contents="test")
33+
34+
fm = FileManager(delta=9999)
35+
assert fm.read("/foo/bar") == "test"
36+
f.set_contents("changed")
37+
assert fm.read("/foo/bar") == "test"
38+
fm.clear()
39+
assert fm.read("/foo/bar") == "changed"

guake/utils.py

Lines changed: 42 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,10 @@
2121
"""
2222
import enum
2323
import logging
24+
import os
2425
import subprocess
2526
import time
26-
import os
27+
import yaml
2728

2829
import cairo
2930

@@ -109,6 +110,46 @@ def restore_preferences(filename):
109110
p.communicate(input=prefs)
110111

111112

113+
class FileManager:
114+
def __init__(self, delta=1.0):
115+
self._cache = {}
116+
self._delta = max(0.0, delta)
117+
118+
def clear(self):
119+
self._cache.clear()
120+
121+
def read_yaml(self, filename: str):
122+
123+
content = None
124+
125+
try:
126+
content = self.read(filename)
127+
except PermissionError:
128+
log.debug("PermissionError while reading %s.", filename)
129+
except FileNotFoundError:
130+
log.debug("File %s does not exists.", filename)
131+
except UnicodeDecodeError:
132+
log.debug("Encoding error %s (we assume is utf-8).", filename)
133+
134+
if content is not None:
135+
try:
136+
content = yaml.safe_load(content)
137+
except yaml.YAMLError:
138+
log.debug("YAMLError reading %s.", filename)
139+
content = None
140+
return content
141+
142+
def read(self, filename: str) -> str:
143+
# Return the content of a file from the fs or from cache.
144+
if (
145+
filename not in self._cache
146+
or self._cache[filename]["time"] + self._delta < time.monotonic()
147+
):
148+
with open(filename, mode="r", encoding="utf-8") as fd:
149+
self._cache[filename] = {"time": time.monotonic(), "content": fd.read()}
150+
return self._cache[filename]["content"]
151+
152+
112153
class TabNameUtils:
113154
@classmethod
114155
def shorten(cls, text, settings):

requirements-dev.txt

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,8 @@ astroid
1515
autopep8
1616
black==21.8b0
1717
colorlog
18-
dataclasses
1918
fiximports>=0.1.18
20-
flake8
19+
flake8>=3.8.0,<4.0.0
2120
flakehell
2221
mock>=2.0.0
2322
pathlib2

0 commit comments

Comments
 (0)