Skip to content

Commit

Permalink
Don't hardcode file directory (#397)
Browse files Browse the repository at this point in the history
  • Loading branch information
tcamise-gpsw authored Sep 21, 2023
1 parent 6306ae5 commit 19026c9
Show file tree
Hide file tree
Showing 7 changed files with 78 additions and 44 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ and this project adheres to `Semantic Versioning <https://semver.org/spec/v2.0.0
Unreleased
----------
* Fix BLE notifications not being routed correctly
* Don't hardcode media directory. Also append directory to filenames in media list.

0.14.0 (September-13-2022)
--------------------------
Expand Down
51 changes: 34 additions & 17 deletions demos/python/sdk_wireless_camera_control/open_gopro/api/builders.py
Original file line number Diff line number Diff line change
Expand Up @@ -801,25 +801,15 @@ def __init__(

super().__init__(endpoint, identifier, components, arguments, parser, rules)


class HttpGetJsonCommand(HttpCommand):
"""An HTTP command that performs a GET operation and receives JSON as response"""

async def __call__(
self, __communicator__: GoProHttp, rules: list[MessageRules] | None = None, **kwargs: Any
) -> GoProResp:
"""Execute the command by sending it via HTTP
def build_url(self, **kwargs: Any) -> str:
"""Build the URL string from the passed in components and arguments
Args:
__communicator__ (GoProHttp): HTTP communicator
rules (Optional[dict[MessageRules, RuleSignature]], optional): rules to apply when executing this
message. Defaults to None.
**kwargs (Any): arguments to message
**kwargs (Any): additional entries for the dict
Returns:
GoProResp: Response received via HTTP
str: built URL
"""
# Append components
url = self._endpoint
for component in self._components or []:
url += "/" + kwargs.pop(component)
Expand All @@ -835,7 +825,30 @@ async def __call__(
)
):
url += "?" + arg_part
return url


class HttpGetJsonCommand(HttpCommand):
"""An HTTP command that performs a GET operation and receives JSON as response"""

async def __call__(
self,
__communicator__: GoProHttp,
rules: list[MessageRules] | None = None,
**kwargs: Any,
) -> GoProResp:
"""Execute the command by sending it via HTTP
Args:
__communicator__ (GoProHttp): HTTP communicator
rules (Optional[dict[MessageRules, RuleSignature]], optional): rules to apply when executing this
message. Defaults to None.
**kwargs (Any): arguments to message
Returns:
GoProResp: Response received via HTTP
"""
url = self.build_url(**kwargs)
# Send to camera
logger.info(Logger.build_log_tx_str(pretty_print(self._as_dict(**kwargs, endpoint=url))))
response = await __communicator__._http_get(url, self._parser, rules=rules)
Expand All @@ -849,7 +862,11 @@ class HttpGetBinary(HttpCommand):
"""An HTTP command that performs a GET operation and receives a binary stream as response"""

async def __call__( # type: ignore
self, __communicator__: GoProHttp, *, camera_file: str, local_file: Path | None = None
self,
__communicator__: GoProHttp,
*,
camera_file: str,
local_file: Path | None = None,
) -> GoProResp:
"""Execute the command by getting the binary data from the communicator
Expand All @@ -863,8 +880,8 @@ async def __call__( # type: ignore
GoProResp: location on local device that file was written to
"""
# The method that will actually send the command and receive the stream
local_file = local_file or Path(".") / camera_file
url = self._endpoint + "/" + camera_file
local_file = local_file or Path(".") / Path(camera_file).name
url = self.build_url(path=camera_file)
logger.info(
Logger.build_log_tx_str(
pretty_print(self._as_dict(endpoint=url, camera_file=camera_file, local_file=local_file))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,6 @@ async def get_media_metadata(self, *, file: str) -> GoProResp[MediaMetadata]:
Returns:
GoProResp: Media metadata JSON structure
"""
return {"path": f"100GOPRO/{file}"} # type: ignore

@http_get_json_command(
endpoint="gopro/media/list",
Expand Down Expand Up @@ -249,7 +248,6 @@ async def set_date_time(
"dst": int(is_dst),
}

# TODO
@http_get_json_command(endpoint="gopro/camera/get_date_time")
async def get_date_time(self) -> GoProResp[datetime.datetime]:
"""Get the date and time of the camera (Non timezone / DST aware)
Expand Down Expand Up @@ -285,7 +283,7 @@ async def add_file_hilight(
Returns:
GoProResp: command status
"""
return {"path": f"100GOPRO/{file}", "ms": offset or None} # type: ignore
return {"path": file, "ms": offset or None} # type: ignore

@http_get_json_command(
endpoint="gopro/media/hilight/remove",
Expand All @@ -306,7 +304,7 @@ async def remove_file_hilight(
Returns:
GoProResp: command status
"""
return {"path": f"100GOPRO/{file}", "ms": offset} # type: ignore
return {"path": file, "ms": offset} # type: ignore

@http_get_json_command(
endpoint="gopro/webcam/exit",
Expand Down Expand Up @@ -396,7 +394,7 @@ async def wired_usb_control(self, *, control: Params.Toggle) -> GoProResp[None]:
# HTTP GET BINARY COMMANDS
######################################################################################################

@http_get_binary_command(endpoint="gopro/media/gpmf?path=100GOPRO")
@http_get_binary_command(endpoint="gopro/media/gpmf", arguments=["path"])
async def get_gpmf_data(self, *, camera_file: str, local_file: Path | None = None) -> GoProResp[Path]:
"""Get GPMF data for a file.
Expand All @@ -410,7 +408,7 @@ async def get_gpmf_data(self, *, camera_file: str, local_file: Path | None = Non
Path: Path to local_file that output was written to
"""

@http_get_binary_command(endpoint="gopro/media/screennail?path=100GOPRO")
@http_get_binary_command(endpoint="gopro/media/screennail", arguments=["path"])
async def get_screennail__call__(self, *, camera_file: str, local_file: Path | None = None) -> GoProResp[Path]:
"""Get screennail for a file.
Expand All @@ -424,7 +422,7 @@ async def get_screennail__call__(self, *, camera_file: str, local_file: Path | N
Path: Path to local_file that output was written to
"""

@http_get_binary_command(endpoint="gopro/media/thumbnail?path=100GOPRO")
@http_get_binary_command(endpoint="gopro/media/thumbnail", arguments=["path"])
async def get_thumbnail(self, *, camera_file: str, local_file: Path | None = None) -> GoProResp[Path]:
"""Get thumbnail for a file.
Expand All @@ -438,7 +436,7 @@ async def get_thumbnail(self, *, camera_file: str, local_file: Path | None = Non
Path: Path to local_file that output was written to
"""

@http_get_binary_command(endpoint="gopro/media/telemetry?path=100GOPRO")
@http_get_binary_command(endpoint="gopro/media/telemetry", arguments=["path"])
async def get_telemetry(self, *, camera_file: str, local_file: Path | None = None) -> GoProResp[Path]:
"""Download the telemetry data for a camera file and store in a local file.
Expand All @@ -452,7 +450,7 @@ async def get_telemetry(self, *, camera_file: str, local_file: Path | None = Non
Path: Path to local_file that output was written to
"""

@http_get_binary_command(endpoint="videos/DCIM/100GOPRO", identifier="Download File")
@http_get_binary_command(endpoint="videos/DCIM", components=["path"], identifier="Download File")
async def download_file(self, *, camera_file: str, local_file: Path | None = None) -> GoProResp[Path]:
"""Download a video from the camera to a local file.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ async def main(args: argparse.Namespace) -> None:
# The photo is (most likely) the difference between the two sets
photo = media_set_after.difference(media_set_before).pop()
# Download the photo
console.print("Downloading the photo...")
console.print(f"Downloading {photo.filename}...")
await gopro.http_command.download_file(camera_file=photo.filename, local_file=args.output)
console.print(f"Success!! :smiley: File has been downloaded to {args.output}")
except Exception as e: # pylint: disable = broad-except
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,9 @@
from __future__ import annotations

from abc import ABC
from typing import Optional
from typing import Any, Optional

from pydantic import Field, validator
from pydantic import Field, PrivateAttr, validator

from open_gopro import types
from open_gopro.models.bases import CustomBaseModel
Expand Down Expand Up @@ -139,6 +139,16 @@ class MediaList(CustomBaseModel):

identifier: str = Field(alias="id") #: String identifier of this media list
media: list[MediaFileSystem] #: Media filesystem(s)
_files: list[MediaItem] = PrivateAttr(default_factory=list)

def __init__(self, *args: Any, **kwargs: Any) -> None:
super().__init__(*args, **kwargs)
# self.thing = 1
# Modify each file name to use full path
for directory in self.media:
for media in directory.file_system:
media.filename = f"{directory.directory}/{media.filename}"
self._files.append(media)

@property
def files(self) -> list[MediaItem]:
Expand All @@ -147,4 +157,4 @@ def files(self) -> list[MediaItem]:
Returns:
list[MediaItem]: all media items in this media list
"""
return [item for media in self.media for item in media.file_system]
return self._files
Original file line number Diff line number Diff line change
Expand Up @@ -7,39 +7,46 @@

import pytest

from open_gopro.communicator_interface import GoProWifi
from open_gopro.gopro_base import GoProBase

camera_file = "100GOPRO/XXX.mp4"


@pytest.mark.asyncio
async def test_get_with_no_params(mock_wifi_communicator: GoProWifi):
async def test_get_with_no_params(mock_wifi_communicator: GoProBase):
response = await mock_wifi_communicator.http_command.get_media_list()
assert response.url == "gopro/media/list"


@pytest.mark.asyncio
async def test_get_with_params(mock_wifi_communicator: GoProWifi):
async def test_get_with_params(mock_wifi_communicator: GoProBase):
zoom = 99
response = await mock_wifi_communicator.http_command.set_digital_zoom(percent=zoom)
assert response.url == f"gopro/camera/digital_zoom?percent={zoom}"


@pytest.mark.asyncio
async def test_with_multiple_params(mock_wifi_communicator: GoProWifi):
media_file = "XXX.mp4"
offset_ms = 2500
response = await mock_wifi_communicator.http_command.add_file_hilight(file=media_file, offset=offset_ms)
assert response.url == "gopro/media/hilight/file?path=100GOPRO/XXX.mp4&ms=2500"
async def test_get_binary(mock_wifi_communicator: GoProBase):
url, file = await mock_wifi_communicator.http_command.get_gpmf_data(camera_file=camera_file)
assert url == f"gopro/media/gpmf?path={camera_file}"
assert file == Path("XXX.mp4")


@pytest.mark.asyncio
async def test_get_binary_with_component(mock_wifi_communicator: GoProBase):
url, file = await mock_wifi_communicator.http_command.download_file(camera_file=camera_file)
assert url == f"videos/DCIM/{camera_file}"
assert file == Path("XXX.mp4")


@pytest.mark.asyncio
async def test_get_binary(mock_wifi_communicator: GoProWifi):
file = await mock_wifi_communicator.http_command.download_file(
camera_file="test_file", local_file=Path("local_file")
)
assert str(file[1]) == "local_file"
async def test_with_multiple_params(mock_wifi_communicator: GoProBase):
offset_ms = 2500
response = await mock_wifi_communicator.http_command.add_file_hilight(file=camera_file, offset=offset_ms)
assert response.url == f"gopro/media/hilight/file?path={camera_file}&ms=2500"


def test_ensure_no_positional_args(mock_wifi_communicator: GoProWifi):
def test_ensure_no_positional_args(mock_wifi_communicator: GoProBase):
for command in mock_wifi_communicator.http_command.values():
if inspect.getfullargspec(command).args != ["self"]:
logging.error("All arguments to commands must be keyword-only")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,7 @@ def test_media_list():
items = media_list.files
assert len(items) == 12
assert len([item for item in items if isinstance(item, GroupedMediaItem)]) == 2
assert media_list.files[0].filename == "100GOPRO/GX010001.MP4"


VIDEO_METADATA: Final = {
Expand Down

0 comments on commit 19026c9

Please sign in to comment.