99
1010@dataclass
1111class Backend :
12+ """Interface for interacting directly with the server backend.
13+
14+ Arguments:
15+ public: The host name of the public server.
16+ private: The host name of the private server.
17+ static: The host name of the static server.
18+ shortener: The host name of the link shortener server.
19+ """
20+
1221 public : str = "artsandculture.google.com"
1322 private : str = "cilex-aeiopera.uc.r.appspot.com"
1423 static : str = "gacembed.withgoogle.com"
1524 shortener : str = "g.co"
1625
1726 def shorten (self , link : str ) -> str :
18- """Shorten a link with the internal service."""
27+ """Shorten a link with the internal shortener service.
28+
29+ Arguments:
30+ link: The link to shorten.
31+
32+ Returns:
33+ A shortened link from g.co
34+
35+ Raises:
36+ KeyError: If the shortener did not reply with a link.
37+ """
1938 address = f"https://{ self .public } /api/shortUrl"
2039 response = requests .get (address , params = {"destUrl" : link })
21- return re .search (r'.*"(https?://.+?)".*' , response .text ).group (1 )
40+ # We can't parse the response as JSON because it includes garbage.
41+ if match := re .search (r'.*"(https?://.+?)".*' , response .text ):
42+ return match .group (1 )
43+ else :
44+ raise KeyError ("no link found" )
2245
2346 def link (self , identifier : str ) -> str :
24- """Generate a link for a given recording identifier."""
47+ """Generate a link for the given recording identifier.
48+
49+ Arguments:
50+ identifier: The recording identifier in Base64.
51+
52+ Returns:
53+ A long link pointing to the recording on the main Blob Opera page.
54+ """
55+ # Generate the indentifier bytes.
2556 data = f'{{"r":"{ identifier } "}}' .encode ()
26- # Encode the result with a custom Base64 URL-safe extended variant
57+ # Encode the result with a custom Base64 URL-safe extended variant.
2758 code = base64 .urlsafe_b64encode (data ).decode ().replace ("=" , "." )
28- # Return the link with the base prefix and the calculated identifier
59+ # Return the link with the base prefix and the calculated identifier.
2960 address = f"https://{ self .public } /experiment/blob-opera/AAHWrq360NcGbw"
3061 return f"{ address } ?cp={ code } "
3162
3263 def upload (self , recording : bytes ) -> str :
33- """Upload a recording to the server and return its identifier."""
64+ """Upload the given recording to the server and return its identifier.
65+
66+ Arguments:
67+ recording: The recording, serialized with its protocol buffer.
68+
69+ Returns:
70+ A recording identifier.
71+
72+ Raises:
73+ ValueError: If the uploaded recording was rejected by the server.
74+ """
3475
3576 address = f"https://{ self .private } /recording"
3677 response = requests .put (address , data = recording )
@@ -41,17 +82,35 @@ def upload(self, recording: bytes) -> str:
4182 raise ValueError ("invalid recording" )
4283
4384 def download (self , handle : str ) -> bytes :
44- """Download a recording from the server and return its contents."""
85+ """Download a recording from the server and return its contents.
86+
87+ Arguments:
88+ handle: The recording handle, be it a short link, a long link or
89+ a recording identifier.
90+
91+ Returns:
92+ A raw protocol buffer message with the recording.
93+
94+ Raises:
95+ KeyError: If the recording was not found on the server.
96+ """
4597 try :
98+ # If it's a short link, try to resolve the long link.
4699 if handle .startswith (f"https://{ self .shortener } " ):
47100 handle = requests .get (handle ).url
101+
102+ # If it's a long link, try to retrieve the identifier.
48103 if handle .startswith (f"https://{ self .public } " ):
49- code , * _ = urllib .parse .parse_qs (
50- urllib .parse .urlparse (handle ).query
51- )["cp" ]
104+ # Extract the query string from the address.
105+ query_string = urllib .parse .urlparse (handle ).query
106+ # Extract the ``cp`` parameter from the query string.
107+ code , * _ = urllib .parse .parse_qs (query_string )["cp" ]
108+ # Decode the ``cp`` parameter with the custom url-safe Base64.
52109 raw = base64 .urlsafe_b64decode (code .replace ("." , "=" ))
110+ # Extract the recording identifier.
53111 handle = json .loads (raw )["r" ]
54112
113+ # Fetch the recording and return the raw protocol buffer.
55114 address = f"https://{ self .private } /recording/{ handle } "
56115 file = requests .get (address ).json ()["url" ]
57116 return requests .get (file ).content
0 commit comments