| #!/usr/bin/python |
| # Copyright 2011 Google Inc. All Rights Reserved. |
| # |
| # Licensed under the Apache License, Version 2.0 (the "License"); |
| # you may not use this file except in compliance with the License. |
| # You may obtain a copy of the License at |
| # |
| # http://www.apache.org/licenses/LICENSE-2.0 |
| # |
| # Unless required by applicable law or agreed to in writing, software |
| # distributed under the License is distributed on an "AS IS" BASIS, |
| # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| # See the License for the specific language governing permissions and |
| # limitations under the License. |
| |
| """External API for TR-069 support. |
| |
| The classes here represent the ACS (Auto Configuration Server) and CPE |
| (Customer Premises Equipment) endpoints in the TR-069 standard API. You can |
| hand them an hierarchy of tr.core.Exporter and use the |
| TR-069 access methods to manipulate it. |
| |
| This file doesn't implement the XML (SOAP-like) wrapper around the TR-069 |
| API calls; it's just a python version of the API. |
| """ |
| |
| __author__ = 'apenwarr@google.com (Avery Pennarun)' |
| |
| import datetime |
| |
| import tornado.ioloop |
| import tr.core |
| import tr.cwmpbool |
| import tr.cwmptypes |
| import tr.monohelper |
| import download |
| |
| # Time in seconds to refetch the values that are being watched |
| # for notifications. |
| REFRESH_TIMEOUT = 60 |
| |
| # TR69 constants for which notification method to use. |
| PASSIVE_NOTIFY = 1 |
| ACTIVE_NOTIFY = 2 |
| |
| |
| # Tracking of notifications which are particularly expensive to process. |
| # pylint:disable=g-bad-name |
| ExpensiveNotificationsEnable = False |
| ExpensiveNotifications = {} |
| |
| |
| class SetParameterErrors(Exception): |
| """Exceptions which occurred during a SetParameterValues transaction.""" |
| |
| def __init__(self, error_list, msg): |
| super(SetParameterErrors, self).__init__('%s (%s)' % (msg, error_list)) |
| self.error_list = error_list |
| |
| |
| class ParameterNameError(KeyError): |
| """Raised for SetParameterValue/GetParameterNames to nonexistant parameter.""" |
| |
| def __init__(self, parameter, msg): |
| super(ParameterNameError, self).__init__(msg) |
| self.parameter = parameter |
| |
| def __unicode__(self): |
| print 'args: %r' % (self.args,) |
| return "Can't find key: %s" % self.message |
| |
| def __str__(self): |
| return self.__str__() |
| |
| |
| class ParameterTypeError(TypeError): |
| """Raised when a SetParameterValue has the wrong type.""" |
| |
| def __init__(self, parameter, msg): |
| super(ParameterTypeError, self).__init__(msg) |
| self.parameter = parameter |
| |
| |
| class ParameterValueError(ValueError): |
| """Raised when a SetParameterValue has an invalid value.""" |
| |
| def __init__(self, parameter, msg): |
| super(ParameterValueError, self).__init__(msg) |
| self.parameter = parameter |
| |
| |
| class ParameterNotWritableError(AttributeError): |
| """Raised when a SetParameterValue tries to set a read-only parameter.""" |
| |
| def __init__(self, parameter, msg): |
| super(ParameterNotWritableError, self).__init__(msg) |
| self.parameter = parameter |
| |
| |
| class ParameterInternalError(EnvironmentError): |
| """Raised when something else goes wrong with SetParameterValue.""" |
| |
| def __init__(self, parameter, msg): |
| super(ParameterInternalError, self).__init__(msg) |
| self.parameter = parameter |
| |
| |
| class AddObjectsErrors(Exception): |
| """Exceptions which occurred during an AddObjects transaction.""" |
| |
| def __init__(self, error_list, msg): |
| super(AddObjectsErrors, self).__init__('%s (%s)' % (msg, error_list)) |
| self.error_list = error_list |
| |
| |
| class TR069Service(object): |
| """Represents a TR-069 SOAP RPC service.""" |
| |
| def __init__(self): |
| pass |
| |
| def GetRPCMethods(self): |
| """Return a list of callable methods.""" |
| methods = [] |
| for i in sorted(dir(self)): |
| if i[0].isupper(): |
| methods.append(i) |
| return methods |
| |
| |
| class ACS(TR069Service): |
| """Represents a TR-069 ACS (Auto Configuration Server).""" |
| |
| def __init__(self): |
| TR069Service.__init__(self) |
| self.cpe = None |
| |
| def Inform(self, cpe, unused_root, unused_events, unused_max_envelopes, |
| unused_current_time, unused_retry_count, unused_parameter_list): |
| """Called when the CPE first connects to the ACS.""" |
| print 'ACS.Inform' |
| self.cpe = cpe |
| |
| def TransferComplete(self, command_key, fault_struct, |
| start_time, complete_time): |
| """A file transfer requested by the ACS has been completed.""" |
| raise NotImplementedError() |
| |
| def AutonomousTransferComplete(self, |
| announce_url, transfer_url, |
| is_download, file_type, |
| file_size, target_filename, fault_struct, |
| start_time, complete_time): |
| """A file transfer *not* requested by the ACS has been completed.""" |
| raise NotImplementedError() |
| |
| def Kicked(self, command, referer, arg, next_url): |
| """Called whenever the CPE is kicked by the ACS.""" |
| raise NotImplementedError() |
| |
| def RequestDownload(self, file_type, file_type_args): |
| """The CPE wants us to tell it to download something.""" |
| raise NotImplementedError() |
| |
| def DUStateChangeComplete(self, results, command_key): |
| """A requested ChangeDUState has completed.""" |
| raise NotImplementedError() |
| |
| def AutonomousDUStateChangeComplete(self, results): |
| """A DU state change that was not requested by the ACS has completed.""" |
| raise NotImplementedError() |
| |
| |
| class ParameterAttributes(object): |
| """This is a side object that contains all of the attribute settings. |
| |
| TR69 defines the following parameters of which we really only |
| care about 'Notification': |
| Name (is the key) |
| NotificationChange (if set to True allow Notification to change) |
| Notification |
| AccessListChange |
| AccessList |
| |
| AccessList is kept in the value dict just for tr69 compatibility, the |
| only allowed value in the spec is "Subscriber". |
| |
| There is also one timer that fires every minute to check for attributes |
| that have triggered. |
| """ |
| |
| class Attributes(object): |
| """Helper class to hold tr69 parameter attributes.""" |
| |
| def __init__(self): |
| self.current_value = None |
| self.notification = 0 |
| self.access_list = None |
| |
| def __init__(self, root, ioloop): |
| self.ioloop = ioloop |
| self.params = dict() |
| self.root = root |
| self.set_notification_cb = None |
| self.new_value_change_session_cb = None |
| self.timeout = self.ioloop.add_timeout( |
| datetime.timedelta(0, REFRESH_TIMEOUT), self.CheckForTriggers) |
| |
| def ClearParameterAttributes(self, name): |
| """Clear the attributes for a parameter. |
| |
| Args: |
| name: The parameter having its attributes removed. |
| """ |
| if name.endswith('.'): |
| # delete all the child objects that have this name as prefix. |
| children = [x for x in self.params.keys() if x.startswith(name)] |
| for child in children: |
| del self.params[child] |
| name = name[:-1] # clear the '.' off the end. |
| if name in self.params: |
| del self.params[name] |
| |
| def SetParameterAttributes(self, attrs): |
| """Set Attributes for a parameter. |
| |
| The only attributes that are supported are: |
| Notification |
| AccessList. |
| |
| Args: |
| attrs: key/value pairs of attribute names and their values. |
| Returns: |
| None |
| |
| Raises: |
| ValueError: if attrs doesn't contain a name. |
| """ |
| if 'Name' not in attrs: |
| raise ValueError('SetParameterAttributes must have a "Name" attribute.') |
| name = attrs['Name'] |
| |
| if name not in self.params: |
| self.params[name] = ParameterAttributes.Attributes() |
| |
| if ('Notification' in attrs and |
| 'NotificationChange' in attrs and |
| tr.cwmpbool.parse(attrs['NotificationChange'])): |
| self.params[name].notification = int(attrs['Notification']) |
| |
| if ('AccessList' in attrs and |
| 'AccessListChange' in attrs and |
| tr.cwmpbool.parse(attrs['AccessListChange'])): |
| self.params[name].access_list = str(attrs['AccessList']) |
| |
| # Finally store the initial value so changes can be watched for. |
| self.params[name].current_value = self.root.GetExport(name) |
| |
| def GetParameterAttribute(self, name, attr): |
| """Retrieve the given attribute for the parameter name.""" |
| if name not in self.params: |
| self.params[name] = ParameterAttributes.Attributes() |
| |
| if attr == 'Notification': |
| return self.params[name].notification |
| if attr == 'AccessList': |
| return self.params[name].access_list |
| raise ValueError('Attribute %s is not supported.' % attr) |
| |
| def CheckForTriggers(self): |
| """Checks if a notification needs to be sent to the ACS.""" |
| for paramname in self.params: |
| try: |
| start = tr.monohelper.monotime() |
| value = self.root.GetExport(paramname) |
| end = tr.monohelper.monotime() |
| if ExpensiveNotificationsEnable: |
| accumulated = ExpensiveNotifications.get(paramname, 0.0) |
| accumulated += end - start |
| ExpensiveNotifications[paramname] = accumulated |
| except (KeyError, IndexError): |
| # ACS sets notifications for ephemeral objects like Device.Hosts.Host. |
| # The object doesn't exist right now, it clearly has no notifications. |
| continue |
| attrs = self.params[paramname] |
| |
| # This checks that the Notification attribute is set, and if it |
| # is 1 (passive notification) or 2(active notification) sets the |
| # parameter data to inform the ACS with. |
| # And if the Notification is 2 (active) it will start a new session |
| # with the ACS. |
| # It is okay to call the set_notification_parameters_cb multiple |
| # times, the caller will build up a list of all the parameters to |
| # inform with. |
| if ((attrs.notification == PASSIVE_NOTIFY or |
| attrs.notification == ACTIVE_NOTIFY) and |
| value != attrs.current_value and |
| self.set_notification_parameters_cb): |
| self.set_notification_parameters_cb([(paramname, value)]) |
| if (self.new_value_change_session_cb and |
| attrs.notification == ACTIVE_NOTIFY): |
| self.new_value_change_session_cb() |
| attrs.current_value = value |
| self.ioloop.remove_timeout(self.timeout) |
| self.timeout = self.ioloop.add_timeout( |
| datetime.timedelta(0, REFRESH_TIMEOUT), self.CheckForTriggers) |
| |
| |
| class CPE(TR069Service): |
| """Represents a TR-069 CPE (Customer Premises Equipment).""" |
| |
| def __init__(self, root): |
| TR069Service.__init__(self) |
| self._last_parameter_key = '' |
| self.root = root |
| self.download_manager = download.DownloadManager() |
| self.transfer_complete_received_cb = None |
| self.inform_response_received_cb = None |
| self.parameter_attrs = ParameterAttributes( |
| root, tornado.ioloop.IOLoop.instance()) |
| |
| # There's some magic here, functions that start with a capital letter |
| # are assumed to be ACS invokable, for example 'SetParameterAttributes' |
| # so this helper starts with a lower case letter. |
| # pylint:disable=g-bad-name |
| def setCallbacks(self, send_transfer_complete, |
| transfer_complete_received, |
| inform_response_received, |
| set_notification_parameters, |
| new_value_change_session): |
| self.download_manager.send_transfer_complete = send_transfer_complete |
| self.transfer_complete_received_cb = transfer_complete_received |
| self.inform_response_received_cb = inform_response_received |
| self.parameter_attrs.set_notification_parameters_cb = ( |
| set_notification_parameters) |
| self.parameter_attrs.new_value_change_session_cb = new_value_change_session |
| |
| def startup(self): |
| """Handle any initialization after reboot.""" |
| self.download_manager.RestoreDownloads() |
| |
| def _SetParameterKey(self, value): |
| self._last_parameter_key = value |
| |
| def getParameterKey(self): |
| return self._last_parameter_key |
| |
| def _SetParameterValue(self, name, value): |
| """Given a parameter (which can include an object), set its value.""" |
| if name == 'ParameterKey': |
| self._SetParameterKey(value) |
| return None |
| else: |
| return self.root.SetExportParam(name, value) |
| |
| def _ConcludeTransaction(self, objects, do_commit): |
| """Commit or abandon all pending writes. |
| |
| Args: |
| objects: list of dirty objects to commit |
| do_commit: call CommitTransaction if True, else AbandonTransaction |
| |
| Returns: |
| The response code to return to ACS. |
| |
| SetParameterValues is an atomic transaction, all parameters are set or |
| none of them are. We set obj.dirty and call obj.StartTransaction on |
| every object written to. Now we walk back through the dirtied objects |
| to finish the transaction. |
| """ |
| # TODO(dgentry) At some point there will be interdependencies between |
| # objects. We'll need to develop a means to express those dependencies |
| # and walk the dirty objects in a specific order. |
| for obj in objects: |
| assert obj.dirty |
| obj.dirty = False |
| if do_commit: |
| obj.CommitTransaction() |
| else: |
| obj.AbandonTransaction() |
| return 0 # all values changed successfully |
| |
| @staticmethod |
| def _Apply(error_list, fullname, attr_error, func, *args): |
| try: |
| return func(*args) |
| except TypeError as e: |
| error_list.append(ParameterTypeError(parameter=fullname, msg=str(e))) |
| except ValueError as e: |
| error_list.append(ParameterValueError(parameter=fullname, msg=str(e))) |
| except KeyError as e: |
| error_list.append(ParameterNameError(parameter=fullname, msg=str(e))) |
| except AttributeError as e: |
| # AttributeError might mean the attribute is missing, or that it's |
| # read-only. Since we call _Apply() to read the values first, |
| # it means missing in that case. If we get to the part where we start |
| # writing values, it must mean read-only, because it wasn't missing |
| # before. |
| error_list.append(attr_error(parameter=fullname, msg=str(e))) |
| except EnvironmentError as e: |
| error_list.append(ParameterInternalError(parameter=fullname, msg=str(e))) |
| |
| def SetParameterValues(self, parameter_list, parameter_key): |
| """Sets parameters on some objects.""" |
| parameter_list = list(parameter_list) |
| error_list = [] |
| keys = [] |
| values = [] |
| for key, value in parameter_list: |
| if key == 'ParameterKey': |
| pass # parameter_key overrides it anyhow |
| else: |
| keys.append(key) |
| values.append(value) |
| lookup = [] |
| i = 0 |
| try: |
| for l in self.root.LookupAndFixupExports(keys): |
| lookup.append(l) |
| i += 1 |
| except KeyError as e: |
| error_list = [ParameterNameError(keys[i], e.args[0])] |
| raise SetParameterErrors(error_list=error_list, |
| msg='Transaction Errors: %d' % len(error_list)) |
| |
| # phase 1: grab existing values. |
| oldvals = [] |
| for key, (h, param) in zip(keys, lookup): |
| oldvals.append(self._Apply(error_list, key, ParameterNameError, |
| getattr, h.obj, param)) |
| if error_list: |
| # don't need to _ConcludeTransaction since we didn't change anything yet |
| raise SetParameterErrors(error_list=error_list, |
| msg='Transaction Errors: %d' % len(error_list)) |
| |
| # phase 2: try validating new values. |
| # Since not all properties have validators, this won't catch all |
| # possible errors, but the more objects that support validators, the |
| # fewer transactions we'll have to rollback in the future. |
| assert not error_list |
| for key, (h, param), value in zip(keys, lookup, values): |
| self._Apply(error_list, key, ParameterNotWritableError, |
| tr.cwmptypes.tryattr, h.obj, param, value) |
| if error_list: |
| # don't need to _ConcludeTransaction since we didn't change anything yet |
| raise SetParameterErrors(error_list=error_list, |
| msg='Transaction Errors: %d' % len(error_list)) |
| |
| # phase 3: try setting new values. |
| assert not error_list |
| dirty = set() |
| for key, (h, param), value in zip(keys, lookup, values): |
| self._Apply(error_list, key, ParameterNotWritableError, |
| h.SetExportParam, param, value) |
| dirty.add(h.obj) |
| print 'error_list was: %r' % (error_list,) |
| if error_list: |
| # if there were *any* errors, need to undo *all* changes. |
| # First reset all values to their recorded values. (Some of them might |
| # fail, but we can't do anything about that, so throw away the error |
| # list.) |
| for key, (h, param), value, oldval in zip(keys, lookup, values, oldvals): |
| if oldval != value: |
| self._Apply([], key, ParameterNotWritableError, |
| setattr, h.obj, param, oldval) |
| # Now tell the objects themselves to undo the transactions, if they |
| # support it. |
| # TODO(apenwarr): Deprecate per-object transaction support. |
| # Once upon a time, we didn't have high-level transactions (ie. this |
| # function) so objects had to do it themselves, but it was very error |
| # prone. |
| self._ConcludeTransaction(objects=dirty, do_commit=False) |
| raise SetParameterErrors(error_list=error_list, |
| msg='Transaction Errors: %d' % len(error_list)) |
| |
| # if we get here, all went well. |
| assert not error_list |
| self._SetParameterKey(parameter_key) |
| return self._ConcludeTransaction(dirty, True) |
| |
| def _GetParameterValue(self, name): |
| """Given a parameter (which can include an object), return its value.""" |
| if name == 'ParameterKey': |
| return self._last_parameter_key |
| else: |
| return self.root.GetExport(name) |
| |
| def GetParameterValues(self, parameter_names): |
| """Gets parameters from some objects. |
| |
| Args: |
| parameter_names: a list of parameter name strings. |
| Returns: |
| A list of (name, value) tuples. |
| """ |
| result = [] |
| for param in parameter_names: |
| if not param: |
| # tr69 A.3.2.2: empty string indicates top of the name hierarchy. |
| paramlist = self.root.ListExports(None, recursive=False) |
| parameter_names.extend(paramlist) |
| elif param.endswith('.'): |
| paramlist = self.root.ListExports(param[:-1], False) |
| for p in paramlist: |
| parameter_names.append(param + p) |
| else: |
| try: |
| result.append((param, self._GetParameterValue(param))) |
| except KeyError: |
| raise ParameterNameError(parameter=param, msg=param) |
| return result |
| |
| def _JoinParamPath(self, parameter_path, param): |
| if parameter_path: |
| return '.'.join([parameter_path, param]) |
| else: |
| return param |
| |
| def GetParameterNames(self, parameter_path, next_level_only): |
| """Get the names of parameters or objects (possibly recursively).""" |
| orig_path = parameter_path |
| if parameter_path.endswith('.'): |
| parameter_path = parameter_path[:-1] |
| # First look up and find the parameter, if it doesn't exist we |
| # need to raise an exception. Spec: If the fault is caused by an |
| # invalid ParameterPath value, the Invalid Parameter Name fault |
| # code (9005) MUST be used instead of the more general Invalid |
| # Arguments fault code(9003). (An empty parameter_path is valid, |
| # it means the root object) |
| # spec: if ParameterPath were empty, with NextLevel equal true, |
| # the response would list only IternetGatewayDevice. (if the CPE |
| # is an Internet Gateway Device) |
| if parameter_path: |
| try: |
| self.root.GetExport(parameter_path) |
| except KeyError: |
| # ParameterNameError will get changed into the proper tr-69 fault |
| # 9005. |
| # The second parameter is what gets sent back to ACS and has to be |
| # the parameter name. |
| raise ParameterNameError(parameter=orig_path, msg=orig_path) |
| |
| if not next_level_only and orig_path: |
| # tr-69 A.3.2.3 If false, the response MUST contain the Parameter |
| # or object whose name exactly matches the ParameterPath argument... |
| yield orig_path |
| exports = self.root.ListExports(parameter_path, not next_level_only) |
| for param in exports: |
| yield self._JoinParamPath(parameter_path, str(param)) |
| |
| def SetParameterAttributes(self, attrs): |
| """Set attributes (access control, notifications) on some parameters. |
| |
| The 'Name' parameter can either be the name for a single parameter |
| or an object. Alternatively this can be be a partial path name and |
| indicates that all child parameters should have these parameters set. |
| |
| Args: |
| attrs: A dict of parameters to set. |
| """ |
| param = attrs['Name'] |
| if not self.root.SetExportAttrs(param, attrs): |
| # TODO(jnewlin): Handle the case with the partial path. |
| self.parameter_attrs.SetParameterAttributes(attrs) |
| |
| def GetParameterAttributes(self, parameter_names): |
| """Get attributes (access control, notifications) on some parameters.""" |
| raise NotImplementedError() |
| |
| def AddObject(self, object_name, parameter_key): |
| """Create a new object with default parameters.""" |
| assert object_name.endswith('.') |
| (idx, _) = self.root.AddExportObject(object_name[:-1]) |
| self._SetParameterKey(parameter_key) |
| return (idx, 0) # successfully created |
| |
| def X_CATAWAMPUS_ORG_AddObjects(self, objcount_list, parameter_key): |
| """Create several new objects with default parameters. |
| |
| This is not a standard method in TR-069, it's an extension we added. |
| |
| Args: |
| objcount_list: a list of (object_name, count). Each object_name ends |
| in a dot. |
| parameter_key: the tr-069 cookie used to check which commands completed. |
| Returns: |
| A tuple of (objindex_list, status). |
| objindex_list: a list of (object_name, [indexes...]). |
| status: 0 if successful. |
| Raises: |
| AddObjectsErrors: if any errors are encountered while adding objects. |
| """ |
| out = [] |
| error_list = [] |
| for object_name, count in objcount_list: |
| if not object_name.endswith('.'): |
| error_list.append(ParameterNameError( |
| parameter=object_name, |
| msg='Object name must end with "."')) |
| continue |
| if count > 1000: |
| error_list.append(ParameterValueError( |
| parameter=object_name, |
| msg='Tried to insert too many objects at once')) |
| continue |
| idxlist = [] |
| for _ in xrange(int(count)): |
| idxo = self._Apply(error_list, object_name, ParameterNotWritableError, |
| self.root.AddExportObject, object_name[:-1]) |
| if not idxo: |
| break |
| (idx, _) = idxo |
| idxlist.append(idx) |
| out.append((object_name, idxlist)) |
| if error_list: |
| # there are errors to report, which means we can't report the list of |
| # added objects. So we have to undo as many as the adds as we can |
| # before returning the error. |
| for object_name, idxlist in out: |
| for idx in idxlist: |
| self._Apply(error_list, object_name, ParameterNameError, |
| self.root.DeleteExportObject, object_name[:-1], idx) |
| raise AddObjectsErrors(error_list=error_list, |
| msg='AddObjects Errors: %d' % len(error_list)) |
| self._SetParameterKey(parameter_key) |
| return (out, 0) # successfully created |
| |
| def DeleteObject(self, object_name, parameter_key): |
| """Delete an object and its sub-objects/parameters.""" |
| assert object_name.endswith('.') |
| self.parameter_attrs.ClearParameterAttributes(object_name) |
| path = object_name.split('.') |
| self.root.DeleteExportObject('.'.join(path[:-2]), path[-2]) |
| self._SetParameterKey(parameter_key) |
| return 0 # successfully deleted |
| |
| def Download(self, command_key, file_type, url, username, password, |
| file_size, target_filename, delay_seconds, |
| success_url, failure_url): # pylint:disable=unused-argument |
| """Initiate a download immediately or after a delay.""" |
| return self.download_manager.NewDownload( |
| command_key=command_key, |
| file_type=file_type, |
| url=url, |
| username=username, |
| password=password, |
| file_size=file_size, |
| target_filename=target_filename, |
| delay_seconds=delay_seconds) |
| |
| def Reboot(self, command_key): |
| """Reboot the CPE.""" |
| self.download_manager.Reboot(command_key) |
| |
| def GetQueuedTransfers(self): |
| """Retrieve a list of queued file transfers (downloads and uploads).""" |
| return self.download_manager.GetAllQueuedTransfers() |
| |
| def ScheduleInform(self, delay_seconds, command_key): |
| """Request that this CPE call Inform() at some point in the future.""" |
| raise NotImplementedError() |
| |
| def SetVouchers(self, voucher_list): |
| """Set option vouchers (deprecated).""" |
| raise NotImplementedError() |
| |
| def GetOptions(self, option_name): |
| """Get option vouchers (deprecated).""" |
| raise NotImplementedError() |
| |
| def Upload(self, command_key, file_type, url, |
| username, password, delay_seconds): |
| """Initiate a file upload immediately or after a delay.""" |
| raise NotImplementedError() |
| |
| def FactoryReset(self): |
| """Factory reset the CPE.""" |
| raise NotImplementedError() |
| |
| def GetAllQueuedTransfers(self): |
| """Get a list of all uploads/downloads that are still in the queue.""" |
| return self.download_manager.GetAllQueuedTransfers() |
| |
| def ScheduleDownload(self, command_key, file_type, url, |
| username, password, file_size, target_filename, |
| time_window_list): |
| """Schedule a download for some time in the future.""" |
| raise NotImplementedError() |
| |
| def CancelTransfer(self, command_key): |
| """Cancel a scheduled file transfer.""" |
| return self.download_manager.CancelTransfer(command_key) |
| |
| def ChangeDUState(self, operations, command_key): |
| """Trigger an install, update, or uninstall operation.""" |
| raise NotImplementedError() |
| |
| def transferCompleteResponseReceived(self): |
| if self.transfer_complete_received_cb: |
| self.transfer_complete_received_cb() |
| return self.download_manager.TransferCompleteResponseReceived() |
| |
| def informResponseReceived(self): |
| if self.inform_response_received_cb: |
| self.inform_response_received_cb() |