Compare commits

..
6 Commits
6 changed files with 70 additions and 44 deletions
+4
View File
@@ -1,5 +1,9 @@
# Changelog
## Unreleased
- Fix legibility of "Damaged" and "Missing" fields for tiny screen by reducing horizontal padding
## 1.2.2:
This release fixes a bug where orphaned parts in the `inventory` table are blocking the database upgrade.
+49 -27
View File
@@ -92,50 +92,72 @@ class BrickInstructions(object):
# Download an instruction file
def download(self, path: str, /) -> None:
"""
Streams the PDF in chunks and uses self.socket.update_total
+ self.socket.progress_count to drive a determinate bar.
"""
try:
# start progress
self.socket.progress(message=f'Downloading {self.filename}')
target = self.path(filename=secure_filename(self.filename))
# skip if already exists
# Skip if we already have it
if os.path.isfile(target):
return self.socket.complete(
message=f'File {self.filename} already exists, skipped'
message=f"File {self.filename} already exists, skipped"
)
# path is already a full URL from find_instructions()
url = path
self.socket.progress(message=f'Requesting {url}')
# use cloudscraper to pass the CF challenge here too
# Fetch PDF via cloudscraper (to bypass Cloudflare)
scraper = cloudscraper.create_scraper()
scraper.headers.update({'User-Agent': current_app.config['REBRICKABLE_USER_AGENT']})
response = scraper.get(url, stream=True)
if not response.ok:
raise DownloadException(f'Failed to download: HTTP {response.status_code}')
scraper.headers.update({
"User-Agent": current_app.config['REBRICKABLE_USER_AGENT']
})
resp = scraper.get(path, stream=True)
if not resp.ok:
raise DownloadException(f"Failed to download: HTTP {resp.status_code}")
# record size if available
try:
self.size = int(response.headers.get('Content-Length', 0))
except ValueError:
self.size = 0
# Tell the socket how many bytes in total
total = int(resp.headers.get("Content-Length", 0))
self.socket.update_total(total)
# download to disk
self.socket.progress(message=f'Downloading {self.filename} ({self.human_size()})')
with open(target, 'wb') as f:
copyfileobj(response.raw, f)
# Reset the counter and kick off at 0%
self.socket.progress_count = 0
self.socket.progress(message=f"Starting download {self.filename}")
logger.info(f'The instruction file {self.filename} has been downloaded')
self.socket.complete(message=f'File {self.filename} downloaded ({self.human_size()})')
# Write out in 8 KiB chunks and update the counter
with open(target, "wb") as f:
for chunk in resp.iter_content(chunk_size=8192):
if not chunk:
continue
f.write(chunk)
# Bump the internal counter and emit
self.socket.progress_count += len(chunk)
self.socket.progress(
message=(
f"Downloading {self.filename} "
f"({humanize.naturalsize(self.socket.progress_count)}/"
f"{humanize.naturalsize(self.socket.progress_total)})"
)
)
# Done!
logger.info(f"Downloaded {self.filename}")
self.socket.complete(
message=f"File {self.filename} downloaded ({self.human_size()})"
)
except Exception as e:
self.socket.fail(
message=f'Error downloading {self.filename}: {e}'
)
logger.debug(traceback.format_exc())
self.socket.fail(
message=f"Error downloading {self.filename}: {e}"
)
# Display the size in a human format
def human_size(self) -> str:
return humanize.naturalsize(self.size)
try:
size = self.size
except AttributeError:
size = os.path.getsize(self.path())
return humanize.naturalsize(size)
# Display the time in a human format
def human_time(self) -> str:
+4 -4
View File
@@ -61,9 +61,9 @@ docker compose up -d
2. Access BrickTracker at `http://localhost:3333`
Please refer to [Environment Variables Reference](docs/env.md) for a list of available variables.
Please refer to [Environment Variables Reference](env.md) for a list of available variables.
3. Read more in [First steps](docs/first-steps.md)
3. Read more in [First steps](first-steps.md)
## Troubleshooting
@@ -85,6 +85,6 @@ Please refer to [Environment Variables Reference](docs/env.md) for a list of ava
- Check for any syntax errors in `.env` file
- Verify no conflicting environment variables are set in the shell
For more troubleshooting, take a look at [Common Errors](docs/common-errors.md)
For more troubleshooting, take a look at [Common Errors](common-errors.md)
Please refer to [Setup](docs/setup.md) for more information.
Please refer to [Setup](setup.md) for more information.
+1 -1
View File
@@ -53,7 +53,7 @@ services:
The [.env.sample](../.env.sample) file provides ample documentation on all the configurable options. Have a look at it.
You can make a copy of `.env.sample` as `.env` with your options or create an `.env` file from scratch.
[Environment Variables Reference](docs/env.md) contains a table of the available variables.
[Environment Variables Reference](env.md) contains a table of the available variables.
## Database file
+11 -11
View File
@@ -1,6 +1,6 @@
{% macro checkbox(name, id, prefix, url, checked, parent=none, delete=false) %}
{% if g.login.is_authenticated() %}
<input class="form-check-input" type="checkbox" id="{{ prefix }}-{{ id }}" {% if checked %}checked{% endif %}
<input class="form-check-input px-1" type="checkbox" id="{{ prefix }}-{{ id }}" {% if checked %}checked{% endif %}
{% if not delete %}
data-changer-id="{{ id }}" data-changer-prefix="{{ prefix }}" data-changer-url="{{ url }}" {% if parent %}data-changer-parent="{{ parent }}"{% endif %}
{% else %}
@@ -22,8 +22,8 @@
{% else %}
<label class="visually-hidden" for="{{ prefix }}-{{ id }}">{{ name }}</label>
<div class="input-group">
{% if icon %}<span class="input-group-text"><i class="ri-{{ icon }} me-1"></i><span class="ms-1 d-none d-md-inline"> {{ name }}</span></span>{% endif %}
<input class="form-control form-control-sm flex-shrink-1" type="text" id="{{ prefix }}-{{ id }}" value="{% if value %}{{ value }}{% endif %}"
{% if icon %}<span class="input-group-text px-1"><i class="ri-{{ icon }} me-1"></i><span class="ms-1 d-none d-md-inline"> {{ name }}</span></span>{% endif %}
<input class="form-control form-control-sm flex-shrink-1 px-1" type="text" id="{{ prefix }}-{{ id }}" value="{% if value %}{{ value }}{% endif %}"
{% if g.login.is_authenticated() %}
data-changer-id="{{ id }}" data-changer-prefix="{{ prefix }}" data-changer-url="{{ url }}"
{% if date %}data-changer-date="true"{% endif %}
@@ -31,12 +31,12 @@
disabled
{% endif %}
autocomplete="off">
{% if suffix %}<span class="input-group-text d-none d-md-inline">{{ suffix }}</span>{% endif %}
{% if suffix %}<span class="input-group-text d-none d-md-inline px-1">{{ suffix }}</span>{% endif %}
{% if g.login.is_authenticated() %}
<span id="status-{{ prefix }}-{{ id }}" class="input-group-text ri-save-line"></span>
<button id="clear-{{ prefix }}-{{ id }}" type="button" class="btn btn-sm btn-light btn-outline-danger border"><i class="ri-eraser-line"></i></button>
<span id="status-{{ prefix }}-{{ id }}" class="input-group-text ri-save-line px-1"></span>
<button id="clear-{{ prefix }}-{{ id }}" type="button" class="btn btn-sm btn-light btn-outline-danger border px-1"><i class="ri-eraser-line"></i></button>
{% else %}
<span class="input-group-text ri-prohibited-line"></span>
<span class="input-group-text ri-prohibited-line px-1"></span>
{% endif %}
</div>
{% endif %}
@@ -46,8 +46,8 @@
{% if g.login.is_authenticated() %}
<label class="visually-hidden" for="{{ prefix }}-{{ id }}">{{ name }}</label>
<div class="input-group">
{% if icon %}<span class="input-group-text"><i class="ri-{{ icon }} me-1"></i><span class="ms-1 d-none d-md-inline"> {{ name }}</span></span>{% endif %}
<select id="{{ prefix }}-{{ id }}" class="form-select"
{% if icon %}<span class="input-group-text px-1"><i class="ri-{{ icon }} me-1"></i><span class="ms-1 d-none d-md-inline"> {{ name }}</span></span>{% endif %}
<select id="{{ prefix }}-{{ id }}" class="form-select px-1"
{% if not delete %}
data-changer-id="{{ id }}" data-changer-prefix="{{ prefix }}" data-changer-url="{{ url }}"
{% else %}
@@ -59,8 +59,8 @@
<option value="{{ metadata.fields.id }}" {% if metadata.fields.id == value %}selected{% endif %}>{{ metadata.fields.name }}</option>
{% endfor %}
</select>
<span id="status-{{ prefix }}-{{ id }}" class="input-group-text ri-save-line"></span>
<button id="clear-{{ prefix }}-{{ id }}" type="button" class="btn btn-sm btn-light btn-outline-danger border"><i class="ri-eraser-line"></i></button>
<span id="status-{{ prefix }}-{{ id }}" class="input-group-text ri-save-line px-1"></span>
<button id="clear-{{ prefix }}-{{ id }}" type="button" class="btn btn-sm btn-light btn-outline-danger border px-1"><i class="ri-eraser-line"></i></button>
</div>
{% endif %}
{% endmacro %}
+1 -1
View File
@@ -10,7 +10,7 @@
<label class="visually-hidden" for="grid-search">Search</label>
<div class="input-group">
<span class="input-group-text"><i class="ri-search-line"></i><span class="ms-1 d-none d-md-inline"> Search</span></span>
<input id="grid-search" data-search-exact="name,number,parts,searchPurchaseLocation,searchStorage,theme,year" data-search-list="searchOwner,searchTag" class="form-control form-control-sm" type="text" placeholder="Set, name, number of parts, theme, year, owner, purchase location, storage, tag" value="">
<input id="grid-search" data-search-exact="name,number,parts,searchPurchaseLocation,searchStorage,theme,year" data-search-list="searchOwner,searchTag" class="form-control form-control-sm px-1" type="text" placeholder="Set, name, number of parts, theme, year, owner, purchase location, storage, tag" value="">
<button id="grid-search-clear" type="button" class="btn btn-light btn-outline-danger border"><i class="ri-eraser-line"></i></button>
</div>
</div>