$search
00001 # Software License Agreement (BSD License) 00002 # 00003 # Copyright (c) 2011, Willow Garage, Inc. 00004 # All rights reserved. 00005 # 00006 # Redistribution and use in source and binary forms, with or without 00007 # modification, are permitted provided that the following conditions 00008 # are met: 00009 # 00010 # * Redistributions of source code must retain the above copyright 00011 # notice, this list of conditions and the following disclaimer. 00012 # * Redistributions in binary form must reproduce the above 00013 # copyright notice, this list of conditions and the following 00014 # disclaimer in the documentation and/or other materials provided 00015 # with the distribution. 00016 # * Neither the name of Willow Garage, Inc. nor the names of its 00017 # contributors may be used to endorse or promote products derived 00018 # from this software without specific prior written permission. 00019 # 00020 # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 00021 # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 00022 # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS 00023 # FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE 00024 # COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, 00025 # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 00026 # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 00027 # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 00028 # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 00029 # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN 00030 # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 00031 # POSSIBILITY OF SUCH DAMAGE. 00032 # 00033 # Revision $Id: app.py 14667 2011-08-12 23:55:04Z pratkanis $ 00034 00035 # author: leibs, kwc 00036 00037 import os 00038 import errno 00039 import yaml 00040 00041 import roslib.names 00042 from roslib.packages import InvalidROSPkgException 00043 from .exceptions import AppException, InvalidAppException, NotFoundException, InternalAppException 00044 00045 class Interface(object): 00046 def __init__(self, subscribed_topics, published_topics): 00047 self.subscribed_topics = subscribed_topics 00048 self.published_topics = published_topics 00049 00050 def __eq__(self, other): 00051 if not isinstance(other, Interface): 00052 return False 00053 return self.subscribed_topics == other.subscribed_topics and \ 00054 self.published_topics == other.published_topics 00055 00056 class Client(object): 00057 __slots__ = ['client_type', 'manager_data', 'app_data'] 00058 def __init__(self, client_type, manager_data, app_data): 00059 self.client_type = client_type 00060 self.manager_data = manager_data 00061 self.app_data = app_data 00062 00063 def as_dict(self): 00064 return {'client_type': self.client_type, 'manager_data': self.manager_data, 'app_data': self.app_data} 00065 00066 def __eq__(self, other): 00067 if not isinstance(other, Client): 00068 return False 00069 return self.client_type == other.client_type and \ 00070 self.manager_data == other.manager_data and \ 00071 self.app_data == other.app_data 00072 00073 def __repr__(self): 00074 return yaml.dump(self.as_dict()) 00075 00076 class AppDefinition(object): 00077 __slots__ = ['name', 'display_name', 'description', 'platform', 00078 'launch', 'interface', 'clients', 'icon'] 00079 def __init__(self, name, display_name, description, platform, 00080 launch, interface, clients, icon=None): 00081 self.name = name 00082 self.display_name = display_name 00083 self.description = description 00084 self.platform=platform 00085 self.launch = launch 00086 self.interface = interface 00087 self.clients = clients 00088 self.icon = icon 00089 00090 def __repr__(self): 00091 d = {} 00092 for s in self.__slots__: 00093 if s == 'clients': 00094 d[s] = [c.as_dict() for c in self.clients] 00095 else: 00096 d[s] = getattr(self, s) 00097 return yaml.dump(d) 00098 # return "name: %s\ndisplay: %s\ndescription: %s\nplatform: %s\nlaunch: %s\ninterface: %s\nclients: %s"%(self.name, self.display_name, self.description, self.platform, self.launch, self.interface, self.clients) 00099 00100 def __eq__(self, other): 00101 if not isinstance(other, AppDefinition): 00102 return False 00103 return self.name == other.name and \ 00104 self.display_name == other.display_name and \ 00105 self.description == other.description and \ 00106 self.platform == other.platform and \ 00107 self.launch == other.launch and \ 00108 self.interface == other.interface and \ 00109 self.clients == other.clients and \ 00110 self.icon == other.icon 00111 00112 def find_resource(resource): 00113 """ 00114 @return: filepath of resource. Does not validate if filepath actually exists. 00115 00116 @raise ValueError: if resource is not a valid resource name. 00117 @raise roslib.packages.InvalidROSPkgException: if package referred 00118 to in resource name cannot be found. 00119 @raise NotFoundException: if resource does not exist. 00120 """ 00121 p, a = roslib.names.package_resource_name(resource) 00122 if not p: 00123 raise ValueError("Resource is missing package name: %s"%(resource)) 00124 matches = roslib.packages.find_resource(p, a) 00125 # TODO: convert ValueError to better type for better error messages 00126 if len(matches) == 1: 00127 return matches[0] 00128 elif not matches: 00129 raise NotFoundException("No resource [%s]"%(resource)) 00130 else: 00131 raise ValueError("Multiple resources named [%s]"%(resource)) 00132 00133 def load_Interface_from_file(filename): 00134 """ 00135 @raise IOError: I/O error reading file (e.g. does not exist) 00136 @raise InvalidAppException: if app file is invalid 00137 """ 00138 with open(filename,'r') as f: 00139 y = yaml.load(f.read()) 00140 y = y or {} #coerce to dict 00141 try: 00142 subscribed_topics = y.get('subscribed_topics', {}) 00143 published_topics = y.get('published_topics', {}) 00144 except KeyError: 00145 raise InvalidAppException("Malformed interface, missing keys") 00146 return Interface(published_topics=published_topics, subscribed_topics=subscribed_topics) 00147 00148 def _AppDefinition_load_icon_entry(app_data, appfile="UNKNOWN"): 00149 """ 00150 @raise InvalidAppExcetion: if app definition is invalid. 00151 """ 00152 # load/validate launch entry 00153 try: 00154 icon_resource = app_data.get('icon', '') 00155 if icon_resource == '': 00156 return None 00157 icon_filename = find_resource(icon_resource) 00158 if not icon_filename or not os.path.exists(icon_filename): 00159 return None 00160 return icon_filename 00161 except ValueError as e: 00162 raise InvalidAppException("Malformed appfile [%s]: bad icon entry: %s"%(appfile, e)) 00163 except NotFoundException: 00164 # TODO: make this a soft fail? 00165 raise InvalidAppException("App file [%s] refers to icon that cannot be found"%(appfile)) 00166 except InvalidROSPkgException as e: 00167 raise InvalidAppException("App file [%s] refers to package that is not installed: %s"%(appfile, str(e))) 00168 00169 def _AppDefinition_load_launch_entry(app_data, appfile="UNKNOWN"): 00170 """ 00171 @raise InvalidAppExcetion: if app definition is invalid. 00172 """ 00173 # load/validate launch entry 00174 try: 00175 launch = find_resource(app_data['launch']) 00176 if not os.path.exists(launch): 00177 raise InvalidAppException("Malformed appfile [%s]: refers to launch that does not exist."%(appfile)) 00178 return launch 00179 except ValueError as e: 00180 raise InvalidAppException("Malformed appfile [%s]: bad launch entry: %s"%(appfile, e)) 00181 except NotFoundException: 00182 raise InvalidAppException("App file [%s] refers to launch that is not installed"%(appfile)) 00183 except InvalidROSPkgException as e: 00184 raise InvalidAppException("App file [%s] refers to package that is not installed: %s"%(appfile, str(e))) 00185 00186 def _AppDefinition_load_interface_entry(app_data, appfile="UNKNOWN"): 00187 """ 00188 @raise InvalidAppExcetion: if app definition is invalid. 00189 """ 00190 # load/validate interface entry 00191 try: 00192 return load_Interface_from_file(find_resource(app_data['interface'])) 00193 except IOError as e: 00194 if e.errno == errno.ENOENT: 00195 raise InvalidAppException("Malformed appfile [%s]: refers to interface file that does not exist"%(appfile)) 00196 else: 00197 raise InvalidAppException("Error with appfile [%s]: cannot read interface file"%(appfile)) 00198 except ValueError: 00199 raise InvalidAppException("Malformed appfile [%s]: bad interface entry"%(appfile)) 00200 except InvalidROSPkgException as e: 00201 raise InvalidAppException("App file [%s] refers to package that is not installed: %s"%(appfile, str(e))) 00202 00203 def _AppDefinition_load_clients_entry(app_data, appfile="UNKNOWN"): 00204 """ 00205 @raise InvalidAppExcetion: if app definition is invalid. 00206 """ 00207 clients_data = app_data.get('clients', []) 00208 clients = [] 00209 for c in clients_data: 00210 for reqd in ['type', 'manager']: 00211 if not reqd in c: 00212 raise InvalidAppException("Malformed appfile [%s], missing required key [%s]"%(appfile, reqd)) 00213 client_type = c['type'] 00214 manager_data = c['manager'] 00215 if not type(manager_data) == dict: 00216 raise InvalidAppException("Malformed appfile [%s]: manager data must be a map"%(appfile)) 00217 00218 app_data = c.get('app', {}) 00219 if not type(app_data) == dict: 00220 raise InvalidAppException("Malformed appfile [%s]: app data must be a map"%(appfile)) 00221 00222 clients.append(Client(client_type, manager_data, app_data)) 00223 return clients 00224 00225 def load_AppDefinition_from_file(appfile, appname): 00226 """ 00227 @raise InvalidAppExcetion: if app definition is invalid. 00228 @raise IOError: I/O error reading appfile (e.g. file does not exist). 00229 """ 00230 with open(appfile,'r') as f: 00231 app_data = yaml.load(f.read()) 00232 for reqd in ['launch', 'interface', 'platform']: 00233 if not reqd in app_data: 00234 raise InvalidAppException("Malformed appfile [%s], missing required key [%s]"%(appfile, reqd)) 00235 00236 display_name = app_data.get('display', appname) 00237 description = app_data.get('description', '') 00238 platform = app_data['platform'] 00239 00240 00241 launch = _AppDefinition_load_launch_entry(app_data, appfile) 00242 interface = _AppDefinition_load_interface_entry(app_data, appfile) 00243 clients = _AppDefinition_load_clients_entry(app_data, appfile) 00244 icon = _AppDefinition_load_icon_entry(app_data, appfile) 00245 00246 return AppDefinition(appname, display_name, description, platform, 00247 launch, interface, clients, icon) 00248 00249 def load_AppDefinition_by_name(appname): 00250 """ 00251 @raise InvalidAppExcetion: if app definition is invalid. 00252 @raise NotFoundExcetion: if app definition is not installed. 00253 @raise ValueError: if appname is invalid. 00254 """ 00255 if not appname: 00256 raise ValueError("app name is empty") 00257 00258 try: 00259 appfile = find_resource(appname + '.app') 00260 except InvalidROSPkgException as e: 00261 raise NotFoundException("Cannot locate app file for %s: package is not installed."%(appname)) 00262 00263 try: 00264 return load_AppDefinition_from_file(appfile, appname) 00265 except IOError as e: 00266 if e.errno == errno.ENOENT: 00267 raise NotFoundException("Cannot locate app file for %s."%(appname)) 00268 else: 00269 raise InternalAppException("I/O error loading AppDefinition file: %s."%(e.errno))