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