Skip to content

Commit bc1297d

Browse files
authored
Merge pull request #50 from mensch272/wb@mensch
Fixed discord thread access errors
2 parents bd2d296 + 25fe90f commit bc1297d

File tree

12 files changed

+125
-97
lines changed

12 files changed

+125
-97
lines changed

CHANGELOG.md

+6
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,19 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
66

77
## [Unreleased] - yyyy-mm-dd
88

9+
## [0.8.1] - 2021-12-13
10+
911
## Changed
1012

1113
- Removed url lazy loading from db in favour of sql statement
14+
- Changed discord session `call` to `get` and it now returns the method
15+
instead of calling it
1216

1317
## Fixed
1418

1519
- Fixed where provided webnovel urls not added to db #44
20+
- Fixed discord dm message
21+
- Fixed discord thread access errors
1622

1723
## [0.8.0] - 2021-11-25
1824

README.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ The default environmental variables are shown below. Modify them to your liking
4242
```shell
4343
DISCORD_TOKEN= # Required: discord bot token
4444
DISCORD_SESSION_TIMEOUT=10 # Minutes
45-
DISCORD_DOWNLOAD_THREADS=4
45+
DISCORD_SESSION_THREADS=5
4646
DISCORD_SEARCH_LIMIT=20 # Maximum results to show
4747
DISCORD_SEARCH_DISABLED=no # Disable search functionality
4848
```

novelsave/client/bots/discord/config.py

+4-6
Original file line numberDiff line numberDiff line change
@@ -5,21 +5,21 @@
55

66
import dotenv
77
from loguru import logger
8+
import copy
89

910
from novelsave.settings import config, console_formatter
1011

1112

12-
@functools.lru_cache()
1313
def app() -> dict:
1414
"""Initialize and return the configuration used by the base application"""
15-
return config.copy()
15+
return copy.deepcopy(config)
1616

1717

1818
def logger_config() -> dict:
1919
return {
2020
"handlers": [
2121
{
22-
"sink": sys.stderr,
22+
"sink": sys.stdout,
2323
"level": "TRACE",
2424
"format": console_formatter,
2525
"backtrace": True,
@@ -58,9 +58,7 @@ def discord() -> dict:
5858
"key": discord_token,
5959
"session": {
6060
"retain": timedelta(minutes=intenv("DISCORD_SESSION_TIMEOUT", 10)),
61-
},
62-
"download": {
63-
"threads": intenv("DISCORD_DOWNLOAD_THREADS", 4),
61+
"threads": intenv("DISCORD_SESSION_THREADS", 5),
6462
},
6563
"search": {
6664
"limit": intenv("DISCORD_SEARCH_LIMIT", 20),

novelsave/client/bots/discord/decorators.py

+20-16
Original file line numberDiff line numberDiff line change
@@ -5,28 +5,32 @@
55
from .session import Session
66

77

8-
def ensure_close(func):
9-
"""Ensures that when this method ends the session will be closed"""
8+
def session_task(close_on_exit=True):
9+
def inner(func):
10+
"""Ensures that when this method ends the session will be closed"""
1011

11-
@functools.wraps(func)
12-
def wrapped(*args, **kwargs):
13-
session: Session = args[0].session
12+
@functools.wraps(func)
13+
def wrapped(*args, **kwargs):
14+
session: Session = args[0].session
1415

15-
result = None
16-
try:
17-
result = func(*args, **kwargs)
18-
except Exception as e:
19-
if not session.is_closed:
20-
session.send_sync(f"`❗ {str(e).strip()}`")
16+
result = None
17+
try:
18+
result = func(*args, **kwargs)
19+
except Exception as e:
20+
if not session.is_closed:
21+
session.send_sync(f"`❗ {str(e).strip()}`")
2122

22-
logger.exception(e)
23+
logger.exception(e)
2324

24-
if not session.is_closed:
25-
session.sync(session.close_and_inform)
25+
session.close_session()
26+
if close_on_exit and not session.is_closed:
27+
session.sync(session.close_and_inform)
2628

27-
return result
29+
return result
2830

29-
return wrapped
31+
return wrapped
32+
33+
return inner
3034

3135

3236
def log_error(func):

novelsave/client/bots/discord/endpoints/commands.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ async def dm(ctx: commands.Context):
1313
"""Send a direct message to you"""
1414
await ctx.author.send(
1515
f"Hello, {ctx.author.name}.\n"
16-
f" Send `{ctx.clean_prefix}help` to get usage instructions."
16+
f"Send `{ctx.clean_prefix}help` to get usage instructions."
1717
)
1818

1919

novelsave/client/bots/discord/endpoints/download.py

+46-41
Original file line numberDiff line numberDiff line change
@@ -16,13 +16,11 @@
1616
from novelsave.exceptions import SourceNotFoundException
1717
from novelsave.utils.helpers import url_helper, string_helper
1818
from .. import checks, mfmt
19-
from ..decorators import ensure_close
19+
from ..decorators import session_task
2020
from ..session import SessionFragment, SessionHandler
2121

2222

2323
class DownloadHandler(SessionFragment):
24-
download_threads: int = Provide["discord_config.download.threads"]
25-
2624
def __init__(self, *args, **kwargs):
2725
super(DownloadHandler, self).__init__(*args, **kwargs)
2826

@@ -46,12 +44,12 @@ async def download_state(self, ctx: commands.Context):
4644
async def packaging_state(self, ctx: commands.Context):
4745
await ctx.send("I'm currently packaging the novel.")
4846

49-
@ensure_close
47+
@session_task()
5048
def download(self, url: str, targets: List[str]):
5149
self.session.state = self.info_state
5250

5351
try:
54-
source_gateway = self.session.source_service.source_from_url(url)
52+
source_gateway = self.session.source_service().source_from_url(url)
5553
except SourceNotFoundException:
5654
self.session.send_sync(mfmt.error("This website is not yet supported."))
5755
self.session.send_sync(
@@ -61,7 +59,7 @@ def download(self, url: str, targets: List[str]):
6159
return
6260

6361
try:
64-
packagers = self.session.packager_provider.filter_packagers(targets)
62+
packagers = self.session.packager_provider().filter_packagers(targets)
6563
except ValueError as e:
6664
self.session.send_sync(mfmt.error(str(e)))
6765
return
@@ -77,9 +75,11 @@ def download(self, url: str, targets: List[str]):
7775
f"volumes of {chapter_count} chapters."
7876
)
7977

80-
novel = self.session.novel_service.insert_novel(novel_dto)
81-
self.session.novel_service.insert_chapters(novel, novel_dto.volumes)
82-
self.session.novel_service.insert_metadata(novel, novel_dto.metadata)
78+
novel_service = self.session.novel_service()
79+
80+
novel = novel_service.insert_novel(novel_dto)
81+
novel_service.insert_chapters(novel, novel_dto.volumes)
82+
novel_service.insert_metadata(novel, novel_dto.metadata)
8383

8484
self.session.state = self.download_state
8585
self.download_thumbnail(novel)
@@ -114,57 +114,61 @@ def download_thumbnail(self, novel: Novel):
114114
)
115115
return
116116

117-
thumbnail_path = self.session.path_service.thumbnail_path(novel)
118-
self.session.novel_service.set_thumbnail_asset(
119-
novel, self.session.path_service.relative_to_data_dir(thumbnail_path)
117+
path_service = self.session.path_service()
118+
novel_service = self.session.novel_service()
119+
120+
thumbnail_path = path_service.thumbnail_path(novel)
121+
novel_service.set_thumbnail_asset(
122+
novel, path_service.relative_to_data_dir(thumbnail_path)
120123
)
121124

122125
thumbnail_path.parent.mkdir(parents=True, exist_ok=True)
123-
self.session.file_service.write_bytes(thumbnail_path, response.content)
126+
self.session.file_service().write_bytes(thumbnail_path, response.content)
124127

125128
size = string_helper.format_bytes(len(response.content))
126129
self.session.send_sync(f"Downloaded and saved thumbnail image ({size}).")
127130

128131
def download_chapters(self, novel: Novel, source_gateway: BaseSourceGateway):
129-
chapters = self.session.novel_service.get_pending_chapters(novel, -1)
132+
novel_service = self.session.novel_service()
133+
134+
chapters = novel_service.get_pending_chapters(novel, -1)
130135
if not chapters:
131136
logger.info("Skipped chapter download as none are pending.")
132137
return
133138

134139
self.session.send_sync(
135-
f"Downloading {len(chapters)} chapters using {self.download_threads} threads…"
140+
f"Downloading {len(chapters)} chapters using {self.session.thread_count - 1} threads…"
136141
)
142+
137143
self.total = len(chapters)
138144
self.value = 1
139145

140-
with futures.ThreadPoolExecutor(
141-
max_workers=self.download_threads
142-
) as download_executor:
143-
download_futures = [
144-
download_executor.submit(
145-
source_gateway.update_chapter_content,
146-
self.session.dto_adapter.chapter_to_dto(c),
147-
)
148-
for c in chapters
149-
]
150-
151-
for chapter in futures.as_completed(download_futures):
152-
try:
153-
chapter_dto = chapter.result()
154-
except Exception as e:
155-
logger.exception(e)
156-
continue
157-
158-
chapter_dto.content = self.session.asset_service.collect_assets(
159-
novel, chapter_dto
160-
)
161-
self.session.novel_service.update_content(chapter_dto)
146+
dto_adapter = self.session.dto_adapter()
147+
asset_service = self.session.asset_service()
162148

163-
logger.debug(
164-
f"Chapter content downloaded: '{chapter_dto.title}' ({chapter_dto.index})"
165-
)
149+
download_futures = [
150+
self.session.executor.submit(
151+
source_gateway.update_chapter_content,
152+
dto_adapter.chapter_to_dto(c),
153+
)
154+
for c in chapters
155+
]
156+
157+
for chapter in futures.as_completed(download_futures):
158+
try:
159+
chapter_dto = chapter.result()
160+
except Exception as e:
161+
logger.exception(e)
162+
continue
163+
164+
chapter_dto.content = asset_service.collect_assets(novel, chapter_dto)
165+
novel_service.update_content(chapter_dto)
166+
167+
logger.debug(
168+
f"Chapter content downloaded: '{chapter_dto.title}' ({chapter_dto.index})"
169+
)
166170

167-
self.value += 1
171+
self.value += 1
168172

169173
def package(self, novel: Novel, packagers: Iterable[BasePackager]):
170174
formats = ", ".join(p.keywords()[0] for p in packagers)
@@ -179,6 +183,7 @@ def package(self, novel: Novel, packagers: Iterable[BasePackager]):
179183
else:
180184
self.session.send_sync(f"Uploading {output.name}…")
181185

186+
# TODO: ability upload larger than 8 Mb
182187
if output.stat().st_size > 7.99 * 1024 * 1024:
183188
self.session.send_sync(
184189
mfmt.error(

novelsave/client/bots/discord/endpoints/search.py

+5-4
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
from novelsave.core.dtos import NovelDTO
99
from novelsave.core.services.source import BaseSourceService
1010
from .. import checks, mfmt
11-
from ..decorators import log_error
11+
from ..decorators import log_error, session_task
1212
from ..session import SessionHandler, SessionFragment, Session
1313

1414

@@ -49,6 +49,7 @@ async def _state_source_select(self, ctx: commands.Context):
4949
await ctx.send(self._source_list())
5050

5151
@log_error
52+
@session_task(False)
5253
def search(self, word: str):
5354
self.session.state = self._state_searching
5455
search_capable = [
@@ -178,13 +179,13 @@ async def select(self, ctx: commands.Context, num: int):
178179
await ctx.send(self.unsupported)
179180
return
180181

181-
if not await session.call(SearchHandler.is_select):
182+
if not session.get(SearchHandler.is_select)():
182183
await ctx.send("Session does not require selection.")
183184
return
184185

185-
if await session.call(SearchHandler.is_novel_select):
186+
if session.get(SearchHandler.is_novel_select)():
186187
await session.run(ctx, SearchHandler.select_novel, num - 1)
187188
else:
188-
url = await session.call(SearchHandler.select_source, num - 1)
189+
url = session.get(SearchHandler.select_source)(num - 1)
189190
await ctx.send(f"{url} selected.")
190191
await ctx.invoke(session.bot.get_command("download"), url)

novelsave/client/bots/discord/main.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -24,9 +24,9 @@ def wire(packages):
2424
def main():
2525
"""Start the discord bot"""
2626
from .bot import bot
27-
from . import endpoints
27+
from . import endpoints, session
2828

29-
application, discord_application = wire([endpoints])
29+
application, discord_application = wire([endpoints, session])
3030

3131
# cogs
3232
bot.add_cog(endpoints.SessionCog())

novelsave/client/bots/discord/mixins.py

+27-10
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
11
import shutil
2+
import threading
3+
4+
from loguru import logger
25

36
from novelsave import migrations
47
from novelsave.containers import Application
@@ -62,19 +65,33 @@ def setup_container(self, id_: str):
6265
self.application = Application()
6366
self.application.config.from_dict(self._make_unique_config(id_))
6467

65-
# acquire services
66-
self.source_service = self.application.services.source_service()
67-
self.novel_service = self.application.services.novel_service()
68-
self.path_service = self.application.services.path_service()
69-
self.dto_adapter = self.application.adapters.dto_adapter()
70-
self.asset_service = self.application.services.asset_service()
71-
self.file_service = self.application.services.file_service()
72-
self.packager_provider = self.application.packagers.packager_provider()
73-
7468
# migrate database to latest schema
7569
migrations.migrate(self.application.config.get("infrastructure.database.url"))
7670

71+
def source_service(self):
72+
return self.application.services.source_service()
73+
74+
def novel_service(self):
75+
return self.application.services.novel_service()
76+
77+
def path_service(self):
78+
return self.application.services.path_service()
79+
80+
def dto_adapter(self):
81+
return self.application.adapters.dto_adapter()
82+
83+
def asset_service(self):
84+
return self.application.services.asset_service()
85+
86+
def file_service(self):
87+
return self.application.services.file_service()
88+
89+
def packager_provider(self):
90+
return self.application.packagers.packager_provider()
91+
7792
def close_session(self):
93+
logger.debug(f"Session closed; thread id: {threading.current_thread().ident}")
7894
self.application.infrastructure.session().close()
79-
self.application.infrastructure.session_factory().close_all()
95+
96+
def close_engine(self):
8097
self.application.infrastructure.engine().dispose()

novelsave/client/bots/discord/session/handler.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ def get(self, ctx: commands.Context) -> Session:
2828
def get_or_create(self, ctx: commands.Context):
2929
"""Create or return already existing session"""
3030
try:
31-
return self.get(ctx).renew_context(ctx)
31+
return self.get(ctx)
3232
except KeyError:
3333
session = self.session_factory(bot, ctx)
3434
self.sessions[session_key(ctx)] = session

0 commit comments

Comments
 (0)