Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve admin webui #112

Merged
merged 17 commits into from
Feb 25, 2024
Merged
Show file tree
Hide file tree
Changes from 11 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .flake8
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
[flake8]
max-line-length = 88
extend-ignore = E203
per-file-ignores = __init__.py:F401 spkrepo/app.py:F841
per-file-ignores = __init__.py:F401
exclude =
docs/*
migrations/*
2 changes: 1 addition & 1 deletion migrations/alembic.ini
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
[alembic]
script_location = .
script_location = ./migrations

[loggers]
keys = root,sqlalchemy,alembic
Expand Down
5 changes: 5 additions & 0 deletions migrations/env.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,14 @@
from flask import current_app
from sqlalchemy import engine_from_config, pool

from spkrepo import create_app

config = context.config
fileConfig(config.config_file_name)

# Set up the Flask application context
app = create_app() # Create the Flask app
app.app_context().push() # Push the app context

config.set_main_option(
"sqlalchemy.url", current_app.config.get("SQLALCHEMY_DATABASE_URI")
Expand Down
4 changes: 0 additions & 4 deletions migrations/versions/dc7687894ba7_increase_field_sizes.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@


def upgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.alter_column(
"version",
"conf_dependencies",
Expand Down Expand Up @@ -42,11 +41,9 @@ def upgrade():
type_=sa.UnicodeText(),
existing_nullable=True,
)
# ### end Alembic commands ###


def downgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.alter_column(
"version",
"conf_resource",
Expand Down Expand Up @@ -75,4 +72,3 @@ def downgrade():
type_=sa.VARCHAR(length=255),
existing_nullable=True,
)
# ### end Alembic commands ###
11 changes: 6 additions & 5 deletions spkrepo/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,11 @@
import jinja2
from flask import Flask
from flask_admin import Admin
from flask_babel import Babel
from wtforms import HiddenField

from . import config as default_config
from .cli import spkrepo as spkrepo_cli
from .ext import cache, db, debug_toolbar, mail, migrate, security
from .ext import babel, cache, db, debug_toolbar, mail, migrate, security
from .models import user_datastore
from .views import (
ArchitectureView,
Expand All @@ -16,6 +15,7 @@
IndexView,
PackageView,
ScreenshotView,
ServiceView,
SpkrepoConfirmRegisterForm,
UserView,
VersionView,
Expand Down Expand Up @@ -53,18 +53,19 @@ def create_app(config=None, register_blueprints=True, init_admin=True):
admin.add_view(UserView())
admin.add_view(ArchitectureView())
admin.add_view(FirmwareView())
admin.add_view(ServiceView())
admin.add_view(ScreenshotView())
admin.add_view(PackageView())
admin.add_view(VersionView())
admin.add_view(BuildView())
admin.init_app(app)

# Initialize Flask-Babel
babel = Babel(app)

# Commands
app.cli.add_command(spkrepo_cli)

# Flask-Babel
babel.init_app(app)

# SQLAlchemy
db.init_app(app)

Expand Down
9 changes: 8 additions & 1 deletion spkrepo/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -180,8 +180,15 @@ def depopulate_db():
from spkrepo.models import Package

for package in Package.query.all():
shutil.rmtree(os.path.join(current_app.config["DATA_PATH"], package.name))
# Delete the package and its associated versions and builds
db.session.delete(package)

# Remove the directory associated with the package (if it exists)
shutil.rmtree(
os.path.join(current_app.config["DATA_PATH"], package.name),
ignore_errors=True,
)

db.session.commit()


Expand Down
4 changes: 4 additions & 0 deletions spkrepo/ext.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,15 @@
# -*- coding: utf-8 -*-
from flask_babel import Babel
from flask_caching import Cache
from flask_debugtoolbar import DebugToolbarExtension
from flask_mail import Mail
from flask_migrate import Migrate
from flask_security import Security
from flask_sqlalchemy import SQLAlchemy

# Flask-Babel
babel = Babel()

# Cache
cache = Cache()

Expand Down
27 changes: 25 additions & 2 deletions spkrepo/models.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
# -*- coding: utf-8 -*-
import hashlib
import io
import os
import shutil
Expand Down Expand Up @@ -328,7 +329,7 @@ class Build(db.Model):
publisher_user_id = db.Column(db.Integer, db.ForeignKey("user.id"))
checksum = db.Column(db.Unicode(32))
extract_size = db.Column(db.Integer)
path = db.Column(db.Unicode(100))
path = db.Column(db.Unicode(2048))
md5 = db.Column(db.Unicode(32))
insert_date = db.Column(db.DateTime, default=db.func.now(), nullable=False)
active = db.Column(db.Boolean(), default=False, nullable=False)
Expand All @@ -343,7 +344,11 @@ class Build(db.Model):
)
firmware = db.relationship("Firmware", lazy=False)
publisher = db.relationship("User", foreign_keys=[publisher_user_id])
downloads = db.relationship("Download", back_populates="build")
downloads = db.relationship(
"Download",
back_populates="build",
cascade="save-update, merge, delete, delete-orphan",
)

@classmethod
def generate_filename(cls, package, version, firmware, architectures):
Expand All @@ -360,6 +365,24 @@ def save(self, stream):
) as f:
f.write(stream.read())

def calculate_md5(self):
if not self.path:
raise ValueError("Path cannot be empty.")

file_path = os.path.join(current_app.config["DATA_PATH"], self.path)

if not os.path.exists(file_path):
raise FileNotFoundError(f"File not found at path: {file_path}")

if self.md5 is None:
with io.open(file_path, "rb") as f:
md5_hash = hashlib.md5()
for chunk in iter(lambda: f.read(4096), b""):
md5_hash.update(chunk)
return md5_hash.hexdigest()

return self.md5

def _after_insert(self):
assert os.path.exists(os.path.join(current_app.config["DATA_PATH"], self.path))

Expand Down
3 changes: 1 addition & 2 deletions spkrepo/tests/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -281,8 +281,7 @@ def create_spk(self, create, extracted, **kwargs):
with create_spk(self) as spk_stream:
self.save(spk_stream)
if self.md5 is None:
spk_stream.seek(0)
self.md5 = hashlib.md5(spk_stream.read()).hexdigest()
self.md5 = self.calculate_md5()
spk_stream.close()

@classmethod
Expand Down
57 changes: 37 additions & 20 deletions spkrepo/tests/test_nas.py
Original file line number Diff line number Diff line change
Expand Up @@ -227,11 +227,11 @@ def test_stable_build_active_stable(self):
catalog[0], build, data, dict(arch="88f628x", build="1594")
)

def test_stable_build_active_stable_5004(self):
def test_stable_noarch_build_active_stable_5004(self):
build = BuildFactory(
active=True,
version__report_url=None,
architectures=[Architecture.find("88f6281", syno=True)],
architectures=[Architecture.find("noarch", syno=True)],
firmware=Firmware.find(1594),
)
db.session.commit()
Expand All @@ -247,6 +247,23 @@ def test_stable_build_active_stable_5004(self):
catalog["packages"][0], build, data, dict(arch="88f628x", build="5004")
)

def test_stable_arch_build_active_stable_5004(self):
BuildFactory(
active=True,
version__report_url=None,
architectures=[Architecture.find("88f6281", syno=True)],
firmware=Firmware.find(1594),
)
db.session.commit()
data = dict(arch="88f6281", build="5004", language="enu")
response = self.client.post(url_for("nas.catalog"), data=data)
self.assert200(response)
self.assertHeader(response, "Content-Type", "application/json")
catalog = json.loads(response.data.decode())
self.assertIn("packages", catalog)
self.assertIn("keyrings", catalog)
self.assertEqual(len(catalog["packages"]), 0)

def test_stable_build_active_stable_download_count(self):
package = PackageFactory()
build = BuildFactory(
Expand Down Expand Up @@ -543,9 +560,9 @@ def test_generic(self):
response = self.client.get(
url_for(
"nas.download",
architecture_id=architecture.id,
firmware_build=4458,
build_id=build.id,
md5=build.md5,
arch=architecture.code,
build=4458,
),
environ_base={"REMOTE_ADDR": "127.0.0.1"},
headers={"User-Agent": "My User Agent"},
Expand Down Expand Up @@ -575,9 +592,9 @@ def test_wrong_build(self):
response = self.client.get(
url_for(
"nas.download",
architecture_id=architecture.id,
firmware_build=4458,
build_id=build.id + 1,
md5=build.md5 + "1",
arch=architecture.code,
build=4458,
)
)
self.assert404(response)
Expand All @@ -596,9 +613,9 @@ def test_inactive_build(self):
response = self.client.get(
url_for(
"nas.download",
architecture_id=architecture.id,
firmware_build=4458,
build_id=build.id,
md5=build.md5,
arch=architecture.code,
build=4458,
)
)
self.assert403(response)
Expand All @@ -617,9 +634,9 @@ def test_wrong_architecture(self):
response = self.client.get(
url_for(
"nas.download",
architecture_id=10,
firmware_build=4458,
build_id=build.id,
md5=build.md5,
arch=Architecture.find(10).code if Architecture.find(10) else "",
build=4458,
)
)
self.assert404(response)
Expand All @@ -638,9 +655,9 @@ def test_incorrect_architecture(self):
response = self.client.get(
url_for(
"nas.download",
architecture_id=Architecture.find("cedarview").id,
firmware_build=4458,
build_id=build.id,
md5=build.md5,
arch=Architecture.find("cedarview").code,
build=4458,
)
)
self.assert400(response)
Expand All @@ -659,9 +676,9 @@ def test_incorrect_firmware_build(self):
response = self.client.get(
url_for(
"nas.download",
architecture_id=architecture.id,
firmware_build=1593,
build_id=build.id,
md5=build.md5,
arch=architecture.code,
build=1593,
)
)
self.assert400(response)
Expand Down
12 changes: 12 additions & 0 deletions spkrepo/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -345,6 +345,18 @@ def unsign(self):
self.stream.truncate()
self.stream.seek(0)

def calculate_md5(self):
md5_hash = hashlib.md5()

# Ensure the stream position is at the beginning
self.stream.seek(0)

# Update MD5 hash directly from the stream
for chunk in iter(lambda: self.stream.read(4096), b""):
md5_hash.update(chunk)

return md5_hash.hexdigest()

def _generate_signature(self, stream, timestamp_url, gnupghome): # pragma: no cover
# generate the signature
gpg = gnupg.GPG(gnupghome=gnupghome)
Expand Down
1 change: 1 addition & 0 deletions spkrepo/views/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
IndexView,
PackageView,
ScreenshotView,
ServiceView,
UserView,
VersionView,
)
Expand Down
Loading