Skip to content
This repository was archived by the owner on Oct 15, 2025. It is now read-only.

Commit 145b5e9

Browse files
author
Jose David Rueda
authored
[bitwarden] v3.1
- cache `rbw list` response - add cache_timeout config prop
1 parent a8507e5 commit 145b5e9

1 file changed

Lines changed: 77 additions & 23 deletions

File tree

bitwarden/__init__.py

Lines changed: 77 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,71 @@
11
# -*- coding: utf-8 -*-
22

3+
import time
4+
from dataclasses import dataclass
5+
from enum import Enum
36
from pathlib import Path
4-
from subprocess import run, CalledProcessError
7+
from subprocess import CalledProcessError, run
58

69
from albert import *
710

811
md_iid = "3.0"
9-
md_version = "3.0"
12+
md_version = "3.1"
1013
md_name = "Bitwarden"
1114
md_description = "'rbw' wrapper extension"
1215
md_license = "MIT"
1316
md_url = "https://github.com/albertlauncher/python/tree/main/bitwarden"
1417
md_authors = ["@ovitor", "@daviddeadly", "@manuelschneid3r"]
1518
md_bin_dependencies = ["rbw"]
1619

20+
MAX_MINUTES_CACHE_TIMEOUT = 60
21+
DEFAULT_MINUTE_CACHE_TIMEOUT = 5
22+
23+
24+
@dataclass(frozen=True)
25+
class ConfigKeys:
26+
CACHE_TIMEOUT = "cache_timeout"
27+
1728

1829
class Plugin(PluginInstance, TriggerQueryHandler):
30+
_cached_items = None
31+
_last_fetch_time = 0
1932

2033
iconUrls = [f"file:{Path(__file__).parent}/bw.svg"]
2134

2235
def __init__(self):
2336
PluginInstance.__init__(self)
2437
TriggerQueryHandler.__init__(self)
2538

39+
self.cache_timeout = (
40+
self.readConfig(ConfigKeys.CACHE_TIMEOUT, int)
41+
or DEFAULT_MINUTE_CACHE_TIMEOUT
42+
)
43+
2644
def defaultTrigger(self):
27-
return 'bw '
45+
return "bw "
46+
47+
@property
48+
def cache_timeout(self):
49+
return int(self._cache_timeout / 60)
50+
51+
@cache_timeout.setter
52+
def cache_timeout(self, value):
53+
self._cache_timeout = int(value * 60)
54+
self.writeConfig(ConfigKeys.CACHE_TIMEOUT, value)
55+
56+
def configWidget(self):
57+
return [
58+
{
59+
"type": "label",
60+
"text": "Cache (result of `rbw list`) duration",
61+
},
62+
{
63+
"type": "spinbox",
64+
"property": ConfigKeys.CACHE_TIMEOUT,
65+
"label": f"Minutes: (max: {MAX_MINUTES_CACHE_TIMEOUT}, disable: 0)",
66+
"widget_properties": {"maximum": MAX_MINUTES_CACHE_TIMEOUT},
67+
},
68+
]
2869

2970
def handleTriggerQuery(self, query):
3071
if query.string.strip().lower() == "sync":
@@ -37,11 +78,9 @@ def handleTriggerQuery(self, query):
3778
Action(
3879
id="sync",
3980
text="Syncing Bitwarden Vault",
40-
callable=lambda: run(
41-
["rbw", "sync"],
42-
)
81+
callable=lambda: self._sync_vault(),
4382
)
44-
]
83+
],
4584
)
4685
)
4786

@@ -56,30 +95,38 @@ def handleTriggerQuery(self, query):
5695
Action(
5796
id="copy",
5897
text="Copy password to clipboard",
59-
callable=lambda item=p: self._password_to_clipboard(item)
98+
callable=lambda item=p: self._password_to_clipboard(item),
6099
),
61100
Action(
62101
id="copy-auth",
63102
text="Copy auth code to clipboard",
64-
callable=lambda item=p: self._code_to_clipboard(item)
103+
callable=lambda item=p: self._code_to_clipboard(item),
65104
),
66105
Action(
67106
id="copy-username",
68107
text="Copy username to clipboard",
69-
callable=lambda username=p["user"]:
70-
setClipboardText(text=username)
108+
callable=lambda username=p["user"]: setClipboardText(
109+
text=username
110+
),
71111
),
72112
Action(
73113
id="edit",
74114
text="Edit entry in terminal",
75-
callable=lambda item=p: self._edit_entry(item)
76-
)
77-
]
115+
callable=lambda item=p: self._edit_entry(item),
116+
),
117+
],
78118
)
79119
)
80120

81-
@staticmethod
82-
def _get_items():
121+
def _get_items(self):
122+
not_first_time = self._cached_items is not None
123+
124+
time_passed = time.time() - self._last_fetch_time
125+
is_chache_fresh = time_passed < self._cache_timeout
126+
127+
if not_first_time and is_chache_fresh:
128+
return self._cached_items
129+
83130
field_names = ["id", "name", "user", "folder"]
84131
raw_items = run(
85132
["rbw", "list", "--fields", ",".join(field_names)],
@@ -98,12 +145,16 @@ def _get_items():
98145
item["path"] = item["folder"] + "/" + item["name"]
99146
else:
100147
item["path"] = item["name"]
148+
101149
items.append(item)
102150

151+
self._cached_items = items
152+
self._last_fetch_time = time.time()
153+
103154
return items
104155

105156
def _filter_items(self, query):
106-
passwords = self._get_items()
157+
passwords = self._get_items() or []
107158
search_fields = ["path", "user"]
108159
# Use a set for faster membership tests
109160
words = set(query.string.strip().lower().split())
@@ -121,15 +172,18 @@ def _filter_items(self, query):
121172

122173
return filtered_passwords
123174

175+
def _sync_vault(self):
176+
run(["rbw", "sync"], check=True)
177+
178+
self._cached_items = None
179+
self._last_fetch_time = 0
180+
124181
@staticmethod
125182
def _password_to_clipboard(item):
126183
rbw_id = item["id"]
127184

128185
password = run(
129-
["rbw", "get", rbw_id],
130-
capture_output=True,
131-
encoding="utf-8",
132-
check=True
186+
["rbw", "get", rbw_id], capture_output=True, encoding="utf-8", check=True
133187
).stdout.strip()
134188

135189
setClipboardText(text=password)
@@ -143,14 +197,14 @@ def _code_to_clipboard(item):
143197
["rbw", "code", rbw_id],
144198
capture_output=True,
145199
encoding="utf-8",
146-
check=True
200+
check=True,
147201
).stdout.strip()
148202
except CalledProcessError as err:
149203
code = run(
150204
["echo", err.__str__()],
151205
capture_output=True,
152206
encoding="utf-8",
153-
check=True
207+
check=True,
154208
).stdout.strip()
155209

156210
setClipboardText(text=code)

0 commit comments

Comments
 (0)