Skip to content
Merged
Show file tree
Hide file tree
Changes from 8 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
6 changes: 6 additions & 0 deletions app/api/files.py
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,12 @@ def upload_presentation() -> (dict, int):
Config.c.constants.presentation_file_max_size_in_megabytes
)
}, 404
# check if file can be stored (*2.5 is an estimate of pdf and preview)
if not DBManager().check_storage_limit(request.content_length * 2.5):
return {
'message': "Not enough place in database to store file"
}, 404
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Измените код на 413 (он тут более логичен - в 146 строчке тоже обновите) и добавьте обработку этой ошибки на клиенте - чтобы пользователь не гадал что и как сломалось (для случая переполнения БД добавьте сообщение, что необходимо обратиться по почте - она есть в конфиге в разделе bugreport)


presentation_file = request.files['presentation']

# check extension and mimetype of file
Expand Down
3 changes: 3 additions & 0 deletions app/mongo_models.py
Original file line number Diff line number Diff line change
Expand Up @@ -156,3 +156,6 @@ class Logs(MongoModel):
filename = fields.CharField()
funcName = fields.CharField()
lineno = fields.IntegerField()

class StorageMeta(MongoModel):
used_size = fields.IntegerField()
54 changes: 49 additions & 5 deletions app/mongo_odm.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
from gridfs import GridFSBucket, NoFile
from pymodm import connect
from pymodm.connection import _get_db
from pymodm.errors import ValidationError
from pymodm.errors import ValidationError, DoesNotExist
from pymodm.files import GridFSStorage
from pymongo import ReturnDocument
from pymongo.errors import CollectionInvalid
Expand All @@ -23,26 +23,35 @@
RecognizedAudioToProcess,
RecognizedPresentationsToProcess, Sessions,
TaskAttempts, TaskAttemptsToPassBack, Tasks,
Trainings, TrainingsToProcess)
Trainings, TrainingsToProcess, StorageMeta)
from app.status import (AudioStatus, PassBackStatus, PresentationStatus,
TrainingStatus)
from app.utils import remove_blank_and_none

logger = get_root_logger()


BYTES_PER_MB = 1024*1024

class DBManager:
def __new__(cls):
def __new__(cls, max_size=20000): # max_size only on first creation, in MB
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Максимальный размер для БД укажите в конфиге - раздел [constants] для этого подойдет

if not hasattr(cls, 'init_done'):
cls.instance = super(DBManager, cls).__new__(cls)
connect(Config.c.mongodb.url + Config.c.mongodb.database_name)
cls.instance.storage = GridFSStorage(GridFSBucket(_get_db()))
cls.instance.max_size = max_size * BYTES_PER_MB
cls.init_done = True
return cls.instance

def add_file(self, file, filename=uuid.uuid4()):
return self.storage.save(name=filename, content=file)
try:
file.seek(0, os.SEEK_END)
size = file.tell()
file.seek(0)
except:
size = len(file)
_id = self.storage.save(name=filename, content=file)
self.update_storage_size(size)
return _id

def read_and_add_file(self, path, filename=None):
if filename is None:
Expand All @@ -67,6 +76,41 @@ def get_file(self, file_id):
except (NoFile, ValidationError, InvalidId) as e:
logger.warning('file_id = {}, {}.'.format(file_id, e))
return None

def _get_or_create_storage_meta(self):
try:
return StorageMeta.objects.get({})
except DoesNotExist:
meta = StorageMeta(used_size=0).save()
return meta

def get_used_storage_size(self):
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

добавьте метод для актуализации данных (вдруг где-то логика дала сбой и насчитала нам что-то, или вручную из бд были удалены файлы) - ему нужно будет пройтись по всем документам и посчитать их общий вес

  • на странице capacity сделайте кнопку для такого обновления данных, чтобы по запросу пересчитывать размер и выводить актуальный

return self._get_or_create_storage_meta().used_size

def set_used_storage_size(self, size):
meta = self._get_or_create_storage_meta()
meta.used_size = size
meta.save()

def update_storage_size(self, deltasize):
meta = self._get_or_create_storage_meta()
meta.used_size += deltasize
meta.save()

def get_max_size(self):
return self.max_size

# returns Bool variable - True if file can be stored else False
def check_storage_limit(self, new_file_size):
current_size = self.get_used_storage_size()
inf_msg = (
f"Check for ability to add file: "
f"Current: {current_size/BYTES_PER_MB:.2f} MB, "
f"New file: {new_file_size/BYTES_PER_MB:.2f} MB, "
f"Storage size: {self.max_size/BYTES_PER_MB} MB"
)
logger.info(inf_msg)
return False if current_size + new_file_size > self.max_size else True


class TrainingsDBManager:
Expand Down
26 changes: 26 additions & 0 deletions app/routes/capacity.py
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Добавьте ссылку на эту страницу в /admin

Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
from flask import Blueprint, render_template
from app.mongo_odm import DBManager
from app.root_logger import get_root_logger
from app.lti_session_passback.auth_checkers import is_admin

logger = get_root_logger()

BYTES_PER_MB = 1024*1024

routes_capacity = Blueprint(
'routes_capacity', __name__, url_prefix='/capacity')

@routes_capacity.route('/', methods=['GET'])
def storage_capacity():
if not is_admin():
return {}, 404
current_size = DBManager().get_used_storage_size()
max_size = DBManager().get_max_size()
ratio = current_size / max_size
return render_template(
'capacity.html',
size=round(current_size / BYTES_PER_MB, 2),
max_size=round(max_size / BYTES_PER_MB, 2),
ratio=round(ratio * 100, 1)
)

19 changes: 19 additions & 0 deletions app/templates/capacity.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
{% extends 'base.html' %}

{% block content %}
<div class="container mt-4">
<div class="card">
<div class="card-body">
<h4 class="card-title">Загруженность Базы Данных</h4>
<p><strong>Использовано:</strong> {{ size }} Мбайт</p>
<p><strong>Максимум:</strong> {{ max_size }} Мбайт</p>
<p><strong>Заполнено:</strong> {{ ratio }}%</p>
<div class="progress">
<div class="progress-bar" role="progressbar" style="width: {{ ratio }}%;" aria-valuenow="{{ ratio }}" aria-valuemin="0" aria-valuemax="100">
{{ ratio }}
</div>
</div>
</div>
</div>
</div>
{% endblock %}
2 changes: 2 additions & 0 deletions app/web_speech_trainer.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
from app.routes.presentations import routes_presentations
from app.routes.trainings import routes_trainings
from app.routes.version import routes_version
from app.routes.capacity import routes_capacity
from app.status import PassBackStatus, TrainingStatus
from app.training_manager import TrainingManager
from app.utils import ALLOWED_EXTENSIONS, DEFAULT_EXTENSION
Expand All @@ -53,6 +54,7 @@
app.register_blueprint(routes_presentations)
app.register_blueprint(routes_trainings)
app.register_blueprint(routes_version)
app.register_blueprint(routes_capacity)

logger = get_root_logger(service_name='web')

Expand Down
1 change: 1 addition & 0 deletions tests/api/test_files.py
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,7 @@ def test_too_big_file(self):

def test_file_is_not_pdf(self):
with patch('app.api.files.check_auth', return_value=True), \
patch('app.api.files.DBManager') as mock_db_manager, \
patch('app.api.files.Config.c', new_callable=PropertyMock) as mock_config, \
app.test_client() as test_client:
mock_config.return_value = Mock(constants=Mock(presentation_file_max_size_in_megabytes='10'), testing=Mock(active=True))
Expand Down