latest bacloud cleanup

This commit is contained in:
Eric Froemling 2020-01-17 20:47:35 -08:00
parent 7abe4d8673
commit 1f7f5e49f5
2 changed files with 68 additions and 28 deletions

View File

@ -459,6 +459,7 @@
<w>dynload</w> <w>dynload</w>
<w>eachother</w> <w>eachother</w>
<w>easteregghunt</w> <w>easteregghunt</w>
<w>edcc</w>
<w>editcontroller</w> <w>editcontroller</w>
<w>editgame</w> <w>editgame</w>
<w>editorconfig</w> <w>editorconfig</w>
@ -481,6 +482,7 @@
<w>encerr</w> <w>encerr</w>
<w>endcall</w> <w>endcall</w>
<w>endindex</w> <w>endindex</w>
<w>endmessage</w>
<w>endparen</w> <w>endparen</w>
<w>endtime</w> <w>endtime</w>
<w>ensurepip</w> <w>ensurepip</w>
@ -1171,6 +1173,7 @@
<w>passwd</w> <w>passwd</w>
<w>patcomp</w> <w>patcomp</w>
<w>pathlib</w> <w>pathlib</w>
<w>pathnames</w>
<w>pathstonames</w> <w>pathstonames</w>
<w>patsubst</w> <w>patsubst</w>
<w>pausable</w> <w>pausable</w>

View File

@ -69,27 +69,37 @@ class Response:
"""Response sent from the bacloud server to the client. """Response sent from the bacloud server to the client.
Attributes: Attributes:
message: If present, client should print this message. message: If present, client should print this message before any other
response processing (including error handling) occurs.
error: If present, client should abort with this error message. error: If present, client should abort with this error message.
loadpackage: If present, client should load this package from a loadpackage: If present, client should load this package from a
location on disk (arg1) and push its manifest to a server command location on disk (arg1) and push its manifest to a server command
(arg2) with provided args (arg3). The manifest should be added to (arg2) with provided args (arg3). The manifest should be added to
the args as 'manifest'. arg4 is the index file name whose the args as 'manifest'. arg4, if present, is the index file name whose
contents should be included with the manifest. contents should be included with the manifest.
upload: If present, client should upload the requested files (arg1) uploads: If present, client should upload the requested files (arg1)
from the loaded package to a server command (arg2) with provided from the loaded package to a server command (arg2) with provided
args (arg3). Arg4 and arg5 are a server call and args which should args (arg3). Arg4 and arg5 are a server call and args which should
be called once all file uploads finish. be called once all file uploads finish.
login: If present, a token that should be stored client-side and passed login: If present, a token that should be stored client-side and passed
with subsequent commands. with subsequent commands.
logout: If True, any existing client-side token should be discarded. logout: If True, any existing client-side token should be discarded.
inline_downloads: If present, pathnames mapped to base64 gzipped data to
be written to the client. This should only be used for relatively
small files as they are all included inline as part of the response.
deletes: If present, file paths that should be deleted on the client.
endmessage: If present, a message that should be printed after all other
response processing is done.
""" """
message: Optional[str] = None message: Optional[str] = None
error: Optional[str] = None error: Optional[str] = None
loadpackage: Optional[Tuple[str, str, Dict, str]] = None loadpackage: Optional[Tuple[str, str, Dict, Optional[str]]] = None
upload: Optional[Tuple[List[str], str, Dict, str, Dict]] = None uploads: Optional[Tuple[List[str], str, Dict, str, Dict]] = None
login: Optional[str] = None login: Optional[str] = None
logout: bool = False logout: bool = False
inline_downloads: Optional[Dict[str, str]] = None
deletes: Optional[List[str]] = None
endmessage: Optional[str] = None
class CleanError(Exception): class CleanError(Exception):
@ -165,7 +175,7 @@ class App:
def __init__(self) -> None: def __init__(self) -> None:
self._state = StateData() self._state = StateData()
self._package: Optional[Package] = None # self._package: Optional[Package] = None
self._project_root: Optional[Path] = None self._project_root: Optional[Path] = None
def run(self) -> None: def run(self) -> None:
@ -243,16 +253,16 @@ class App:
output = json.loads(response_raw_2.content.decode()) output = json.loads(response_raw_2.content.decode())
# Create a default Response and fill in only attrs we're aware of. # Create a default Response and fill in only attrs we're aware of.
# (server may send attrs unknown to older clients) # (newer server may send us attrs we're unaware of)
response = Response() response = Response()
for key, val in output.items(): for key, val in output.items():
if hasattr(response, key): if hasattr(response, key):
setattr(response, key, val) setattr(response, key, val)
# Handle common responses (can move these out of here at some point) # Handle a few things inline.
# (so this functionality is available even to recursive commands, etc.)
if response.message is not None: if response.message is not None:
print(response.message) print(response.message, flush=True)
if response.error is not None: if response.error is not None:
raise CleanError(response.error) raise CleanError(response.error)
@ -261,9 +271,10 @@ class App:
def _upload_file(self, filename: str, call: str, args: Dict) -> None: def _upload_file(self, filename: str, call: str, args: Dict) -> None:
print(f'{CLRBLU}Uploading {filename}{CLREND}', flush=True) print(f'{CLRBLU}Uploading {filename}{CLREND}', flush=True)
assert self._package is not None # assert self._package is not None
with tempfile.TemporaryDirectory() as tempdir: with tempfile.TemporaryDirectory() as tempdir:
srcpath = Path(self._package.path, filename) # srcpath = Path(self._package.path, filename)
srcpath = Path(filename)
gzpath = Path(tempdir, 'file.gz') gzpath = Path(tempdir, 'file.gz')
subprocess.run(f'gzip --stdout "{srcpath}" > "{gzpath}"', subprocess.run(f'gzip --stdout "{srcpath}" > "{gzpath}"',
shell=True, shell=True,
@ -284,30 +295,31 @@ class App:
assert isinstance(packagepath, str) assert isinstance(packagepath, str)
assert isinstance(callname, str) assert isinstance(callname, str)
assert isinstance(callargs, dict) assert isinstance(callargs, dict)
assert isinstance(indexfile, str) assert indexfile is None or isinstance(indexfile, str)
self._package = Package.load_from_disk(Path(packagepath)) package = Package.load_from_disk(Path(packagepath))
# Make the remote call they gave us with the package # Make the remote call they gave us with the package
# manifest added in. # manifest added in.
with Path(self._package.path, indexfile).open() as infile: if indexfile is not None:
index = infile.read() with Path(package.path, indexfile).open() as infile:
index = infile.read()
else:
index = ''
callargs['manifest'] = { callargs['manifest'] = {
'index': index, 'index': index,
'files': { 'files': {key: asdict(val)
key: asdict(val) for key, val in package.files.items()}
for key, val in self._package.files.items()
}
} }
return callname, callargs return callname, callargs
def _handle_upload_response( def _handle_uploads(self,
self, response: Response) -> Optional[Tuple[str, Dict]]: response: Response) -> Optional[Tuple[str, Dict]]:
from concurrent.futures import ThreadPoolExecutor from concurrent.futures import ThreadPoolExecutor
assert response.upload is not None assert response.uploads is not None
assert self._package is not None # assert self._package is not None
assert len(response.upload) == 5 assert len(response.uploads) == 5
(filenames, uploadcmd, uploadargs, completecmd, (filenames, uploadcmd, uploadargs, completecmd,
completeargs) = response.upload completeargs) = response.uploads
assert isinstance(filenames, list) assert isinstance(filenames, list)
assert isinstance(uploadcmd, str) assert isinstance(uploadcmd, str)
assert isinstance(uploadargs, dict) assert isinstance(uploadargs, dict)
@ -328,6 +340,23 @@ class App:
# Lastly, run the 'upload complete' command we were passed. # Lastly, run the 'upload complete' command we were passed.
return completecmd, completeargs return completecmd, completeargs
def _handle_inline_downloads(self, response: Response) -> None:
"""Handle inline file data to be saved to the client."""
import base64
import zlib
assert response.inline_downloads is not None
for fname, fdata in response.inline_downloads.items():
data_zipped = base64.b64decode(fdata)
data = zlib.decompress(data_zipped)
with open(fname, 'wb') as outfile:
outfile.write(data)
def _handle_deletes(self, response: Response) -> None:
"""Handle file deletes."""
assert response.deletes is not None
for fname in response.deletes:
os.unlink(fname)
def run_user_command(self, args: List[str]) -> None: def run_user_command(self, args: List[str]) -> None:
"""Run a single user command to completion.""" """Run a single user command to completion."""
@ -339,12 +368,20 @@ class App:
nextcall = None nextcall = None
if response.loadpackage is not None: if response.loadpackage is not None:
nextcall = self._handle_loadpackage_response(response) nextcall = self._handle_loadpackage_response(response)
if response.upload is not None: if response.uploads is not None:
nextcall = self._handle_upload_response(response) nextcall = self._handle_uploads(response)
if response.login is not None: if response.login is not None:
self._state.login_token = response.login self._state.login_token = response.login
if response.logout: if response.logout:
self._state.login_token = None self._state.login_token = None
if response.inline_downloads:
self._handle_inline_downloads(response)
if response.deletes:
self._handle_deletes(response)
# This should always be printed last.
if response.endmessage is not None:
print(response.endmessage, flush=True)
if __name__ == '__main__': if __name__ == '__main__':