1#!/usr/local/bin/python3.8 2# -*- coding: utf-8 -*- 3# 4# (c) Copyright 2003-2015 HP Development Company, L.P. 5# 6# This program is free software; you can redistribute it and/or modify 7# it under the terms of the GNU General Public License as published by 8# the Free Software Foundation; either version 2 of the License, or 9# (at your option) any later version. 10# 11# This program is distributed in the hope that it will be useful, 12# but WITHOUT ANY WARRANTY; without even the implied warranty of 13# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14# GNU General Public License for more details. 15# 16# You should have received a copy of the GNU General Public License 17# along with this program; if not, write to the Free Software 18# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 19# 20# Author: Don Welch 21# 22# NOTE: This module is safe for 'from g import *' 23# 24 25# Std Lib 26import sys 27import os 28import os.path 29from .sixext import PY3 30from .sixext.moves import configparser 31import locale 32import pwd 33import stat 34import re 35 36# Local 37from .codes import * 38from . import logger 39from . import os_utils 40from .sixext import to_unicode 41if PY3: 42 QString = type("") 43 44 def cmp(a, b): 45 return (a > b) - (a < b) 46 47# System wide logger 48log = logger.Logger('', logger.Logger.LOG_LEVEL_INFO, logger.Logger.LOG_TO_CONSOLE) 49log.set_level('info') 50 51 52MINIMUM_PYQT_MAJOR_VER = 3 53MINIMUM_PYQT_MINOR_VER = 14 54MINIMUM_QT_MAJOR_VER = 3 55MINIMUM_QT_MINOR_VER = 0 56 57 58def to_bool(s, default=False): 59 if isinstance(s, str) and s: 60 if s[0].lower() in ['1', 't', 'y']: 61 return True 62 elif s[0].lower() in ['0', 'f', 'n']: 63 return False 64 elif isinstance(s, bool): 65 return s 66 67 return default 68 69 70# System wide properties 71class Properties(dict): 72 73 def __getattr__(self, attr): 74 if attr in list(self.keys()): 75 return self.__getitem__(attr) 76 else: 77 return "" 78 79 def __setattr__(self, attr, val): 80 self.__setitem__(attr, val) 81 82prop = Properties() 83 84 85 86class ConfigBase(object): 87 def __init__(self, filename): 88 self.filename = filename 89 self.conf = configparser.ConfigParser() 90 self.read() 91 92 93 def get(self, section, key, default=to_unicode('')): 94 try: 95 return self.conf.get(section, key) 96 except (configparser.NoOptionError, configparser.NoSectionError): 97 return default 98 99 100 def set(self, section, key, value): 101 if not self.conf.has_section(section): 102 self.conf.add_section(section) 103 104 self.conf.set(section, key, value) 105 self.write() 106 107 108 def sections(self): 109 return self.conf.sections() 110 111 112 def has_section(self, section): 113 return self.conf.has_section(section) 114 115 116 def options(self, section): 117 return self.conf.options(section) 118 119 keys = options 120 121 def read(self): 122 if self.filename is not None: 123 filename = self.filename 124 if filename.startswith("/root/"): 125 # Don't try opening a file in root's home directory. 126 log.error("attempted to read from '%s'" % self.filename) 127 return 128 try: 129 fp = open(self.filename, "r") 130 try: 131 self.conf.readfp(fp) 132 except configparser.MissingSectionHeaderError: 133 print("") 134 log.error("Found No Section in %s. Please set the http proxy for root and try again." % self.filename) 135 except (configparser.DuplicateOptionError): 136 log.warn("Found Duplicate Entery in %s" % self.filename) 137 self.CheckDuplicateEntries() 138 finally: 139 fp.close() 140 except (OSError, IOError, configparser.MissingSectionHeaderError): 141 log.debug("Unable to open file %s for reading." % self.filename) 142 143 def write(self): 144 if self.filename is not None: 145 filename = self.filename 146 if filename.startswith("/root/") or filename.startswith("/etc/"): 147 # Don't try writing a file in root's home directory or 148 # the system-wide config file. 149 # See bug #479178. 150 log.error("attempted to write to '%s'" % self.filename) 151 return 152 153 try: 154 fp = open(self.filename, "w") 155 self.conf.write(fp) 156 fp.close() 157 except (OSError, IOError): 158 log.debug("Unable to open file %s for writing." % self.filename) 159 160 def CheckDuplicateEntries(self): 161 try: 162 f = open(self.filename,'r') 163 data = f.read() 164 f.close() 165 except IOError: 166 data ="" 167 168 final_data ='' 169 for a in data.splitlines(): 170 if not a or a not in final_data: 171 final_data = final_data +'\n' +a 172 173 import tempfile 174 fd, self.filename = tempfile.mkstemp() 175 f = open(self.filename,'w') 176 f.write(final_data) 177 f.close() 178 179 self.read() 180 os.unlink(self.filename) 181 182 183class SysConfig(ConfigBase): 184 def __init__(self): 185 ConfigBase.__init__(self, '/usr/local/etc/hp/hplip.conf') 186 187 188class State(ConfigBase): 189 def __init__(self): 190 if not os.path.exists('/var/lib/hp/') and os.geteuid() == 0: 191 os.makedirs('/var/lib/hp/') 192 cmd = 'chmod 755 /var/lib/hp/' 193 os_utils.execute(cmd) 194 ConfigBase.__init__(self, '/var/lib/hp/hplip.state') 195 196 197class UserConfig(ConfigBase): 198 def __init__(self): 199 200 sts, prop.user_dir = os_utils.getHPLIPDir() 201 202 if not os.geteuid() == 0: 203 prop.user_config_file = os.path.join(prop.user_dir, 'hplip.conf') 204 205 if not os.path.exists(prop.user_config_file): 206 try: 207 open(prop.user_config_file, 'w').close() 208 s = os.stat(os.path.dirname(prop.user_config_file)) 209 os.chown(prop.user_config_file, s[stat.ST_UID], s[stat.ST_GID]) 210 except IOError: 211 pass 212 213 ConfigBase.__init__(self, prop.user_config_file) 214 215 else: 216 # If running as root, conf file is None 217 prop.user_config_file = None 218 ConfigBase.__init__(self, None) 219 220 221 def workingDirectory(self): 222 t = self.get('last_used', 'working_dir', os.path.expanduser("~")) 223 try: 224 t = t.decode('utf-8') 225 except UnicodeError: 226 log.error("Invalid unicode: %s" % t) 227 log.debug("working directory: %s" % t) 228 return t 229 230 231 def setWorkingDirectory(self, t): 232 self.set('last_used', 'working_dir', t.encode('utf-8')) 233 log.debug("working directory: %s" % t.encode('utf-8')) 234 235 236 237os.umask(0o037) 238 239# System Config File: Directories and build settings. Not altered after installation. 240sys_conf = SysConfig() 241 242# System State File: System-wide runtime settings 243sys_state = State() 244 245# Per-user Settings File: (Note: For Qt4 code, limit the use of this to non-GUI apps. only) 246user_conf = UserConfig() 247 248 249# Language settings 250try: 251 prop.locale, prop.encoding = locale.getdefaultlocale() 252except ValueError: 253 prop.locale = 'en_US' 254 prop.encoding = 'UTF8' 255 256prop.version = sys_conf.get('hplip', 'version', '0.0.0') # e.g., 3.9.2b.10 257_p, _x = re.compile(r'(\d\w*)', re.I), [] 258for _y in prop.version.split('.')[:3]: 259 _z = _p.match(_y) 260 if _z is not None: 261 _x.append(_z.group(1)) 262 263prop.installed_version = '.'.join(_x) # e.g., '3.9.2' 264try: 265 prop.installed_version_int = int(''.join(['%02x' % int(_y) for _y in _x]), 16) # e.g., 0x030902 -> 198914 266except ValueError: 267 prop.installed_version_int = 0 268 269prop.home_dir = sys_conf.get('dirs', 'home', os.path.realpath(os.path.normpath(os.getcwd()))) 270prop.username = pwd.getpwuid(os.getuid())[0] 271pdb = pwd.getpwnam(prop.username) 272prop.userhome = pdb[5] 273 274prop.history_size = 50 275 276prop.data_dir = os.path.join(prop.home_dir, 'data') 277prop.image_dir = os.path.join(prop.home_dir, 'data', 'images') 278prop.xml_dir = os.path.join(prop.home_dir, 'data', 'xml') 279prop.models_dir = os.path.join(prop.home_dir, 'data', 'models') 280prop.localization_dir = os.path.join(prop.home_dir, 'data', 'localization') 281 282prop.max_message_len = 8192 283prop.max_message_read = 65536 284prop.read_timeout = 90 285 286prop.ppd_search_path = '/usr/local/share;/usr/local/share;/usr/lib;/usr/local/lib;/usr/libexec;/opt;/usr/lib64' 287prop.ppd_search_pattern = 'HP-*.ppd.*' 288prop.ppd_download_url = 'http://www.linuxprinting.org/ppd-o-matic.cgi' 289prop.ppd_file_suffix = '-hpijs.ppd' 290 291# Build and install configurations 292prop.gui_build = to_bool(sys_conf.get('configure', 'gui-build', '0')) 293prop.net_build = to_bool(sys_conf.get('configure', 'network-build', '0')) 294prop.par_build = to_bool(sys_conf.get('configure', 'pp-build', '0')) 295prop.usb_build = True 296prop.scan_build = to_bool(sys_conf.get('configure', 'scanner-build', '0')) 297prop.fax_build = to_bool(sys_conf.get('configure', 'fax-build', '0')) 298prop.doc_build = to_bool(sys_conf.get('configure', 'doc-build', '0')) 299prop.foomatic_xml_install = to_bool(sys_conf.get('configure', 'foomatic-xml-install', '0')) 300prop.foomatic_ppd_install = to_bool(sys_conf.get('configure', 'foomatic-ppd-install', '0')) 301prop.hpcups_build = to_bool(sys_conf.get('configure', 'hpcups-install', '0')) 302prop.hpijs_build = to_bool(sys_conf.get('configure', 'hpijs-install', '0')) 303 304# Spinner, ala Gentoo Portage 305spinner = "\|/-\|/-" 306spinpos = 0 307enable_spinner = True 308 309def change_spinner_state(enable =True): 310 global enable_spinner 311 enable_spinner = enable 312 313def update_spinner(): 314 global spinner, spinpos, enable_spinner 315 if enable_spinner and not log.is_debug() and sys.stdout.isatty(): 316 sys.stdout.write("\b" + spinner[spinpos]) 317 spinpos=(spinpos + 1) % 8 318 sys.stdout.flush() 319 320def cleanup_spinner(): 321 global enable_spinner 322 if enable_spinner and not log.is_debug() and sys.stdout.isatty(): 323 sys.stdout.write("\b \b") 324 sys.stdout.flush() 325 326# Convert string to int and return a list. 327def xint(ver): 328 try: 329 l = [int(x) for x in ver.split('.')] 330 except: 331 pass 332 return l 333 334# In case of import failure of extension modules, check whether its a mixed python environment issue. 335def check_extension_module_env(ext_mod): 336 337 flag = 0 338 ext_mod_so = ext_mod + '.so' 339 340 python_ver = xint((sys.version).split(' ')[0]) #find the current python version ; xint() to convert string to int, returns a list 341 if python_ver[0] == 3 : 342 python_ver = 3 343 else : 344 python_ver = 2 345 346 for dirpath, dirname, filenames in os.walk('/usr/lib/'): #find the .so path 347 if ext_mod_so in filenames: 348 ext_path = dirpath 349 flag = 1 350 351 if flag == 0: 352 log.error('%s not present in the system. Please re-install HPLIP.' %ext_mod) 353 sys.exit(1) 354 355 m = re.search('python(\d(\.\d){0,2})', ext_path) #get the python version where the .so file is found 356 ext_ver = xint(m.group(1)) 357 358 if ext_ver[0] == 3: 359 ver = 3 360 else: 361 ver = 2 362 363 if python_ver != ver : #compare the python version and the version where .so files are present 364 log.error("%s Extension module is missing from Python's path." %ext_mod) 365 log.info("To fix this issue, please refer to this 'http://hplipopensource.com/node/372'") 366 sys.exit(1) 367 368# Internal/messaging errors 369 370ERROR_STRINGS = { 371 ERROR_SUCCESS : 'No error', 372 ERROR_UNKNOWN_ERROR : 'Unknown error', 373 ERROR_DEVICE_NOT_FOUND : 'Device not found', 374 ERROR_INVALID_DEVICE_ID : 'Unknown/invalid device-id field', 375 ERROR_INVALID_DEVICE_URI : 'Unknown/invalid device-uri field', 376 ERROR_DATA_LENGTH_EXCEEDS_MAX : 'Data length exceeds maximum', 377 ERROR_DEVICE_IO_ERROR : 'Device I/O error', 378 ERROR_NO_PROBED_DEVICES_FOUND : 'No probed devices found', 379 ERROR_DEVICE_BUSY : 'Device busy', 380 ERROR_DEVICE_STATUS_NOT_AVAILABLE : 'DeviceStatus not available', 381 ERROR_INVALID_SERVICE_NAME : 'Invalid service name', 382 ERROR_ERROR_INVALID_CHANNEL_ID : 'Invalid channel-id (service name)', 383 ERROR_CHANNEL_BUSY : 'Channel busy', 384 ERROR_DEVICE_DOES_NOT_SUPPORT_OPERATION : 'Device does not support operation', 385 ERROR_DEVICEOPEN_FAILED : 'Device open failed', 386 ERROR_INVALID_DEVNODE : 'Invalid device node', 387 ERROR_INVALID_HOSTNAME : "Invalid hostname ip address", 388 ERROR_INVALID_PORT_NUMBER : "Invalid JetDirect port number", 389 ERROR_NO_CUPS_QUEUE_FOUND_FOR_DEVICE : "No CUPS queue found for device.", 390 ERROR_DATFILE_ERROR: "DAT file error", 391 ERROR_INVALID_TIMEOUT: "Invalid timeout", 392 ERROR_IO_TIMEOUT: "I/O timeout", 393 ERROR_FAX_INCOMPATIBLE_OPTIONS: "Incompatible fax options", 394 ERROR_FAX_INVALID_FAX_FILE: "Invalid fax file", 395 ERROR_FAX_FILE_NOT_FOUND: "Fax file not found", 396 ERROR_INTERNAL : 'Unknown internal error', 397 } 398 399 400class Error(Exception): 401 def __init__(self, opt=ERROR_INTERNAL): 402 self.opt = opt 403 self.msg = ERROR_STRINGS.get(opt, ERROR_STRINGS[ERROR_INTERNAL]) 404 log.debug("Exception: %d (%s)" % (opt, self.msg)) 405 Exception.__init__(self, self.msg, opt) 406 407 408# Make sure True and False are avail. in pre-2.2 versions 409#try: 410# True 411#except NameError: 412# True = (1==1) 413# False = not True 414 415# as new translations are completed, add them here 416supported_locales = { 'en_US': ('us', 'en', 'en_us', 'american', 'america', 'usa', 'english'),} 417# Localization support was disabled in 3.9.2 418 #'zh_CN': ('zh', 'cn', 'zh_cn' , 'china', 'chinese', 'prc'), 419 #'de_DE': ('de', 'de_de', 'german', 'deutsche'), 420 #'fr_FR': ('fr', 'fr_fr', 'france', 'french', 'français'), 421 #'it_IT': ('it', 'it_it', 'italy', 'italian', 'italiano'), 422 #'ru_RU': ('ru', 'ru_ru', 'russian'), 423 #'pt_BR': ('pt', 'br', 'pt_br', 'brazil', 'brazilian', 'portuguese', 'brasil', 'portuguesa'), 424 #'es_MX': ('es', 'mx', 'es_mx', 'mexico', 'spain', 'spanish', 'espanol', 'español'), 425 #} 426 427 428