overhangio / openedx-scorm-xblock

SCORM XBlock for Open edX
GNU Affero General Public License v3.0
39 stars 50 forks source link

FileExistsError #3

Closed deepdad closed 4 years ago

deepdad commented 4 years ago

The XBlock causes there to be a popup after uploading that "Studio's having trouble saving your work".

In the logs for the CMS, a FileExistsError is logged originating from the XBlock:

cms_1             | 2020-08-01 03:41:18,296 ERROR 11 [root] [user None] signals.py:23 - Uncaught exception from None
cms_1             | Traceback (most recent call last):
cms_1             |   File "/openedx/venv/lib/python3.5/site-packages/django/core/handlers/exception.py", line 34, in inner
cms_1             |     response = get_response(request)
cms_1             |   File "/openedx/venv/lib/python3.5/site-packages/django/core/handlers/base.py", line 115, in _get_response
cms_1             |     response = self.process_exception_by_middleware(e, request)
cms_1             |   File "/openedx/venv/lib/python3.5/site-packages/django/core/handlers/base.py", line 113, in _get_response
cms_1             |     response = wrapped_callback(request, *callback_args, **callback_kwargs)
cms_1             |   File "/opt/pyenv/versions/3.5.9/lib/python3.5/contextlib.py", line 30, in inner
cms_1             |     return func(*args, **kwds)
cms_1             |   File "/openedx/venv/lib/python3.5/site-packages/django/contrib/auth/decorators.py", line 21, in _wrapped_view
cms_1             |     return view_func(request, *args, **kwargs)
cms_1             |   File "/openedx/edx-platform/cms/djangoapps/contentstore/views/component.py", line 478, in component_handler
cms_1             |     resp = handler_descriptor.handle(handler, req, suffix)
cms_1             |   File "/openedx/venv/lib/python3.5/site-packages/xblock/mixins.py", line 89, in handle
cms_1             |     return self.runtime.handle(self, handler_name, request, suffix)
cms_1             |   File "/openedx/edx-platform/common/lib/xmodule/xmodule/x_module.py", line 1453, in handle
cms_1             |     return super(MetricsMixin, self).handle(block, handler_name, request, suffix=suffix)
cms_1             |   File "/openedx/venv/lib/python3.5/site-packages/xblock/runtime.py", line 1063, in handle
cms_1             |     results = handler(request, suffix)
cms_1             |   File "/openedx/venv/lib/python3.5/site-packages/openedxscorm/scormxblock.py", line 193, in studio_submit
cms_1             |     scorm_zipfile.open(zipinfo.filename),
cms_1             |   File "/openedx/venv/lib/python3.5/site-packages/django/core/files/storage.py", line 52, in save
cms_1             |     return self._save(name, content)
cms_1             |   File "/openedx/venv/lib/python3.5/site-packages/django/core/files/storage.py", line 249, in _save
cms_1             |     raise IOError("%s exists and is not a directory." % directory)
cms_1             | OSError: /openedx/media/scorm/c154229b568d45128e1098b530267a35/a346b1db27aaa89b89b31e1c3e2a1af04482abad/assets exists and is not a directory.
cms_1             | 2020-08-01 03:41:18,298 ERROR 11 [django.request] [user 4] log.py:228 - Internal Server Error: /xblock/block-v1:b+c+d+type@scorm+block@c154229b568d45128e1098b530267a35/handler/studio_submit
cms_1             | Traceback (most recent call last):
cms_1             |   File "/openedx/venv/lib/python3.5/site-packages/django/core/handlers/exception.py", line 34, in inner
cms_1             |     response = get_response(request)
cms_1             |   File "/openedx/venv/lib/python3.5/site-packages/django/core/handlers/base.py", line 115, in _get_response
cms_1             |     response = self.process_exception_by_middleware(e, request)
cms_1             |   File "/openedx/venv/lib/python3.5/site-packages/django/core/handlers/base.py", line 113, in _get_response
cms_1             |     response = wrapped_callback(request, *callback_args, **callback_kwargs)
cms_1             |   File "/opt/pyenv/versions/3.5.9/lib/python3.5/contextlib.py", line 30, in inner
cms_1             |     return func(*args, **kwds)
cms_1             |   File "/openedx/venv/lib/python3.5/site-packages/django/contrib/auth/decorators.py", line 21, in _wrapped_view
cms_1             |     return view_func(request, *args, **kwargs)
cms_1             |   File "/openedx/edx-platform/cms/djangoapps/contentstore/views/component.py", line 478, in component_handler
cms_1             |     resp = handler_descriptor.handle(handler, req, suffix)
cms_1             |   File "/openedx/venv/lib/python3.5/site-packages/xblock/mixins.py", line 89, in handle
cms_1             |     return self.runtime.handle(self, handler_name, request, suffix)
cms_1             |   File "/openedx/edx-platform/common/lib/xmodule/xmodule/x_module.py", line 1453, in handle
cms_1             |     return super(MetricsMixin, self).handle(block, handler_name, request, suffix=suffix)
cms_1             |   File "/openedx/venv/lib/python3.5/site-packages/xblock/runtime.py", line 1063, in handle
cms_1             |     results = handler(request, suffix)
cms_1             |   File "/openedx/venv/lib/python3.5/site-packages/openedxscorm/scormxblock.py", line 193, in studio_submit
cms_1             |     scorm_zipfile.open(zipinfo.filename),
cms_1             |   File "/openedx/venv/lib/python3.5/site-packages/django/core/files/storage.py", line 52, in save
cms_1             |     return self._save(name, content)
cms_1             |   File "/openedx/venv/lib/python3.5/site-packages/django/core/files/storage.py", line 249, in _save
cms_1             |     raise IOError("%s exists and is not a directory." % directory)
cms_1             | OSError: /openedx/media/scorm/c154229b568d45128e1098b530267a35/a346b1db27aaa89b89b31e1c3e2a1af04482abad/assets exists and is not a directory.
cms_1             | ...- - [01/Aug/2020:03:41:18 +0000] "POST /xblock/block-v1:b+c+d+type@scorm+block@c154229b568d45128e1098b530267a35/handler/studio_submit HTTP/1.0" 500 52 "https://studio.hoteruba.com/container/block-v1:b+c+d+type@vertical+block@652e43960d2b4f8499ecd9b85f8354a9" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/84.0.4147.105 Safari/537.36"

exception FileExistsError Raised when trying to create a file or directory which already exists. Corresponds to errno EEXIST.

Some SCORM content (notably the two SCORMS linked here) are consequently saved, publishable an working in the course, while apparently one other one (made with Evolve) does not work hereafter. However, in both caes the error is logged and it seems to be the cause for the other SCORM not working.

deepdad commented 4 years ago

Here is an example SCORM v1.2 course that doesn't work. https://drive.google.com/file/d/13_R6XrDgLD47cH0LYMeYPpO0wpt-bj3S/view?usp=sharing

deepdad commented 4 years ago

I also made this very simple Single Page course in Evolve (appitierre.com) and didn't fill anything in. Exported it as SCORM v1.2, I try uploading this in studio but it gives the "Studio's having trouble saving your work" error popup. This popup also appears when the upload succeeds though.

On the server side, I don't see anything strange. The logs log an error for a file or folder that already "exists", this is the error that causes the popup on the client side.

When I look inside the studio application container though, I see nothing odd. I can deleted the folder for the SCORM, then upload it in the web client. that causes the folder to be created, but that looks very normal. Well normal, what I see is that not all the files are uploaded. And that the subfolders are uploaded as empty files, which may be the cause of the error, not sure.

adlcp_rootv1p2.xsd course imsmanifest.xml ims_xml.xsd libraries main.html player assets imscp_rootv1p1p2.xsd imsmd_rootv1p2p1.xsd index.html log_output.html offline_API_wrapper.js popup.html

all flat files

and the rest are missing.

regisb commented 4 years ago

Hi @deepdad! Thanks for the thorough report. In particular, the SCORM v1.2 example package you linked above was very useful.

This bug is due to the fact that some of the ZipInfo objects listed by the .zipinfo() method are folders, not files. In Python 3.6 there exists a is_dir() method but this method is not available in Python 3.5, so we'll have to resort to something else.

The issue could not be reproduced with the other zip files, probably because the ZipInfo objects were listed in a different order.

This issue will be fixed in the next Tutor release.

deepdad commented 4 years ago

Hi @regisb is_dir could work via the os module I guess? Are you switching to Python3.6? 3.8 is the latest.

deepdad commented 4 years ago

Thanks a lot for fixing BTW.

regisb commented 4 years ago

Unfortunately we cannot switch to Python 3.6, as Open edX currently supports only Python 3.5. An upgrade to Python 3.8 is scheduled in the coming months though: https://openedx.atlassian.net/wiki/spaces/AC/pages/1165395730/Upgrades

deepdad commented 4 years ago

How can I apply your patch without adding the repo as a private requirement and without waiting for the next release please?

regisb commented 4 years ago

This patch is already released as part of v10.2.0.