# Copyright (c) 2025 Kim Jarvis TPF Software Services S.A. kim.jarvis@tpfsystems.com # This software is licensed under the MIT License. See the LICENSE file for details.#importasyncsshfromreemote.commandimportCommandfromtypingimportUnion,List,Optionalimportos
[docs]classDownload:""" A class to handle secure builtin downloads from remote hosts using SCP (Secure Copy Protocol). This class provides functionality to download builtin or directories from one or more remote hosts to a local destination path. It supports various SCP options including preserving builtin attributes and recursive directory downloads. Attributes: srcpaths (Union[str, List[str]]): Source builtin or directory path(s) on remote host(s). Can be a single string path or a list of paths. Supports host:path format. dstpath (str): Local destination path where builtin will be downloaded. preserve (bool): If True, preserves builtin modification times, access times, and modes from the original builtin. Defaults to False. recurse (bool): If True, recursively copies entire directories. Defaults to False. block_size (int): Block size used for builtin transfers in bytes. Defaults to 16384. port (int): SSH port to use for connections. Defaults to 22. **Examples:** .. code:: python yield Download( srcpaths='/home/user/*.txt', # Remove the host: prefix dstpath='/home/kim/', recurse=True ) Note: This class requires proper SSH credentials to be configured for the target hosts. The actual download operation is executed asynchronously through the Operation class. """def__init__(self,srcpaths:Union[str,List[str]],dstpath:str,preserve:bool=False,recurse:bool=False,block_size:int=16384,port:int=22):self.srcpaths=srcpathsself.dstpath=dstpathself.preserve=preserveself.recurse=recurseself.block_size=block_sizeself.port=portdef__repr__(self):returnf"Download(srcpaths={self.srcpaths!r}, dstpath={self.dstpath!r}, preserve={self.preserve}, recurse={self.recurse})"@staticmethodasyncdef_download_callback(host_info,global_info,command,cp,caller):"""Static callback method for builtin download"""# Validate host_info (matching Upload error handling)required_keys=['host','username','password']forkeyinrequired_keys:ifkeynotinhost_infoorhost_info[key]isNone:raiseValueError(f"Missing or invalid value for '{key}' in host_info.")# Validate caller attributes (matching Upload error handling)ifcaller.srcpathsisNone:raiseValueError("The 'srcpaths' attribute of the caller cannot be None.")ifcaller.dstpathisNone:raiseValueError("The 'dstpath' attribute of the caller cannot be None.")try:# Create proper connection parametersconnect_kwargs={'host':host_info['host'],'username':host_info.get('username'),'password':host_info.get('password'),'client_keys':host_info.get('client_keys'),'known_hosts':host_info.get('known_hosts')}# Remove None valuesconnect_kwargs={k:vfork,vinconnect_kwargs.items()ifvisnotNone}# Set port if specified and different from defaultifcaller.port!=22:connect_kwargs['port']=caller.port# Connect to the SSH serverasyncwithasyncssh.connect(**connect_kwargs)asconn:# Handle source paths - remove any host prefix since we're already connectedifisinstance(caller.srcpaths,list):srcpaths=[]forsrcpathincaller.srcpaths:# Remove host: prefix if presentif':'insrcpath:# Extract path after host: partsrcpath=srcpath.split(':',1)[1]srcpaths.append(srcpath)else:if':'incaller.srcpaths:# Extract path after host: partsrcpaths=caller.srcpaths.split(':',1)[1]else:srcpaths=caller.srcpaths# Perform the SCP downloadawaitasyncssh.scp((conn,srcpaths),# Use connection object instead of host stringcaller.dstpath,preserve=caller.preserve,recurse=caller.recurse,block_size=caller.block_size)except(OSError,asyncssh.Error)asexc:raisedefexecute(self):r=yieldCommand(f"{self}",local=True,callback=self._download_callback,caller=self)r.executed=Truer.changed=Truereturnr