diff --git a/.gitignore b/.gitignore index bdf7aae8..912c5e48 100644 --- a/.gitignore +++ b/.gitignore @@ -7,7 +7,6 @@ core __pycache__ current rascsi_interface_pb2.py -src/raspberrypi/hfdisk/ *~ messages.pot messages.mo @@ -27,3 +26,7 @@ s.sh # temporary kicad files *-backups + +# submodules +hfdisk* +mac-hard-disk-drivers diff --git a/docker/rascsi-web/Dockerfile b/docker/rascsi-web/Dockerfile index 95c5578c..106137be 100644 --- a/docker/rascsi-web/Dockerfile +++ b/docker/rascsi-web/Dockerfile @@ -28,7 +28,7 @@ RUN ./easyinstall.sh --run_choice=11 RUN ./easyinstall.sh --run_choice=13 # Setup wired network bridge -RUN ./easyinstall.sh --run_choice=6 --headless +RUN ./easyinstall.sh --run_choice=5 --headless USER root WORKDIR /home/pi diff --git a/easyinstall.sh b/easyinstall.sh index b352ccbb..4dba5c65 100755 --- a/easyinstall.sh +++ b/easyinstall.sh @@ -58,13 +58,13 @@ PYTHON_COMMON_PATH="$BASE/python/common" SYSTEMD_PATH="/etc/systemd/system" SSL_CERTS_PATH="/etc/ssl/certs" SSL_KEYS_PATH="/etc/ssl/private" -HFS_FORMAT=/usr/bin/hformat HFDISK_BIN=/usr/bin/hfdisk -LIDO_DRIVER=$BASE/lido-driver.img GIT_BRANCH=$(git rev-parse --abbrev-ref HEAD) GIT_REMOTE=${GIT_REMOTE:-origin} TOKEN="" SECRET_FILE="$HOME/.config/rascsi/rascsi_secret" +FILE_SHARE_PATH="$HOME/shared_files" +FILE_SHARE_NAME="Pi File Server" set -e @@ -106,7 +106,10 @@ function installPackages() { unar \ disktype \ libgmock-dev \ - man2html + man2html \ + hfsutils \ + dosfstools \ + kpartx } # install Debian packges for RaSCSI standalone @@ -592,98 +595,30 @@ function createDriveCustom() { createDrive "$driveSize" "$driveName" } -# Creates an HFS file system -function formatDrive() { - diskPath="$1" - volumeName="$2" - - if [ ! -x $HFS_FORMAT ]; then - # Install hfsutils to have hformat to format HFS - sudo apt-get install hfsutils --assume-yes
  • {{ _("The largest file size accepted in this form is %(max_file_size)s MiB. Use other file transfer means for larger files.", max_file_size=max_file_size) }}
  • {{ _("File uploads will progress only if you stay on this page. If you navigate away before the transfer is completed, you will end up with an incomplete file.") }}
  • -
  • {{ _("Install Netatalk to use the AFP File Server.", url="https://github.com/akuker/RASCSI/wiki/AFP-File-Sharing") }}
  • +
  • {{ _("Install Netatalk or Samba to use the File Server.") }}
  • @@ -447,8 +447,8 @@

    @@ -489,15 +489,15 @@ {{ _("Download File from the Web") }}
    @@ -560,6 +560,7 @@ @@ -587,6 +588,24 @@ {% endfor %} + +
    diff --git a/python/web/src/web.py b/python/web/src/web.py index 6e79f405..2565d1c7 100644 --- a/python/web/src/web.py +++ b/python/web/src/web.py @@ -59,7 +59,7 @@ from web_utils import ( ) from settings import ( WEB_DIR, - AFP_DIR, + FILE_SERVER_DIR, MAX_FILE_SIZE, DEFAULT_CONFIG, DRIVE_PROPERTIES_FILE, @@ -251,7 +251,7 @@ def index(): drive_properties=format_drive_properties(APP.config["RASCSI_DRIVE_PROPERTIES"]), RESERVATIONS=RESERVATIONS, CFG_DIR=CFG_DIR, - AFP_DIR=AFP_DIR, + FILE_SERVER_DIR=FILE_SERVER_DIR, PROPERTIES_SUFFIX=PROPERTIES_SUFFIX, ARCHIVE_FILE_SUFFIXES=ARCHIVE_FILE_SUFFIXES, CONFIG_FILE_SUFFIX=CONFIG_FILE_SUFFIX, @@ -864,8 +864,8 @@ def download_file(): """ destination = request.form.get("destination") url = request.form.get("url") - if destination == "afp": - destination_dir = AFP_DIR + if destination == "file_server": + destination_dir = FILE_SERVER_DIR else: server_info = ractl_cmd.get_server_info() destination_dir = server_info["image_dir"] @@ -895,8 +895,8 @@ def upload_file(): return make_response(auth["msg"], 403) destination = request.form.get("destination") - if destination == "afp": - destination_dir = AFP_DIR + if destination == "file_server": + destination_dir = FILE_SERVER_DIR else: server_info = ractl_cmd.get_server_info() destination_dir = server_info["image_dir"] @@ -913,6 +913,7 @@ def create_file(): size = (int(request.form.get("size")) * 1024 * 1024) file_type = request.form.get("type") drive_name = request.form.get("drive_name") + drive_format = request.form.get("drive_format") safe_path = is_safe_path(file_name) if not safe_path["status"]: @@ -922,6 +923,68 @@ def create_file(): if not process["status"]: return response(error=True, message=process["msg"]) + message_postfix = "" + + # Formatting and injecting driver, if one is choosen + if drive_format: + volume_name = f"HD {size / 1024 / 1024:0.0f}M" + known_formats = [ + "Lido 7.56", + "SpeedTools 3.6", + "FAT16", + "FAT32", + ] + message_postfix = f" ({drive_format})" + + if drive_format not in known_formats: + return response( + error=True, + message=_( + "%(drive_format)s is not a valid hard disk format.", + drive_format=drive_format, + ) + ) + elif drive_format.startswith("FAT"): + if drive_format == "FAT16": + fat_size = "16" + elif drive_format == "FAT32": + fat_size = "32" + else: + return response( + error=True, + message=_( + "%(drive_format)s is not a valid hard disk format.", + drive_format=drive_format, + ) + ) + + process = file_cmd.partition_disk(full_file_name, volume_name, "FAT") + if not process["status"]: + return response(error=True, message=process["msg"]) + + process = file_cmd.format_fat( + full_file_name, + # FAT volume labels are max 11 chars + volume_name[:11], + fat_size, + ) + if not process["status"]: + return response(error=True, message=process["msg"]) + + else: + driver_base_path = Path(f"{WEB_DIR}/../../../mac-hard-disk-drivers") + process = file_cmd.partition_disk(full_file_name, volume_name, "HFS") + if not process["status"]: + return response(error=True, message=process["msg"]) + + process = file_cmd.format_hfs( + full_file_name, + volume_name, + driver_base_path / Path(drive_format.replace(" ", "-") + ".img"), + ) + if not process["status"]: + return response(error=True, message=process["msg"]) + # Creating the drive properties file, if one is chosen if drive_name: properties = get_properties_by_drive_name( @@ -937,15 +1000,20 @@ def create_file(): return response( status_code=201, message=_( - "Image file with properties created: %(file_name)s", + "Image file with properties created: %(file_name)s%(drive_format)s", file_name=full_file_name, + drive_format=message_postfix, ), image=full_file_name, ) return response( status_code=201, - message=_("Image file created: %(file_name)s", file_name=full_file_name), + message=_( + "Image file created: %(file_name)s%(drive_format)s", + file_name=full_file_name, + drive_format=message_postfix, + ), image=full_file_name, ) diff --git a/python/web/tests/api/test_files.py b/python/web/tests/api/test_files.py index b36d7f5f..4ad2a4df 100644 --- a/python/web/tests/api/test_files.py +++ b/python/web/tests/api/test_files.py @@ -65,6 +65,62 @@ def test_create_file_with_properties(http_client, list_files, delete_file): delete_file(file_name) +# route("/files/create", methods=["POST"]) +def test_create_file_and_format_hfs(http_client, list_files, delete_file): + file_prefix = str(uuid.uuid4()) + file_name = f"{file_prefix}.hda" + + response = http_client.post( + "/files/create", + data={ + "file_name": file_prefix, + "type": "hda", + "size": 1, + "drive_format": "Lido 7.56", + }, + ) + + response_data = response.json() + + assert response.status_code == 201 + assert response_data["status"] == STATUS_SUCCESS + assert response_data["data"]["image"] == file_name + assert response_data["messages"][0]["message"] == f"Image file created: {file_name} (Lido 7.56)" + assert file_name in list_files() + + # Cleanup + delete_file(file_name) + + +# route("/files/create", methods=["POST"]) +def test_create_file_and_format_fat(env, http_client, list_files, delete_file): + if env["is_docker"]: + pytest.skip("Test not supported in Docker environment.") + file_prefix = str(uuid.uuid4()) + file_name = f"{file_prefix}.hdr" + + response = http_client.post( + "/files/create", + data={ + "file_name": file_prefix, + "type": "hdr", + "size": 1, + "drive_format": "FAT32", + }, + ) + + response_data = response.json() + + assert response.status_code == 201 + assert response_data["status"] == STATUS_SUCCESS + assert response_data["data"]["image"] == file_name + assert response_data["messages"][0]["message"] == f"Image file created: {file_name} (FAT32)" + assert file_name in list_files() + + # Cleanup + delete_file(file_name) + + # route("/files/rename", methods=["POST"]) def test_rename_file(http_client, create_test_image, list_files, delete_file): original_file = create_test_image(auto_delete=False) diff --git a/python/web/tests/conftest.py b/python/web/tests/conftest.py index 7e70b696..3fdeb92d 100644 --- a/python/web/tests/conftest.py +++ b/python/web/tests/conftest.py @@ -23,7 +23,7 @@ def env(pytestconfig): "home_dir": home_dir, "cfg_dir": f"{home_dir}/.config/rascsi", "images_dir": f"{home_dir}/images", - "afp_dir": f"{home_dir}/afpshare", + "file_server_dir": f"{home_dir}/shared_files", }