Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
58 changes: 47 additions & 11 deletions ctfcli/cli/media.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@

from ctfcli.core.api import API
from ctfcli.core.config import Config
from ctfcli.core.media import Media
from ctfcli.utils.hashing import hash_file


class MediaCommand:
Expand All @@ -17,23 +19,57 @@ def add(self, path):

filename = os.path.basename(path)
location = f"media/{filename}"
file_payload = {
"type": "page",
"location": location,
}

with open(path, mode="rb") as file_handle:
# Specifically use data= here to send multipart/form-data
r = api.post("/api/v1/files", files={"file": (filename, file_handle)}, data=file_payload)
r.raise_for_status()
resp = r.json()
server_location = resp["data"][0]["location"]
server_location = Media.upload(api, path, location)

config.config.set("media", location, f"/files/{server_location}")

with open(config.config_path, "w+") as f:
config.write(f)

def sync(self):
"""Sync all local media files in the media folder to the remote instance"""
config = Config()
if config.config.has_section("media") is False:
config.config.add_section("media")

media_path = config.project_path / "media"
if not media_path.is_dir():
click.secho(f"Could not locate media folder '{media_path}'", fg="red")
return 1

api = API()

r = api.get("/api/v1/files?type=page")
r.raise_for_status()
remote_files = {f"/files/{f['location']}": f for f in r.json()["data"]}

for local_file in sorted(media_path.rglob("*")):
if not local_file.is_file() or local_file.name.startswith("."):
continue

location = f"media/{local_file.relative_to(media_path).as_posix()}"
remote_file = remote_files.get(config["media"].get(location))

if remote_file:
with open(local_file, mode="rb") as file_handle:
local_sha1sum = hash_file(file_handle)

if remote_file.get("sha1sum") == local_sha1sum:
click.secho(f"Skipping unchanged media '{location}'", fg="yellow")
continue

r = api.delete(f"/api/v1/files/{remote_file['id']}")
r.raise_for_status()

server_location = Media.upload(api, local_file, location)
config.config.set("media", location, f"/files/{server_location}")
click.secho(f"Synced media '{location}'", fg="green")

with open(config.config_path, "w+") as f:
config.write(f)

return 0

def rm(self, path):
"""Remove local media file from remote server and local config"""
config = Config()
Expand Down
16 changes: 16 additions & 0 deletions ctfcli/core/media.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,25 @@
import os

from ctfcli.core.config import Config
from ctfcli.core.exceptions import ProjectNotInitialized
from ctfcli.utils.tools import safe_format


class Media:
@staticmethod
def upload(api, path, location: str) -> str:
file_payload = {
"type": "page",
"location": location,
}

with open(path, mode="rb") as file_handle:
# Specifically use data= here to send multipart/form-data
r = api.post("/api/v1/files", files={"file": (os.path.basename(path), file_handle)}, data=file_payload)
r.raise_for_status()
resp = r.json()
return resp["data"][0]["location"]

@staticmethod
def replace_placeholders(content: str) -> str:
config = Config()
Expand Down
Loading