mirror of
https://github.com/mofeng-git/One-KVM.git
synced 2025-12-12 01:00:29 +08:00
增加快速文件互传功能(基于 MSD)
为 MSD 路径添加配置选项 为 文件镜像名称添加配置选项 修复 make 测试环境
This commit is contained in:
parent
5450d7297c
commit
b8ddf7c2da
8
Makefile
8
Makefile
@ -86,7 +86,7 @@ tox: testenv
|
|||||||
&& cp /usr/share/kvmd/configs.default/kvmd/*.yaml /etc/kvmd \
|
&& cp /usr/share/kvmd/configs.default/kvmd/*.yaml /etc/kvmd \
|
||||||
&& cp /usr/share/kvmd/configs.default/kvmd/*passwd /etc/kvmd \
|
&& cp /usr/share/kvmd/configs.default/kvmd/*passwd /etc/kvmd \
|
||||||
&& cp /usr/share/kvmd/configs.default/kvmd/*.secret /etc/kvmd \
|
&& cp /usr/share/kvmd/configs.default/kvmd/*.secret /etc/kvmd \
|
||||||
&& cp /usr/share/kvmd/configs.default/kvmd/main/$(if $(P),$(P),$(DEFAULT_PLATFORM)).yaml /etc/kvmd/main.yaml \
|
&& cp /usr/share/kvmd/configs.default/kvmd/main.yaml /etc/kvmd/main.yaml \
|
||||||
&& mkdir -p /etc/kvmd/override.d \
|
&& mkdir -p /etc/kvmd/override.d \
|
||||||
&& cp /src/testenv/$(if $(P),$(P),$(DEFAULT_PLATFORM)).override.yaml /etc/kvmd/override.yaml \
|
&& cp /src/testenv/$(if $(P),$(P),$(DEFAULT_PLATFORM)).override.yaml /etc/kvmd/override.yaml \
|
||||||
&& cd /src \
|
&& cd /src \
|
||||||
@ -155,7 +155,7 @@ run-cfg: testenv
|
|||||||
&& cp /usr/share/kvmd/configs.default/kvmd/*.yaml /etc/kvmd \
|
&& cp /usr/share/kvmd/configs.default/kvmd/*.yaml /etc/kvmd \
|
||||||
&& cp /usr/share/kvmd/configs.default/kvmd/*passwd /etc/kvmd \
|
&& cp /usr/share/kvmd/configs.default/kvmd/*passwd /etc/kvmd \
|
||||||
&& cp /usr/share/kvmd/configs.default/kvmd/*.secret /etc/kvmd \
|
&& cp /usr/share/kvmd/configs.default/kvmd/*.secret /etc/kvmd \
|
||||||
&& cp /usr/share/kvmd/configs.default/kvmd/main/$(if $(P),$(P),$(DEFAULT_PLATFORM)).yaml /etc/kvmd/main.yaml \
|
&& cp /usr/share/kvmd/configs.default/kvmd/main.yaml /etc/kvmd/main.yaml \
|
||||||
&& mkdir -p /etc/kvmd/override.d \
|
&& mkdir -p /etc/kvmd/override.d \
|
||||||
&& cp /testenv/$(if $(P),$(P),$(DEFAULT_PLATFORM)).override.yaml /etc/kvmd/override.yaml \
|
&& cp /testenv/$(if $(P),$(P),$(DEFAULT_PLATFORM)).override.yaml /etc/kvmd/override.yaml \
|
||||||
&& $(if $(CMD),$(CMD),python -m kvmd.apps.kvmd -m) \
|
&& $(if $(CMD),$(CMD),python -m kvmd.apps.kvmd -m) \
|
||||||
@ -178,7 +178,7 @@ run-ipmi: testenv
|
|||||||
&& cp /usr/share/kvmd/configs.default/kvmd/*.yaml /etc/kvmd \
|
&& cp /usr/share/kvmd/configs.default/kvmd/*.yaml /etc/kvmd \
|
||||||
&& cp /usr/share/kvmd/configs.default/kvmd/*passwd /etc/kvmd \
|
&& cp /usr/share/kvmd/configs.default/kvmd/*passwd /etc/kvmd \
|
||||||
&& cp /usr/share/kvmd/configs.default/kvmd/*.secret /etc/kvmd \
|
&& cp /usr/share/kvmd/configs.default/kvmd/*.secret /etc/kvmd \
|
||||||
&& cp /usr/share/kvmd/configs.default/kvmd/main/$(if $(P),$(P),$(DEFAULT_PLATFORM)).yaml /etc/kvmd/main.yaml \
|
&& cp /usr/share/kvmd/configs.default/kvmd/main.yaml /etc/kvmd/main.yaml \
|
||||||
&& mkdir -p /etc/kvmd/override.d \
|
&& mkdir -p /etc/kvmd/override.d \
|
||||||
&& cp /testenv/$(if $(P),$(P),$(DEFAULT_PLATFORM)).override.yaml /etc/kvmd/override.yaml \
|
&& cp /testenv/$(if $(P),$(P),$(DEFAULT_PLATFORM)).override.yaml /etc/kvmd/override.yaml \
|
||||||
&& $(if $(CMD),$(CMD),python -m kvmd.apps.ipmi --run) \
|
&& $(if $(CMD),$(CMD),python -m kvmd.apps.ipmi --run) \
|
||||||
@ -201,7 +201,7 @@ run-vnc: testenv
|
|||||||
&& cp /usr/share/kvmd/configs.default/kvmd/*.yaml /etc/kvmd \
|
&& cp /usr/share/kvmd/configs.default/kvmd/*.yaml /etc/kvmd \
|
||||||
&& cp /usr/share/kvmd/configs.default/kvmd/*passwd /etc/kvmd \
|
&& cp /usr/share/kvmd/configs.default/kvmd/*passwd /etc/kvmd \
|
||||||
&& cp /usr/share/kvmd/configs.default/kvmd/*.secret /etc/kvmd \
|
&& cp /usr/share/kvmd/configs.default/kvmd/*.secret /etc/kvmd \
|
||||||
&& cp /usr/share/kvmd/configs.default/kvmd/main/$(if $(P),$(P),$(DEFAULT_PLATFORM)).yaml /etc/kvmd/main.yaml \
|
&& cp /usr/share/kvmd/configs.default/kvmd/main.yaml /etc/kvmd/main.yaml \
|
||||||
&& mkdir -p /etc/kvmd/override.d \
|
&& mkdir -p /etc/kvmd/override.d \
|
||||||
&& cp /testenv/$(if $(P),$(P),$(DEFAULT_PLATFORM)).override.yaml /etc/kvmd/override.yaml \
|
&& cp /testenv/$(if $(P),$(P),$(DEFAULT_PLATFORM)).override.yaml /etc/kvmd/override.yaml \
|
||||||
&& $(if $(CMD),$(CMD),python -m kvmd.apps.vnc --run) \
|
&& $(if $(CMD),$(CMD),python -m kvmd.apps.vnc --run) \
|
||||||
|
|||||||
@ -18,6 +18,7 @@ ENV TZ=Asia/Shanghai
|
|||||||
|
|
||||||
RUN cp /tmp/lib/* /lib/*-linux-*/ \
|
RUN cp /tmp/lib/* /lib/*-linux-*/ \
|
||||||
&& pip install --no-cache-dir --root-user-action=ignore --disable-pip-version-check /tmp/wheel/*.whl \
|
&& pip install --no-cache-dir --root-user-action=ignore --disable-pip-version-check /tmp/wheel/*.whl \
|
||||||
|
&& pip install --no-cache-dir --root-user-action=ignore --disable-pip-version-check pyfatfs \
|
||||||
&& rm -rf /tmp/lib /tmp/wheel
|
&& rm -rf /tmp/lib /tmp/wheel
|
||||||
|
|
||||||
RUN sed -i 's/deb.debian.org/mirrors.tuna.tsinghua.edu.cn/' /etc/apt/sources.list.d/debian.sources \
|
RUN sed -i 's/deb.debian.org/mirrors.tuna.tsinghua.edu.cn/' /etc/apt/sources.list.d/debian.sources \
|
||||||
@ -31,7 +32,7 @@ RUN if [ ${TARGETARCH} = arm ]; then ARCH=armhf; elif [ ${TARGETARCH} = arm64 ];
|
|||||||
&& chmod +x /usr/local/bin/ttyd \
|
&& chmod +x /usr/local/bin/ttyd \
|
||||||
&& adduser kvmd --gecos "" --disabled-password \
|
&& adduser kvmd --gecos "" --disabled-password \
|
||||||
&& ln -sf /usr/share/tesseract-ocr/*/tessdata /usr/share/tessdata \
|
&& ln -sf /usr/share/tesseract-ocr/*/tessdata /usr/share/tessdata \
|
||||||
&& mkdir -p /etc/kvmd_backup/override.d /var/lib/kvmd/msd/images /var/lib/kvmd/msd/meta /var/lib/kvmd/pst/data /opt/vc/bin /run/kvmd /tmp/kvmd-nginx \
|
&& mkdir -p /etc/kvmd_backup/override.d /var/lib/kvmd/msd/images /var/lib/kvmd/msd/meta /var/lib/kvmd/pst/data /var/lib/kvmd/msd/NormalFiles /opt/vc/bin /run/kvmd /tmp/kvmd-nginx \
|
||||||
&& touch /run/kvmd/ustreamer.sock
|
&& touch /run/kvmd/ustreamer.sock
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -21,6 +21,9 @@ kvmd:
|
|||||||
msd:
|
msd:
|
||||||
#type: otg
|
#type: otg
|
||||||
remount_cmd: /bin/true
|
remount_cmd: /bin/true
|
||||||
|
msd_path: /var/lib/kvmd/msd
|
||||||
|
normalfiles_path: NormalFiles
|
||||||
|
normalfiles_size: 256
|
||||||
|
|
||||||
ocr:
|
ocr:
|
||||||
langs:
|
langs:
|
||||||
|
|||||||
@ -87,6 +87,11 @@ class MsdApi:
|
|||||||
async def __set_connected_handler(self, req: Request) -> Response:
|
async def __set_connected_handler(self, req: Request) -> Response:
|
||||||
await self.__msd.set_connected(valid_bool(req.query.get("connected")))
|
await self.__msd.set_connected(valid_bool(req.query.get("connected")))
|
||||||
return make_json_response()
|
return make_json_response()
|
||||||
|
|
||||||
|
@exposed_http("POST", "/msd/make_image")
|
||||||
|
async def __set_zipped_handler(self, req: Request) -> Response:
|
||||||
|
await self.__msd.make_image(valid_bool(req.query.get("zipped")))
|
||||||
|
return make_json_response()
|
||||||
|
|
||||||
# =====
|
# =====
|
||||||
|
|
||||||
|
|||||||
@ -37,8 +37,8 @@ class Partition:
|
|||||||
|
|
||||||
|
|
||||||
# =====
|
# =====
|
||||||
def find_msd() -> Partition:
|
def find_msd(msd_directory_path) -> Partition:
|
||||||
return _find_single("otgmsd")
|
return _find_single("otgmsd", msd_directory_path)
|
||||||
|
|
||||||
|
|
||||||
def find_pst() -> Partition:
|
def find_pst() -> Partition:
|
||||||
@ -46,12 +46,12 @@ def find_pst() -> Partition:
|
|||||||
|
|
||||||
|
|
||||||
# =====
|
# =====
|
||||||
def _find_single(part_type: str) -> Partition:
|
def _find_single(part_type: str, msd_directory_path: str) -> Partition:
|
||||||
parts = _find_partitions(part_type, True)
|
parts = _find_partitions(part_type, True)
|
||||||
if len(parts) == 0:
|
if len(parts) == 0:
|
||||||
if os.path.exists('/var/lib/kvmd/msd'):
|
if os.path.exists(msd_directory_path):
|
||||||
#set default value
|
#set default value
|
||||||
parts = [Partition(mount_path='/var/lib/kvmd/msd', root_path='/var/lib/kvmd/msd',group='kvmd', user='kvmd')]
|
parts = [Partition(mount_path = msd_directory_path, root_path = msd_directory_path, group = 'kvmd', user = 'kvmd')]
|
||||||
else:
|
else:
|
||||||
raise RuntimeError(f"Can't find {part_type!r} mountpoint")
|
raise RuntimeError(f"Can't find {part_type!r} mountpoint")
|
||||||
return parts[0]
|
return parts[0]
|
||||||
|
|||||||
@ -156,6 +156,9 @@ class BaseMsd(BasePlugin):
|
|||||||
|
|
||||||
async def set_connected(self, connected: bool) -> None:
|
async def set_connected(self, connected: bool) -> None:
|
||||||
raise NotImplementedError()
|
raise NotImplementedError()
|
||||||
|
|
||||||
|
async def make_image(self, zipped: bool) -> None:
|
||||||
|
raise NotImplementedError()
|
||||||
|
|
||||||
@contextlib.asynccontextmanager
|
@contextlib.asynccontextmanager
|
||||||
async def read_image(self, name: str) -> AsyncGenerator[BaseMsdReader, None]:
|
async def read_image(self, name: str) -> AsyncGenerator[BaseMsdReader, None]:
|
||||||
|
|||||||
@ -27,6 +27,9 @@ import functools
|
|||||||
import time
|
import time
|
||||||
import os
|
import os
|
||||||
import copy
|
import copy
|
||||||
|
import pyfatfs
|
||||||
|
import pyfatfs.PyFat
|
||||||
|
import pyfatfs.PyFatFS
|
||||||
|
|
||||||
from typing import AsyncGenerator
|
from typing import AsyncGenerator
|
||||||
|
|
||||||
@ -37,8 +40,8 @@ from ....inotify import Inotify
|
|||||||
from ....yamlconf import Option
|
from ....yamlconf import Option
|
||||||
|
|
||||||
from ....validators.basic import valid_bool
|
from ....validators.basic import valid_bool
|
||||||
from ....validators.basic import valid_number
|
from ....validators.basic import valid_number, valid_stripped_string_not_empty
|
||||||
from ....validators.os import valid_command
|
from ....validators.os import valid_command, valid_abs_path
|
||||||
from ....validators.kvm import valid_msd_image_name
|
from ....validators.kvm import valid_msd_image_name
|
||||||
|
|
||||||
from .... import aiotools
|
from .... import aiotools
|
||||||
@ -120,23 +123,30 @@ class Plugin(BaseMsd): # pylint: disable=too-many-instance-attributes
|
|||||||
read_chunk_size: int,
|
read_chunk_size: int,
|
||||||
write_chunk_size: int,
|
write_chunk_size: int,
|
||||||
sync_chunk_size: int,
|
sync_chunk_size: int,
|
||||||
|
normalfiles_size: int,
|
||||||
|
|
||||||
remount_cmd: list[str],
|
remount_cmd: list[str],
|
||||||
|
|
||||||
initial: dict,
|
initial: dict,
|
||||||
|
|
||||||
|
normalfiles_path: str,
|
||||||
|
msd_path: str,
|
||||||
|
|
||||||
gadget: str, # XXX: Not from options, see /kvmd/apps/kvmd/__init__.py for details
|
gadget: str, # XXX: Not from options, see /kvmd/apps/kvmd/__init__.py for details
|
||||||
) -> None:
|
) -> None:
|
||||||
|
|
||||||
self.__read_chunk_size = read_chunk_size
|
self.__read_chunk_size = read_chunk_size
|
||||||
self.__write_chunk_size = write_chunk_size
|
self.__write_chunk_size = write_chunk_size
|
||||||
self.__sync_chunk_size = sync_chunk_size
|
self.__sync_chunk_size = sync_chunk_size
|
||||||
|
self.__normalfiles_path = normalfiles_path
|
||||||
|
self.__msd_path = msd_path
|
||||||
|
self.__normalfiles_size = normalfiles_size
|
||||||
|
|
||||||
self.__initial_image: str = initial["image"]
|
self.__initial_image: str = initial["image"]
|
||||||
self.__initial_cdrom: bool = initial["cdrom"]
|
self.__initial_cdrom: bool = initial["cdrom"]
|
||||||
|
|
||||||
self.__drive = Drive(gadget, instance=0, lun=0)
|
self.__drive = Drive(gadget, instance=0, lun=0)
|
||||||
self.__storage = Storage(fstab.find_msd().root_path, remount_cmd)
|
self.__storage = Storage(fstab.find_msd(msd_path).root_path, remount_cmd)
|
||||||
|
|
||||||
self.__reader: (MsdFileReader | None) = None
|
self.__reader: (MsdFileReader | None) = None
|
||||||
self.__writer: (MsdFileWriter | None) = None
|
self.__writer: (MsdFileWriter | None) = None
|
||||||
@ -165,10 +175,15 @@ class Plugin(BaseMsd): # pylint: disable=too-many-instance-attributes
|
|||||||
"image": Option("", type=valid_msd_image_name, if_empty=""),
|
"image": Option("", type=valid_msd_image_name, if_empty=""),
|
||||||
"cdrom": Option(False, type=valid_bool),
|
"cdrom": Option(False, type=valid_bool),
|
||||||
},
|
},
|
||||||
|
"msd_path": Option("/var/lib/kvmd/msd", type=valid_abs_path),
|
||||||
|
"normalfiles_path": Option("NormalFiles", type=valid_stripped_string_not_empty),
|
||||||
|
"normalfiles_size": Option(256, type=functools.partial(valid_number, min=64)),
|
||||||
}
|
}
|
||||||
|
|
||||||
# =====
|
# =====
|
||||||
|
|
||||||
|
# =====
|
||||||
|
|
||||||
async def get_state(self) -> dict:
|
async def get_state(self) -> dict:
|
||||||
async with self.__state._lock: # pylint: disable=protected-access
|
async with self.__state._lock: # pylint: disable=protected-access
|
||||||
storage: (dict | None) = None
|
storage: (dict | None) = None
|
||||||
@ -184,6 +199,7 @@ class Plugin(BaseMsd): # pylint: disable=too-many-instance-attributes
|
|||||||
|
|
||||||
storage["downloading"] = (self.__reader.get_state() if self.__reader else None)
|
storage["downloading"] = (self.__reader.get_state() if self.__reader else None)
|
||||||
storage["uploading"] = (self.__writer.get_state() if self.__writer else None)
|
storage["uploading"] = (self.__writer.get_state() if self.__writer else None)
|
||||||
|
storage["filespath"] = self.__normalfiles_path
|
||||||
|
|
||||||
vd: (dict | None) = None
|
vd: (dict | None) = None
|
||||||
if self.__state.vd:
|
if self.__state.vd:
|
||||||
@ -286,15 +302,19 @@ class Plugin(BaseMsd): # pylint: disable=too-many-instance-attributes
|
|||||||
|
|
||||||
self.__drive.set_rw_flag(self.__state.vd.rw)
|
self.__drive.set_rw_flag(self.__state.vd.rw)
|
||||||
self.__drive.set_cdrom_flag(self.__state.vd.cdrom)
|
self.__drive.set_cdrom_flag(self.__state.vd.cdrom)
|
||||||
#reboot UDC to fix otg cd-rom and flash switch
|
#reset UDC to fix otg cd-rom and flash switch
|
||||||
udc_path = self.__drive.get_udc_path()
|
try:
|
||||||
with open(udc_path) as file:
|
udc_path = self.__drive.get_udc_path()
|
||||||
enabled = bool(file.read().strip())
|
with open(udc_path) as file:
|
||||||
if enabled:
|
enabled = bool(file.read().strip())
|
||||||
|
if enabled:
|
||||||
|
with open(udc_path, "w") as file:
|
||||||
|
file.write("\n")
|
||||||
with open(udc_path, "w") as file:
|
with open(udc_path, "w") as file:
|
||||||
file.write("\n")
|
file.write(sorted(os.listdir("/sys/class/udc"))[0])
|
||||||
with open(udc_path, "w") as file:
|
except:
|
||||||
file.write(sorted(os.listdir("/sys/class/udc"))[0])
|
logger = get_logger(0)
|
||||||
|
logger.error("Can't reset UDC")
|
||||||
if self.__state.vd.rw:
|
if self.__state.vd.rw:
|
||||||
await self.__state.vd.image.remount_rw(True)
|
await self.__state.vd.image.remount_rw(True)
|
||||||
self.__drive.set_image_path(self.__state.vd.image.path)
|
self.__drive.set_image_path(self.__state.vd.image.path)
|
||||||
@ -306,6 +326,86 @@ class Plugin(BaseMsd): # pylint: disable=too-many-instance-attributes
|
|||||||
|
|
||||||
self.__state.vd.connected = connected
|
self.__state.vd.connected = connected
|
||||||
|
|
||||||
|
@aiotools.atomic_fg
|
||||||
|
async def make_image(self, zipped: bool) -> None:
|
||||||
|
#Note: img size >= 64M
|
||||||
|
def create_fat_image(img_size: int, file_img_path: str, source_dir: str, fat_type: int = 32, label: str = 'One-KVM'):
|
||||||
|
def add_directory_to_fat(fat: str, src_path: str, dst_path: str):
|
||||||
|
for item in os.listdir(src_path):
|
||||||
|
src_item_path = os.path.join(src_path, item)
|
||||||
|
dst_item_path = os.path.join(dst_path, item)
|
||||||
|
|
||||||
|
if os.path.isdir(src_item_path):
|
||||||
|
fat.makedir(dst_item_path)
|
||||||
|
add_directory_to_fat(fat, src_item_path, dst_item_path)
|
||||||
|
elif os.path.isfile(src_item_path):
|
||||||
|
with open(src_item_path, 'rb') as src_file:
|
||||||
|
fat.create(dst_item_path)
|
||||||
|
with fat.open(dst_item_path, 'wb') as dst_file:
|
||||||
|
dst_file.write(src_file.read())
|
||||||
|
print(file_img_path)
|
||||||
|
with open(file_img_path, 'wb') as f:
|
||||||
|
f.seek(img_size * 1024 *1024 - 1)
|
||||||
|
f.write(b'\0')
|
||||||
|
fat_file = pyfatfs.PyFat.PyFat()
|
||||||
|
try:
|
||||||
|
fat_file.mkfs(file_img_path, fat_type = fat_type, label = label)
|
||||||
|
except Exception as e:
|
||||||
|
get_logger(0).exception(f"Error making FAT Filesystem: {e}")
|
||||||
|
finally:
|
||||||
|
fat_file.close()
|
||||||
|
fat_handle = pyfatfs.PyFatFS.PyFatFS(file_img_path)
|
||||||
|
try:
|
||||||
|
add_directory_to_fat(fat_handle, source_dir, '/')
|
||||||
|
except Exception as e:
|
||||||
|
get_logger(0).exception(f"Error adding directory to FAT image: {e}")
|
||||||
|
finally:
|
||||||
|
fat_handle.close()
|
||||||
|
|
||||||
|
def extract_fat_image(file_img_path: str, output_dir: str):
|
||||||
|
try:
|
||||||
|
for root, dirs, files in os.walk(output_dir, topdown=False):
|
||||||
|
for name in files:
|
||||||
|
os.remove(os.path.join(root, name))
|
||||||
|
for name in dirs:
|
||||||
|
os.rmdir(os.path.join(root, name))
|
||||||
|
except Exception as e:
|
||||||
|
get_logger(0).exception(f"Error removing normal file or directory: {e}")
|
||||||
|
fat_handle = pyfatfs.PyFatFS.PyFatFS(file_img_path)
|
||||||
|
try:
|
||||||
|
def extract_directory(fat_handle, src_path: str, dst_path: str):
|
||||||
|
for entry in fat_handle.listdir(src_path):
|
||||||
|
src_item_path = os.path.join(src_path, entry)
|
||||||
|
dst_item_path = os.path.join(dst_path, entry)
|
||||||
|
|
||||||
|
if fat_handle.gettype(src_item_path) is pyfatfs.PyFatFS.ResourceType.directory:
|
||||||
|
os.makedirs(dst_item_path, exist_ok=True)
|
||||||
|
extract_directory(fat_handle, src_item_path, dst_item_path)
|
||||||
|
else:
|
||||||
|
with fat_handle.open(src_item_path, 'rb') as src_file:
|
||||||
|
with open(dst_item_path, 'wb') as dst_file:
|
||||||
|
dst_file.write(src_file.read())
|
||||||
|
extract_directory(fat_handle, '/', output_dir)
|
||||||
|
except Exception as e:
|
||||||
|
get_logger(0).exception(f"Error extracting FAT image: {e}")
|
||||||
|
finally:
|
||||||
|
fat_handle.close()
|
||||||
|
|
||||||
|
async with self.__state.busy():
|
||||||
|
msd_path = self.__msd_path
|
||||||
|
file_storage_path = os.path.join(msd_path, self.__normalfiles_path)
|
||||||
|
file_img_path = os.path.join(msd_path, self.__normalfiles_path + ".img")
|
||||||
|
img_size = self.__normalfiles_size
|
||||||
|
if zipped:
|
||||||
|
if not os.path.exists(file_storage_path):
|
||||||
|
os.makedirs(file_storage_path)
|
||||||
|
if os.path.exists(file_img_path):
|
||||||
|
os.remove(file_img_path)
|
||||||
|
create_fat_image(img_size, file_img_path, file_storage_path)
|
||||||
|
else:
|
||||||
|
if os.path.exists(file_img_path):
|
||||||
|
extract_fat_image(file_img_path, file_storage_path)
|
||||||
|
|
||||||
@contextlib.asynccontextmanager
|
@contextlib.asynccontextmanager
|
||||||
async def read_image(self, name: str) -> AsyncGenerator[MsdFileReader, None]:
|
async def read_image(self, name: str) -> AsyncGenerator[MsdFileReader, None]:
|
||||||
try:
|
try:
|
||||||
|
|||||||
@ -6,3 +6,4 @@ pyrad
|
|||||||
types-PyYAML
|
types-PyYAML
|
||||||
types-aiofiles
|
types-aiofiles
|
||||||
luma.oled
|
luma.oled
|
||||||
|
pyfatfs
|
||||||
|
|||||||
@ -13,7 +13,11 @@ kvmd:
|
|||||||
noop: true
|
noop: true
|
||||||
|
|
||||||
msd:
|
msd:
|
||||||
type: disabled
|
type: otg
|
||||||
|
remount_cmd: /bin/true
|
||||||
|
msd_path: /var/lib/kvmd/msd
|
||||||
|
normalfiles_path: NormalFiles
|
||||||
|
normalfiles_size: 64
|
||||||
|
|
||||||
streamer:
|
streamer:
|
||||||
cmd:
|
cmd:
|
||||||
|
|||||||
@ -596,8 +596,18 @@
|
|||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td i18n="kvm_text84">文件内容:</td>
|
||||||
|
<td>
|
||||||
|
<div class="radio-box">
|
||||||
|
<input checked type="radio" id="msd-mode-radio-image" name="file-mode-radio" value="1">
|
||||||
|
<label for="msd-mode-radio-image" i18n="kvm_text90">ImageFiles</label>
|
||||||
|
<input type="radio" id="msd-mode-radio-file" name="file-mode-radio" value="0">
|
||||||
|
<label for="msd-mode-radio-file" i18n="kvm_text91">NormalFiles</label>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
<hr>
|
|
||||||
<div id="msd-storages"></div>
|
<div id="msd-storages"></div>
|
||||||
<hr>
|
<hr>
|
||||||
<div class="buttons buttons-row">
|
<div class="buttons buttons-row">
|
||||||
@ -674,6 +684,14 @@
|
|||||||
<button class="row25" disabled id="msd-disconnect-button" i18n="kvm_text77">Disconnect</button>
|
<button class="row25" disabled id="msd-disconnect-button" i18n="kvm_text77">Disconnect</button>
|
||||||
<button class="row25" disabled id="msd-reset-button" i18n="kvm_text78">Reset</button>
|
<button class="row25" disabled id="msd-reset-button" i18n="kvm_text78">Reset</button>
|
||||||
</div>
|
</div>
|
||||||
|
<hr>
|
||||||
|
<div class="text"><b i18n="kvm_text85">Quick file transfer:</b><br><sub i18n="kvm_text86">• Select NormalFiles tab to upload, package them and mount image</sub><br><sub i18n="kvm_text87">• Disconnect MSD, unpackage it, select tab to download</sub><br></div>
|
||||||
|
<hr>
|
||||||
|
<div class="buttons buttons-row">
|
||||||
|
<button class="row50" id="msd-file-image-update-button" i18n="kvm_text88">Package files into image</button>
|
||||||
|
<button class="row50" id="msd-file-image-unzip-button" i18n="kvm_text89">Unpackage files from image</button>
|
||||||
|
</div>
|
||||||
|
<hr>
|
||||||
</div>
|
</div>
|
||||||
</li>
|
</li>
|
||||||
<li class="right" id="macro-dropdown"><a class="menu-button" href="#"><img class="led-gray" id="hid-recorder-led" src="/share/svg/led-gear.svg"><span i18n="kvm_text32">Macro</span></a>
|
<li class="right" id="macro-dropdown"><a class="menu-button" href="#"><img class="led-gray" id="hid-recorder-led" src="/share/svg/led-gear.svg"><span i18n="kvm_text32">Macro</span></a>
|
||||||
|
|||||||
@ -50,7 +50,14 @@ li(id="msd-dropdown" class="right feature-disabled")
|
|||||||
label(for="msd-mode-radio-flash") Flash
|
label(for="msd-mode-radio-flash") Flash
|
||||||
td
|
td
|
||||||
+menu_switch_notable("msd-rw-switch", "Writable", false, false, "msd-rw-switch")
|
+menu_switch_notable("msd-rw-switch", "Writable", false, false, "msd-rw-switch")
|
||||||
hr
|
tr
|
||||||
|
td(i18n="kvm_text84") 文件内容:
|
||||||
|
td
|
||||||
|
div(class="radio-box")
|
||||||
|
input(checked type="radio" id="msd-mode-radio-image" name="file-mode-radio" value="1")
|
||||||
|
label(for="msd-mode-radio-image" i18n="kvm_text90") ImageFiles
|
||||||
|
input(type="radio" id="msd-mode-radio-file" name="file-mode-radio" value="0")
|
||||||
|
label(for="msd-mode-radio-file" i18n="kvm_text91") NormalFiles
|
||||||
div(id="msd-storages")
|
div(id="msd-storages")
|
||||||
hr
|
hr
|
||||||
div(class="buttons buttons-row")
|
div(class="buttons buttons-row")
|
||||||
@ -98,3 +105,20 @@ li(id="msd-dropdown" class="right feature-disabled")
|
|||||||
button(disabled id="msd-connect-button" class="row50" i18n="kvm_text76") Connect drive to Server
|
button(disabled id="msd-connect-button" class="row50" i18n="kvm_text76") Connect drive to Server
|
||||||
button(disabled id="msd-disconnect-button" class="row25" i18n="kvm_text77") Disconnect
|
button(disabled id="msd-disconnect-button" class="row25" i18n="kvm_text77") Disconnect
|
||||||
button(disabled id="msd-reset-button" class="row25" i18n="kvm_text78") Reset
|
button(disabled id="msd-reset-button" class="row25" i18n="kvm_text78") Reset
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
hr
|
||||||
|
div(class="text")
|
||||||
|
b(i18n="kvm_text85") Quick file transfer:
|
||||||
|
br
|
||||||
|
sub(i18n="kvm_text86") • Select NormalFiles tab to upload, package them and mount image
|
||||||
|
br
|
||||||
|
sub(i18n="kvm_text87") • Disconnect MSD, unpackage it, select tab to download
|
||||||
|
br
|
||||||
|
hr
|
||||||
|
div(class="buttons buttons-row")
|
||||||
|
button(id="msd-file-image-update-button" class="row50" i18n="kvm_text88") Package files into image
|
||||||
|
button(id="msd-file-image-unzip-button" class="row50" i18n="kvm_text89") Unpackage files from image
|
||||||
|
hr
|
||||||
|
|
||||||
@ -114,6 +114,14 @@
|
|||||||
"kvm_text81":"Start recording",
|
"kvm_text81":"Start recording",
|
||||||
"kvm_text82":"End recording",
|
"kvm_text82":"End recording",
|
||||||
"kvm_text83":"Web UI settings",
|
"kvm_text83":"Web UI settings",
|
||||||
|
"kvm_text84":"File display:",
|
||||||
|
"kvm_text85":"Quick file transfer:",
|
||||||
|
"kvm_text86":"• Select NormalFiles tab to upload, package them and mount image",
|
||||||
|
"kvm_text87":"• Disconnect MSD, unpackage it, select tab to download",
|
||||||
|
"kvm_text88":"Package files into image",
|
||||||
|
"kvm_text89":"Unpackage files from image",
|
||||||
|
"kvm_text90":"ImageFiles",
|
||||||
|
"kvm_text91":"NormalFiles",
|
||||||
|
|
||||||
"atx-ask-switch":"Ask click confirmation",
|
"atx-ask-switch":"Ask click confirmation",
|
||||||
"hid-recorder-loop-switch":"Infinite loop playback",
|
"hid-recorder-loop-switch":"Infinite loop playback",
|
||||||
|
|||||||
@ -93,9 +93,9 @@
|
|||||||
|
|
||||||
"kvm_text60":"驱动器",
|
"kvm_text60":"驱动器",
|
||||||
"kvm_text61":"虚拟存储驱动器",
|
"kvm_text61":"虚拟存储驱动器",
|
||||||
"kvm_text62":"镜像:",
|
"kvm_text62":"文件:",
|
||||||
"kvm_text63":"驱动<a target=\"_blank\" href=\"https://docs.pikvm.org/msd\">模式</a>:",
|
"kvm_text63":"驱动<a target=\"_blank\" href=\"https://docs.pikvm.org/msd\">模式</a>:",
|
||||||
"kvm_text64":"选择要上传的镜像",
|
"kvm_text64":"选择要上传的文件",
|
||||||
"kvm_text65":"上传",
|
"kvm_text65":"上传",
|
||||||
"kvm_text66":"中止",
|
"kvm_text66":"中止",
|
||||||
"kvm_text68":"指定本地文件:",
|
"kvm_text68":"指定本地文件:",
|
||||||
@ -114,6 +114,16 @@
|
|||||||
"kvm_text81":"开始录制",
|
"kvm_text81":"开始录制",
|
||||||
"kvm_text82":"结束录制",
|
"kvm_text82":"结束录制",
|
||||||
"kvm_text83":"网页界面设置",
|
"kvm_text83":"网页界面设置",
|
||||||
|
"kvm_text84":"文件显示:",
|
||||||
|
"kvm_text85":"快速文件互传:",
|
||||||
|
"kvm_text86":"• 切换互传文件上传文件,打包生成镜像文件,挂载 NormalFiles 镜像",
|
||||||
|
"kvm_text87":"• 断开 MSD 连接,选择从镜像文件解压,切换互传文件下载所需文件",
|
||||||
|
"kvm_text88":"从互传文件打包镜像文件",
|
||||||
|
"kvm_text89":"从镜像文件解压互传文件",
|
||||||
|
"kvm_text90":"镜像文件",
|
||||||
|
"kvm_text91":"互传文件",
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
"atx-ask-switch":"点击二次确认",
|
"atx-ask-switch":"点击二次确认",
|
||||||
"hid-recorder-loop-switch":"无限循环重放",
|
"hid-recorder-loop-switch":"无限循环重放",
|
||||||
|
|||||||
@ -46,7 +46,8 @@ export function Msd() {
|
|||||||
|
|
||||||
tools.radio.setOnClick("msd-mode-radio", () => __sendParam("cdrom", tools.radio.getValue("msd-mode-radio")));
|
tools.radio.setOnClick("msd-mode-radio", () => __sendParam("cdrom", tools.radio.getValue("msd-mode-radio")));
|
||||||
tools.el.setOnClick($("msd-rw-switch"), () => __sendParam("rw", $("msd-rw-switch").checked));
|
tools.el.setOnClick($("msd-rw-switch"), () => __sendParam("rw", $("msd-rw-switch").checked));
|
||||||
|
tools.radio.setOnClick("file-mode-radio", __refreshFileMode, false);
|
||||||
|
|
||||||
tools.el.setOnClick($("msd-select-new-button"), __toggleSelectSub);
|
tools.el.setOnClick($("msd-select-new-button"), __toggleSelectSub);
|
||||||
$("msd-new-file").onchange = __selectNewFile;
|
$("msd-new-file").onchange = __selectNewFile;
|
||||||
$("msd-new-url").oninput = __selectNewUrl;
|
$("msd-new-url").oninput = __selectNewUrl;
|
||||||
@ -58,6 +59,9 @@ export function Msd() {
|
|||||||
tools.el.setOnClick($("msd-connect-button"), () => __clickConnectButton(true));
|
tools.el.setOnClick($("msd-connect-button"), () => __clickConnectButton(true));
|
||||||
tools.el.setOnClick($("msd-disconnect-button"), () => __clickConnectButton(false));
|
tools.el.setOnClick($("msd-disconnect-button"), () => __clickConnectButton(false));
|
||||||
|
|
||||||
|
tools.el.setOnClick($("msd-file-image-update-button"), () => __clickMakeImageButton(true));
|
||||||
|
tools.el.setOnClick($("msd-file-image-unzip-button"), () => __clickMakeImageButton(false));
|
||||||
|
|
||||||
tools.el.setOnClick($("msd-reset-button"), __clickResetButton);
|
tools.el.setOnClick($("msd-reset-button"), __clickResetButton);
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -97,9 +101,12 @@ export function Msd() {
|
|||||||
if (state.storage.images !== undefined) {
|
if (state.storage.images !== undefined) {
|
||||||
__state.storage.images = state.storage.images;
|
__state.storage.images = state.storage.images;
|
||||||
}
|
}
|
||||||
|
if (state.storage.filespath !== undefined) {
|
||||||
|
__state.storage.filespath = state.storage.filespath;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (state.drive || (state.storage && state.storage.images !== undefined)) {
|
if (state.drive || (state.storage && state.storage.images !== undefined)) {
|
||||||
__updateImageSelector(__state.drive, __state.storage.images);
|
__updateImageSelector(__state.drive, __state.storage.images, __state.storage.filespath);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@ -132,6 +139,7 @@ export function Msd() {
|
|||||||
|
|
||||||
tools.radio.setEnabled("msd-mode-radio", (o && !d.connected && !busy));
|
tools.radio.setEnabled("msd-mode-radio", (o && !d.connected && !busy));
|
||||||
tools.radio.setValue("msd-mode-radio", `${Number(o && d.cdrom)}`);
|
tools.radio.setValue("msd-mode-radio", `${Number(o && d.cdrom)}`);
|
||||||
|
tools.radio.setEnabled("file-mode-radio", (o && !d.connected && !busy));
|
||||||
|
|
||||||
tools.el.setEnabled($("msd-rw-switch"), (o && !d.connected && !busy));
|
tools.el.setEnabled($("msd-rw-switch"), (o && !d.connected && !busy));
|
||||||
$("msd-rw-switch").checked = (o && d.rw);
|
$("msd-rw-switch").checked = (o && d.rw);
|
||||||
@ -174,6 +182,16 @@ export function Msd() {
|
|||||||
}
|
}
|
||||||
$("msd-led").className = led_cls;
|
$("msd-led").className = led_cls;
|
||||||
$("msd-status").innerText = $("msd-led").title = msg;
|
$("msd-status").innerText = $("msd-led").title = msg;
|
||||||
|
|
||||||
|
if (tools.radio.getValue("file-mode-radio") == "0"){
|
||||||
|
tools.el.setEnabled($("msd-connect-button"), false);
|
||||||
|
tools.el.setEnabled($("msd-file-image-update-button"), false);
|
||||||
|
tools.el.setEnabled($("msd-file-image-unzip-button"), false);
|
||||||
|
} else {
|
||||||
|
tools.el.setEnabled($("msd-connect-button"), (o && d.image && !d.connected && !busy));
|
||||||
|
tools.el.setEnabled($("msd-file-image-update-button"), (o && !d.connected && !busy));
|
||||||
|
tools.el.setEnabled($("msd-file-image-unzip-button"), (o && !d.connected && !busy));
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
var __updateUploading = function(uploading) {
|
var __updateUploading = function(uploading) {
|
||||||
@ -227,17 +245,20 @@ export function Msd() {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
var __updateImageSelector = function(drive, images) {
|
var __updateImageSelector = function(drive, images, filespath) {
|
||||||
let sel = "";
|
let sel = "";
|
||||||
let el = $("msd-image-selector");
|
let el = $("msd-image-selector");
|
||||||
|
let fm = tools.radio.getValue("file-mode-radio");
|
||||||
el.options.length = 1;
|
el.options.length = 1;
|
||||||
for (let name of Object.keys(images).sort()) {
|
for (let name of Object.keys(images).sort()) {
|
||||||
tools.selector.addSeparator(el);
|
if ((fm == "0" && name.startsWith(filespath + "/")) || (fm == "1" && !name.startsWith(filespath + "/"))) {
|
||||||
tools.selector.addOption(el, name, name);
|
tools.selector.addSeparator(el);
|
||||||
tools.selector.addComment(el, __makeImageSelectorInfo(images[name]));
|
tools.selector.addOption(el, name, name);
|
||||||
if (drive.image && drive.image.name === name && drive.image.in_storage) {
|
tools.selector.addComment(el, __makeImageSelectorInfo(images[name]));
|
||||||
sel = name;
|
if (drive.image && drive.image.name === name && drive.image.in_storage) {
|
||||||
}
|
sel = name;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (drive.image && !drive.image.in_storage) {
|
if (drive.image && !drive.image.in_storage) {
|
||||||
sel = ".__external__"; // Just some magic name
|
sel = ".__external__"; // Just some magic name
|
||||||
@ -295,10 +316,23 @@ export function Msd() {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
var __refreshFileMode = function() {
|
||||||
|
if (__state.storage.images !== undefined) {
|
||||||
|
__updateImageSelector(__state.drive, __state.storage.images, __state.storage.filespath);
|
||||||
|
}
|
||||||
|
__refreshControls();
|
||||||
|
};
|
||||||
|
|
||||||
var __clickUploadNewButton = function() {
|
var __clickUploadNewButton = function() {
|
||||||
let file = tools.input.getFile($("msd-new-file"));
|
let file = tools.input.getFile($("msd-new-file"));
|
||||||
__http = new XMLHttpRequest();
|
__http = new XMLHttpRequest();
|
||||||
let prefix = encodeURIComponent($("msd-new-part-selector").value);
|
let prefix = ""
|
||||||
|
|
||||||
|
if (tools.radio.getValue("file-mode-radio") == "1"){
|
||||||
|
prefix = encodeURIComponent($("msd-new-part-selector").value);
|
||||||
|
}else{
|
||||||
|
prefix = "NormalFiles";
|
||||||
|
}
|
||||||
if (file) {
|
if (file) {
|
||||||
let image = encodeURIComponent(file.name);
|
let image = encodeURIComponent(file.name);
|
||||||
__http.open("POST", `/api/msd/write?prefix=${prefix}&image=${image}&remove_incomplete=1`, true);
|
__http.open("POST", `/api/msd/write?prefix=${prefix}&image=${image}&remove_incomplete=1`, true);
|
||||||
@ -370,6 +404,21 @@ export function Msd() {
|
|||||||
tools.el.setEnabled($(`msd-${connected ? "connect" : "disconnect"}-button`), false);
|
tools.el.setEnabled($(`msd-${connected ? "connect" : "disconnect"}-button`), false);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
var __clickMakeImageButton = function(zipped) {
|
||||||
|
tools.el.setEnabled($("msd-file-image-update-button"), false);
|
||||||
|
tools.el.setEnabled($("msd-file-image-unzip-button"), false);
|
||||||
|
tools.httpPost("/api/msd/make_image", {"zipped": zipped}, function(http) {
|
||||||
|
if (http.status !== 200) {
|
||||||
|
wm.error("Can't make File Image", http.responseText);
|
||||||
|
}
|
||||||
|
__refreshControls();
|
||||||
|
});
|
||||||
|
__refreshControls();
|
||||||
|
if (__state.storage.images !== undefined) {
|
||||||
|
__updateImageSelector(__state.drive, __state.storage.images, __state.storage.filespath);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
var __clickResetButton = function() {
|
var __clickResetButton = function() {
|
||||||
wm.confirm("Are you sure you want to reset Mass Storage?").then(function(ok) {
|
wm.confirm("Are you sure you want to reset Mass Storage?").then(function(ok) {
|
||||||
if (ok) {
|
if (ok) {
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user