Resolved quite a few file-system encoding/decoding related cases.

In order to resolve the non-ascii characters in path (in user directory,
storage path, etc) on windows, we have converted the path into the
short-path, so that - we don't need to deal with the encoding issues
(specially with Python 2).

We've resolved majority of the issues with this patch.
We still need couple issues to resolve after this in the same area.

TODO
* Add better support for non-ascii characters in the database name on
  windows with Python 3
* Improve the messages created after the background processes by
  different modules (such as Backup, Restore, Import/Export, etc.),
  which does not show short-paths, and xml representable characters for
  non-ascii characters, when found in the database objects, and the file
  PATH.

Fixes #2174, #1797, #2166, #1940

Initial patch by: Surinder Kumar
Reviewed by: Murtuza Zabuawala
This commit is contained in:
Ashesh Vashi 2017-03-07 15:30:57 +05:30
parent 063177155e
commit f2fc1ceba8
15 changed files with 594 additions and 357 deletions

View File

@ -12,7 +12,7 @@
##########################################################################
import os
from logging import *
from pgadmin.utils import env, IS_PY2, IS_WIN, fs_short_path
##########################################################################
# Application settings
@ -81,12 +81,17 @@ MODULE_BLACKLIST = ['test']
# List of treeview browser nodes to skip when dynamically loading
NODE_BLACKLIST = []
# Data directory for storage of config settings etc. This shouldn't normally
# need to be changed - it's here as various other settings depend on it.
if os.name == 'nt':
DATA_DIR = os.path.realpath(os.getenv('APPDATA') + "/pgAdmin")
if IS_WIN:
# Use the short path on windows
DATA_DIR = os.path.realpath(
os.path.join(fs_short_path(env('APPDATA')), u"pgAdmin")
)
else:
DATA_DIR = os.path.realpath(os.path.expanduser('~/.pgadmin/'))
DATA_DIR = os.path.realpath(os.path.expanduser(u'~/.pgadmin/'))
##########################################################################
# Log settings
@ -95,6 +100,9 @@ else:
# Debug mode?
DEBUG = False
import logging
# Application log level - one of:
# CRITICAL 50
# ERROR 40
@ -103,18 +111,16 @@ DEBUG = False
# INFO 20
# DEBUG 10
# NOTSET 0
CONSOLE_LOG_LEVEL = WARNING
FILE_LOG_LEVEL = WARNING
CONSOLE_LOG_LEVEL = logging.WARNING
FILE_LOG_LEVEL = logging.WARNING
# Log format.
CONSOLE_LOG_FORMAT = '%(asctime)s: %(levelname)s\t%(name)s:\t%(message)s'
FILE_LOG_FORMAT = '%(asctime)s: %(levelname)s\t%(name)s:\t%(message)s'
# Log file name
LOG_FILE = os.path.join(
DATA_DIR,
'pgadmin4.log'
)
LOG_FILE = os.path.join(DATA_DIR, 'pgadmin4.log')
##########################################################################
# Server settings
@ -176,11 +182,8 @@ MAX_SESSION_IDLE_TIME = 60
# The default path to the SQLite database used to store user accounts and
# settings. This default places the file in the same directory as this
# config file, but generates an absolute path for use througout the app.
SQLITE_PATH = os.environ.get("SQLITE_PATH") or \
os.path.join(
DATA_DIR,
'pgadmin4.db'
)
SQLITE_PATH = env('SQLITE_PATH') or os.path.join(DATA_DIR, 'pgadmin4.db')
# SQLITE_TIMEOUT will define how long to wait before throwing the error -
# OperationError due to database lock. On slower system, you may need to change
# this to some higher value.
@ -207,10 +210,7 @@ SQLITE_TIMEOUT = 500
# SESSION_DB_PATH = '/run/shm/pgAdmin4_session'
#
##########################################################################
SESSION_DB_PATH = os.path.join(
DATA_DIR,
'sessions'
)
SESSION_DB_PATH = os.path.join(DATA_DIR, 'sessions')
SESSION_COOKIE_NAME = 'pga4_session'
@ -262,10 +262,8 @@ UPGRADE_CHECK_URL = 'https://www.pgadmin.org/versions.json'
# 2. Set path manually like
# STORAGE_DIR = "/path/to/directory/"
##########################################################################
STORAGE_DIR = os.path.join(
DATA_DIR,
'storage'
)
STORAGE_DIR = os.path.join(DATA_DIR, 'storage')
##########################################################################
# Default locations for binary utilities (pg_dump, pg_restore etc)
@ -296,6 +294,7 @@ TESTING_MODE = False
# The default path for SQLite database for testing
TEST_SQLITE_PATH = os.path.join(DATA_DIR, 'test_pgadmin4.db')
##########################################################################
# Allows flask application to response to the each request asynchronously
##########################################################################

View File

@ -34,9 +34,11 @@ config.SETTINGS_SCHEMA_VERSION = SCHEMA_VERSION
# Check if the database exists. If it does not, create it.
if not os.path.isfile(config.SQLITE_PATH):
setupfile = os.path.join(os.path.dirname(os.path.realpath(__file__)),
'setup.py')
exec (open(setupfile).read())
from pgadmin.utils import u, fs_encoding, file_quote
setupfile = os.path.join(
os.path.dirname(os.path.realpath(u(__file__, fs_encoding))), u'setup.py'
)
exec(open(file_quote(setupfile), 'r').read())
##########################################################################
# Server starup

View File

@ -30,8 +30,6 @@ from werkzeug.local import LocalProxy
from werkzeug.utils import find_modules
from pgadmin.model import db, Role, Server, ServerGroup, User, Version, Keys
# Configuration settings
import config
# If script is running under python3, it will not have the xrange function
# defined
@ -129,7 +127,13 @@ def _find_blueprint():
current_blueprint = LocalProxy(_find_blueprint)
def create_app(app_name=config.APP_NAME):
def create_app(app_name=None):
# Configuration settings
import config
if not app_name:
app_name = config.APP_NAME
"""Create the Flask application, startup logging and dynamically load
additional modules (blueprints) that are found in this directory."""
app = PgAdmin(__name__, static_url_path='/static')
@ -193,8 +197,8 @@ def create_app(app_name=config.APP_NAME):
# Setup authentication
##########################################################################
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///{0}?timeout={1}'.format(
config.SQLITE_PATH.replace('\\', '/'),
app.config['SQLALCHEMY_DATABASE_URI'] = u'sqlite:///{0}?timeout={1}'.format(
config.SQLITE_PATH.replace(u'\\', u'/'),
getattr(config, 'SQLITE_TIMEOUT', 500)
)

View File

@ -29,7 +29,7 @@ It also depends on the following environment variable for proper execution.
PROCID - Process-id
OUTDIR - Output directory
"""
from __future__ import print_function, unicode_literals
from __future__ import print_function
# To make print function compatible with python2 & python3
import sys
@ -37,27 +37,31 @@ import os
from datetime import datetime, timedelta, tzinfo
from subprocess import Popen, PIPE
from threading import Thread
import codecs
import signal
def log(msg):
if 'OUTDIR' not in os.environ:
return
_IS_WIN = (os.name == 'nt')
_IS_PY2 = (sys.version_info[0] == 2)
_ZERO = timedelta(0)
_sys_encoding = None
_fs_encoding = None
_u = None
_out_dir = None
_log_file = None
with open(
os.path.join(os.environ['OUTDIR'], ('log_%s' % os.getpid())), 'a'
) as fp:
fp.write(('INFO:: %s\n' % str(msg)))
if _IS_PY2:
def _log(msg):
with open(_log_file, 'a') as fp:
fp.write(('INFO:: %s\n' % str(msg)))
else:
def _log(msg):
with open(_log_file, 'a') as fp:
fp.write(('INFO:: %s\n' % msg.encode('ascii', 'xmlcharrefreplace')))
def log_exception():
if 'OUTDIR' not in os.environ:
return
def _log_exception():
type_, value_, traceback_ = info=sys.exc_info()
with open(
os.path.join(os.environ['OUTDIR'], ('log_%s' % os.getpid())), 'a'
) as fp:
with open(_log_file, 'ab') as fp:
from traceback import format_exception
res = ''.join(
format_exception(type_, value_, traceback_)
@ -67,11 +71,6 @@ def log_exception():
return res
IS_WIN = (os.name == 'nt')
ZERO = timedelta(0)
default_encoding = sys.getdefaultencoding() or "utf-8"
# Copied the 'UTC' class from the 'pytz' package to allow to run this script
# without any external dependent library, and can be used with any python
# version.
@ -83,8 +82,8 @@ class UTC(tzinfo):
"""
zone = "UTC"
_utcoffset = ZERO
_dst = ZERO
_utcoffset = _ZERO
_dst = _ZERO
_tzname = zone
def fromutc(self, dt):
@ -93,13 +92,13 @@ class UTC(tzinfo):
return super(UTC.__class__, self).fromutc(dt)
def utcoffset(self, dt):
return ZERO
return _ZERO
def tzname(self, dt):
return "UTC"
def dst(self, dt):
return ZERO
return _ZERO
def localize(self, dt, is_dst=False):
'''Convert naive time to local time'''
@ -155,15 +154,12 @@ class ProcessLogger(Thread):
Returns:
None
"""
import codecs
Thread.__init__(self)
self.process = None
self.stream = None
self.encoding = default_encoding
self.logger = codecs.open(
os.path.join(
os.environ['OUTDIR'], stream_type
), 'w', self.encoding, "ignore"
)
self.logger = open(os.path.join(_out_dir, stream_type), 'wb')
def attach_process_stream(self, process, stream):
"""
@ -179,27 +175,52 @@ class ProcessLogger(Thread):
self.process = process
self.stream = stream
def log(self, msg):
"""
This function will update log file
if not _IS_PY2:
def log(self, msg):
"""
This function will update log file
Args:
msg: message
Args:
msg: message
Returns:
None
"""
# Write into log file
if self.logger:
if msg:
self.logger.write(
str('{0},{1}').format(
get_current_time(format='%y%m%d%H%M%S%f'),
msg.decode(self.encoding, 'replace')
Returns:
None
"""
# Write into log file
if self.logger:
if msg:
self.logger.write(get_current_time(format='%y%m%d%H%M%S%f').encode('utf-8'))
self.logger.write(b',')
self.logger.write(msg.lstrip(b'\r\n' if _IS_WIN else b'\n'))
self.logger.write(os.linesep.encode('utf-8'))
return True
return False
else:
def log(self, msg):
"""
This function will update log file
Args:
msg: message
Returns:
None
"""
# Write into log file
if self.logger:
if msg:
self.logger.write(
b'{0},{1}{2}'.format(
get_current_time(
format='%y%m%d%H%M%S%f'
),
msg.lstrip(b'\r\n' if _IS_WIN else b'\n'), os.linesep
)
)
)
return True
return False
return True
return False
def run(self):
if self.process and self.stream:
@ -230,14 +251,14 @@ def update_status(**kw):
"""
import json
if os.environ['OUTDIR']:
if _out_dir:
status = dict(
(k, v) for k, v in kw.items() if k in [
'start_time', 'end_time', 'exit_code', 'pid'
]
)
log('Updating the status:\n{0}'.format(json.dumps(status)))
with open(os.path.join(os.environ['OUTDIR'], 'status'), 'w') as fp:
_log('Updating the status:\n{0}'.format(json.dumps(status)))
with open(os.path.join(_out_dir, 'status'), 'w') as fp:
json.dump(status, fp)
else:
raise ValueError("Please verify pid and db_file arguments.")
@ -252,7 +273,7 @@ def execute():
"""
command = sys.argv[1:]
args = dict()
log('Initialize the process execution: {0}'.format(command))
_log('Initialize the process execution: {0}'.format(command))
# Create seprate thread for stdout and stderr
process_stdout = ProcessLogger('out')
@ -270,25 +291,28 @@ def execute():
# Update start time
update_status(**args)
log('Status updated...')
_log('Status updated...')
if 'PROCID' in os.environ and os.environ['PROCID'] in os.environ:
os.environ['PGPASSWORD'] = os.environ[os.environ['PROCID']]
kwargs = dict()
kwargs['close_fds'] = False
kwargs['shell'] = True if IS_WIN else False
kwargs['shell'] = True if _IS_WIN else False
# We need environment variables & values in string
log('Converting the environment variable in the bytes format...')
kwargs['env'] = convert_environment_variables(os.environ.copy())
if _IS_PY2:
_log('Converting the environment variable in the bytes format...')
kwargs['env'] = convert_environment_variables(os.environ.copy())
else:
kwargs['env'] = os.environ.copy()
log('Starting the command execution...')
_log('Starting the command execution...')
process = Popen(
command, stdout=PIPE, stderr=PIPE, stdin=None, **kwargs
)
log('Attaching the loggers to stdout, and stderr...')
_log('Attaching the loggers to stdout, and stderr...')
# Attach the stream to the process logger, and start logging.
process_stdout.attach_process_stream(process, process.stdout)
process_stdout.start()
@ -299,14 +323,14 @@ def execute():
process_stdout.join()
process_stderr.join()
log('Waiting for the process to finish...')
_log('Waiting for the process to finish...')
# Child process return code
exitCode = process.wait()
if exitCode is None:
exitCode = process.poll()
log('Process exited with code: {0}'.format(exitCode))
_log('Process exited with code: {0}'.format(exitCode))
args.update({'exit_code': exitCode})
# Add end_time
@ -322,7 +346,7 @@ def execute():
# If executable not found or invalid arguments passed
except OSError:
info = log_exception()
info = _log_exception()
args.update({'exit_code': 500})
if process_stderr:
process_stderr.log(info)
@ -333,7 +357,7 @@ def execute():
# Unknown errors
except Exception:
info = log_exception()
info = _log_exception()
args.update({'exit_code': 501})
if process_stderr:
process_stderr.log(info)
@ -344,12 +368,12 @@ def execute():
finally:
# Update the execution end_time, and exit-code.
update_status(**args)
log('Exiting the process executor...')
_log('Exiting the process executor...')
if process_stderr:
process_stderr.release()
if process_stdout:
process_stdout.release()
log('Bye!')
_log('Bye!')
# Let's ignore all the signal comming to us.
@ -368,27 +392,50 @@ def convert_environment_variables(env):
for key, value in env.items():
try:
if not isinstance(key, str):
key = key.encode(default_encoding)
if not isinstance(value, str):
value = value.encode(default_encoding)
key = key.encode(_sys_encoding)
if not isinstance(value, str):
value = value.encode(_sys_encoding)
temp_env[key] = value
except Exception as e:
log_exception()
_log_exception()
return temp_env
if __name__ == '__main__':
log('Starting the process executor...')
_sys_encoding = sys.getdefaultencoding()
if not _sys_encoding or _sys_encoding == 'ascii':
# Fall back to 'utf-8', if we couldn't determine the default encoding,
# or 'ascii'.
_sys_encoding = 'utf-8'
_fs_encoding = sys.getfilesystemencoding()
if not _fs_encoding or _fs_encoding == 'ascii':
# Fall back to 'utf-8', if we couldn't determine the file-system encoding,
# or 'ascii'.
_fs_encoding = 'utf-8'
def u(_s, _encoding=_sys_encoding):
if _IS_PY2:
if isinstance(_s, str):
return unicode(_s, _encoding)
return _s
_u = u
_out_dir = u(os.environ['OUTDIR'])
_log_file = os.path.join(_out_dir, ('log_%s' % os.getpid()))
_log('Starting the process executor...')
# Ignore any signals
signal.signal(signal.SIGINT, signal_handler)
signal.signal(signal.SIGTERM, signal_handler)
log('Disabled the SIGINT, SIGTERM signals...')
_log('Disabled the SIGINT, SIGTERM signals...')
if IS_WIN:
log('Disable the SIGBREAKM signal (windows)...')
if _IS_WIN:
_log('Disable the SIGBREAKM signal (windows)...')
signal.signal(signal.SIGBREAK, signal_handler)
log('Disabled the SIGBREAKM signal (windows)...')
_log('Disabled the SIGBREAKM signal (windows)...')
# For windows:
# We would run the process_executor in the detached mode again to make
@ -396,11 +443,14 @@ if __name__ == '__main__':
# depending on the status of the web-server.
if 'PGA_BGP_FOREGROUND' in os.environ and \
os.environ['PGA_BGP_FOREGROUND'] == "1":
log('[CHILD] Start process execution...')
log('Executing the command now from the detached child...')
_log('[CHILD] Start process execution...')
# This is a child process running as the daemon process.
# Let's do the job assing to it.
execute()
# Let's do the job assigning to it.
try:
_log('Executing the command now from the detached child...')
execute()
except:
_log_exception()
else:
from subprocess import CREATE_NEW_PROCESS_GROUP
DETACHED_PROCESS = 0x00000008
@ -414,11 +464,11 @@ if __name__ == '__main__':
env['PGA_BGP_FOREGROUND'] = "1"
# We need environment variables & values in string
log('[PARENT] Converting the environment variable in the bytes format...')
_log('[PARENT] Converting the environment variable in the bytes format...')
try:
env = convert_environment_variables(env)
except Exception as e:
log_exception()
_log_exception()
kwargs = {
'stdin': stdin.fileno(),
@ -426,33 +476,33 @@ if __name__ == '__main__':
'stderr': stderr.fileno(),
'creationflags': CREATE_NEW_PROCESS_GROUP | DETACHED_PROCESS,
'close_fds': False,
'cwd': os.environ['OUTDIR'],
'cwd': _out_dir,
'env': env
}
cmd = [sys.executable]
cmd.extend(sys.argv)
log('[PARENT] Command executings: {0}'.format(cmd))
_log('[PARENT] Command executings: {0}'.format(cmd))
p = Popen(cmd, **kwargs)
exitCode = p.poll()
if exitCode is not None:
log(
_log(
'[PARENT] Child exited with exit-code#{0}...'.format(
exitCode
)
)
else:
log('[PARENT] Started the child with PID#{0}'.format(p.pid))
_log('[PARENT] Started the child with PID#{0}'.format(p.pid))
# Question: Should we wait for sometime?
# Answer: Looks the case...
from time import sleep
sleep(2)
log('[PARENT] Exiting...')
_log('[PARENT] Exiting...')
sys.exit(0)
else:
r, w = os.pipe()
@ -461,35 +511,36 @@ if __name__ == '__main__':
# We will fork the process, and run the child process as daemon, and
# let it do the job.
if os.fork() == 0:
log('[CHILD] Forked the child process...')
_log('[CHILD] Forked the child process...')
# Hmm... So - I need to do the job now...
try:
os.close(r)
log('[CHILD] Make the child process leader...')
_log('[CHILD] Make the child process leader...')
# Let me be the process leader first.
os.setsid()
os.umask(0)
log('[CHILD] Make the child process leader...')
_log('[CHILD] Make the child process leader...')
w = os.fdopen(w, 'w')
# Let me inform my parent - I will do the job, do not worry
# now, and die peacefully.
log('[CHILD] Inform parent about successful child forking...')
_log('[CHILD] Inform parent about successful child forking...')
w.write('1')
w.close()
log('[CHILD] Start executing the background process...')
_log('[CHILD] Start executing the background process...')
execute()
except Exception:
_log_exception()
sys.exit(1)
else:
os.close(w)
r = os.fdopen(r)
# I do not care, what the child send.
r.read()
log('[PARENT] Got message from the child...')
_log('[PARENT] Got message from the child...')
r.close()
log('[PARENT] Exiting...')
_log('[PARENT] Exiting...')
sys.exit(0)

View File

@ -11,8 +11,6 @@
"""
Introduce a function to run the process executor in detached mode.
"""
from __future__ import print_function, unicode_literals
import csv
import os
import sys
@ -21,6 +19,12 @@ from datetime import datetime
from pickle import dumps, loads
from subprocess import Popen
from pgadmin.utils import IS_PY2, u, file_quote, fs_encoding
if IS_PY2:
from StringIO import StringIO
else:
from io import StringIO
import pytz
from dateutil import parser
from flask import current_app
@ -30,11 +34,6 @@ from flask_security import current_user
import config
from pgadmin.model import Process, db
if sys.version_info < (3,):
from StringIO import StringIO
else:
from io import StringIO
def get_current_time(format='%Y-%m-%d %H:%M:%S.%f %z'):
"""
@ -159,17 +158,23 @@ class BatchProcess(object):
csv_writer = csv.writer(
args_csv_io, delimiter=str(','), quoting=csv.QUOTE_MINIMAL
)
csv_writer.writerow(_args)
if sys.version_info.major == 2:
csv_writer.writerow([a.encode('utf-8') if isinstance(a, unicode) else a for a in _args])
else:
csv_writer.writerow(_args)
args_val = args_csv_io.getvalue().strip(str('\r\n'))
j = Process(
pid=int(id), command=_cmd,
arguments=args_csv_io.getvalue().strip(str('\r\n')),
arguments=args_val.decode('utf-8', 'replace') if IS_PY2 and hasattr(args_val, 'decode') \
else args_val,
logdir=log_dir, desc=dumps(self.desc), user_id=current_user.id
)
db.session.add(j)
db.session.commit()
def start(self):
def start(self, cb=None):
def which(program, paths):
def is_exe(fpath):
@ -178,9 +183,9 @@ class BatchProcess(object):
for path in paths:
if not os.path.isdir(path):
continue
exe_file = os.path.join(path, program)
exe_file = os.path.join(u(path, fs_encoding), program)
if is_exe(exe_file):
return exe_file
return file_quote(exe_file)
return None
def convert_environment_variables(env):
@ -191,6 +196,8 @@ class BatchProcess(object):
:return: Encoded environment variable as string
"""
encoding = sys.getdefaultencoding()
if encoding is None or encoding == 'ascii':
encoding = 'utf-8'
temp_env = dict()
for key, value in env.items():
if not isinstance(key, str):
@ -207,22 +214,22 @@ class BatchProcess(object):
_('The process has already finished and can not be restarted.')
)
executor = os.path.join(
os.path.dirname(__file__), 'process_executor.py'
)
executor = file_quote(os.path.join(
os.path.dirname(u(__file__)), u'process_executor.py'
))
paths = sys.path[:]
interpreter = None
if os.name == 'nt':
paths.insert(0, os.path.join(sys.prefix, 'Scripts'))
paths.insert(0, os.path.join(sys.prefix))
paths.insert(0, os.path.join(u(sys.prefix), u'Scripts'))
paths.insert(0, u(sys.prefix))
interpreter = which('pythonw.exe', paths)
interpreter = which(u'pythonw.exe', paths)
if interpreter is None:
interpreter = which('python.exe', paths)
interpreter = which(u'python.exe', paths)
else:
paths.insert(0, os.path.join(sys.prefix, 'bin'))
interpreter = which('python', paths)
paths.insert(0, os.path.join(u(sys.prefix), u'bin'))
interpreter = which(u'python', paths)
p = None
cmd = [
@ -231,15 +238,21 @@ class BatchProcess(object):
]
cmd.extend(self.args)
command = []
for c in cmd:
command.append(str(c))
if os.name == 'nt' and IS_PY2:
command = []
for c in cmd:
command.append(c.encode('utf-8') if isinstance(c, unicode) else str(c))
current_app.logger.info(
"Executing the process executor with the arguments: %s",
' '.join(command)
)
cmd = command
current_app.logger.info(
u"Executing the process executor with the arguments: %s",
''.join(command)
)
cmd = command
else:
current_app.logger.info(
u"Executing the process executor with the arguments: %s", str(cmd)
)
# Make a copy of environment, and add new variables to support
env = os.environ.copy()
@ -247,8 +260,12 @@ class BatchProcess(object):
env['OUTDIR'] = self.log_dir
env['PGA_BGP_FOREGROUND'] = "1"
# We need environment variables & values in string
env = convert_environment_variables(env)
if cb is not None:
cb(env)
if IS_PY2:
# We need environment variables & values in string
env = convert_environment_variables(env)
if os.name == 'nt':
DETACHED_PROCESS = 0x00000008
@ -325,12 +342,16 @@ class BatchProcess(object):
line = f.readline()
line = line.decode(enc, 'replace')
r = c.split(line)
if len(r) < 3:
# ignore this line
pos = f.tell()
continue
if r[1] > ctime:
completed = False
break
log.append([r[1], r[2]])
pos = f.tell()
if idx == 1024:
if idx >= 1024:
completed = False
break
if pos == eofs:
@ -453,7 +474,10 @@ class BatchProcess(object):
if isinstance(desc, IProcessDesc):
args = []
args_csv = StringIO(p.arguments)
args_csv = StringIO(
p.arguments.encode('utf-8') \
if hasattr(p.arguments, 'decode') else p.arguments
)
args_reader = csv.reader(args_csv, delimiter=str(','))
for arg in args_reader:
args = args + arg

View File

@ -9,6 +9,7 @@
"""Implements Backup Utility"""
from __future__ import unicode_literals
import simplejson as json
import os
@ -17,7 +18,8 @@ from flask import render_template, request, current_app, \
from flask_babel import gettext as _
from flask_security import login_required, current_user
from pgadmin.misc.bgprocess.processes import BatchProcess, IProcessDesc
from pgadmin.utils import PgAdminModule, get_storage_directory, html
from pgadmin.utils import PgAdminModule, get_storage_directory, html, \
fs_short_path, document_dir
from pgadmin.utils.ajax import make_json_response, bad_request
from config import PG_DEFAULT_DRIVER
@ -79,14 +81,28 @@ class BackupMessage(IProcessDesc):
Defines the message shown for the backup operation.
"""
def __init__(self, _type, _sid, _bfile, **kwargs):
def __init__(self, _type, _sid, _bfile, *_args, **_kwargs):
self.backup_type = _type
self.sid = _sid
self.bfile = _bfile
self.database = None
self.database = _kwargs['database'] if 'database' in _kwargs else None
self.cmd = ''
if 'database' in kwargs:
self.database = kwargs['database']
def cmdArg(x):
if x:
# x = html.safe_str(x)
x = x.replace('\\', '\\\\')
x = x.replace('"', '\\"')
x = x.replace('""', '\\"')
return ' "' + x + '"'
return ''
for arg in _args:
if arg and len(arg) >= 2 and arg[:2] == '--':
self.cmd += ' ' + arg
else:
self.cmd += cmdArg(arg)
@property
def message(self):
@ -123,65 +139,32 @@ class BackupMessage(IProcessDesc):
res = '<div class="h5">'
if self.backup_type == BACKUP.OBJECT:
res += html.safe_str(
_(
"Backing up an object on the server '{0}' from database '{1}'..."
).format(
"{0} ({1}:{2})".format(s.name, s.host, s.port),
self.database
)
res += _(
"Backing up an object on the server '{0}' from database '{1}'..."
).format(
"{0} ({1}:{2})".format(s.name, s.host, s.port),
self.database
)
elif self.backup_type == BACKUP.GLOBALS:
res += html.safe_str(
_("Backing up the global objects on the server '{0}'").format(
"{0} ({1}:{2})".format(s.name, s.host, s.port)
)
res += _("Backing up the global objects on the server '{0}'").format(
"{0} ({1}:{2})".format(s.name, s.host, s.port)
)
elif self.backup_type == BACKUP.SERVER:
res += html.safe_str(
_("Backing up the server '{0}'").format(
"{0} ({1}:{2})".format(s.name, s.host, s.port)
)
res += _("Backing up the server '{0}'").format(
"{0} ({1}:{2})".format(s.name, s.host, s.port)
)
else:
# It should never reach here.
res += "Backup"
res += '</div><div class="h5">'
res += html.safe_str(
_("Running command:")
)
res += _("Running command:")
res += '</b><br><span class="pg-bg-cmd enable-selection">'
res += html.safe_str(cmd)
replace_next = False
def cmdArg(x):
if x:
x = x.replace('\\', '\\\\')
x = x.replace('"', '\\"')
x = x.replace('""', '\\"')
return ' "' + html.safe_str(x) + '"'
return ''
for arg in args:
if arg and len(arg) >= 2 and arg[:2] == '--':
res += ' ' + arg
elif replace_next:
res += ' "' + html.safe_str(
self.bfile
) + '"'
else:
if arg == '--file':
replace_next = True
res += cmdArg(arg)
res += self.cmd
res += '</span></div>'
return res
@blueprint.route("/")
@login_required
def index():
@ -201,7 +184,7 @@ def script():
)
def filename_with_file_manager_path(file):
def filename_with_file_manager_path(_file):
"""
Args:
file: File name returned from client file manager
@ -213,9 +196,15 @@ def filename_with_file_manager_path(file):
storage_dir = get_storage_directory()
if storage_dir:
return os.path.join(storage_dir, file.lstrip('/'))
_file = os.path.join(storage_dir, _file.lstrip(u'/').lstrip(u'\\'))
elif not os.path.isabs(_file):
_file = os.path.join(document_dir(), _file)
return file
# Touch the file to get the short path of the file on windows.
with open(_file, 'a'):
pass
return fs_short_path(_file)
@blueprint.route('/create_job/<int:sid>', methods=['POST'])
@ -292,7 +281,11 @@ def create_backup_job(sid):
p = BatchProcess(
desc=BackupMessage(
BACKUP.SERVER if data['type'] != 'global' else BACKUP.GLOBALS,
sid, data['file']
sid,
data['file'].encode('utf-8') if hasattr(
data['file'], 'encode'
) else data['file'],
*args
),
cmd=utility, args=args
)
@ -440,9 +433,15 @@ def create_backup_objects_job(sid):
try:
p = BatchProcess(
desc=BackupMessage(
BACKUP.OBJECT, sid, data['file'], database=data['database']
BACKUP.OBJECT, sid,
data['file'].encode('utf-8') if hasattr(
data['file'], 'encode'
) else data['file'],
*args,
database=data['database']
),
cmd=utility, args=args)
cmd=utility, args=args
)
manager.export_password_env(p.id)
p.start()
jid = p.id

View File

@ -16,7 +16,8 @@ from flask import url_for, Response, render_template, request, current_app
from flask_babel import gettext as _
from flask_security import login_required, current_user
from pgadmin.misc.bgprocess.processes import BatchProcess, IProcessDesc
from pgadmin.utils import PgAdminModule, get_storage_directory, html
from pgadmin.utils import PgAdminModule, get_storage_directory, html, \
fs_short_path, document_dir, IS_WIN
from pgadmin.utils.ajax import make_json_response, bad_request
from config import PG_DEFAULT_DRIVER
@ -56,19 +57,47 @@ class ImportExportModule(PgAdminModule):
blueprint = ImportExportModule(MODULE_NAME, __name__)
class Message(IProcessDesc):
class IEMessage(IProcessDesc):
"""
Message(IProcessDesc)
IEMessage(IProcessDesc)
Defines the message shown for the Message operation.
Defines the message shown for the import/export operation.
"""
def __init__(self, _sid, _schema, _tbl, _database, _storage):
def __init__(self, _sid, _schema, _tbl, _database, _storage, *_args):
self.sid = _sid
self.schema = _schema
self.table = _tbl
self.database = _database
self.storage = _storage
self._cmd = ''
if _storage:
_storage = _storage.replace('\\', '/')
def cmdArg(x):
if x:
x = html.safe_str(x)
x = x.replace('\\', '\\\\')
x = x.replace('"', '\\"')
x = x.replace('""', '\\"')
return ' "' + x + '"'
return ''
replace_next = False
for arg in _args:
if arg and len(arg) >= 2 and arg[:2] == '--':
if arg == '--command':
replace_next = True
self._cmd += ' ' + arg
elif replace_next:
arg = cmdArg(arg)
if _storage is not None:
arg = arg.replace(_storage, '<STORAGE_DIR>')
self._cmd += ' "' + arg + '"'
else:
self._cmd+= cmdArg(arg)
@property
def message(self):
@ -90,45 +119,17 @@ class Message(IProcessDesc):
).first()
res = '<div class="h5">'
res += html.safe_str(
_(
"Copying table data '{0}.{1}' on database '{2}' for the server - '{3}'"
).format(
self.schema, self.table, self.database,
"{0} ({1}:{2})".format(s.name, s.host, s.port)
)
res += _(
"Copying table data '{0}.{1}' on database '{2}' for the server - '{3}'"
).format(
self.schema, self.table, self.database,
"{0} ({1}:{2})".format(s.name, s.host, s.port)
)
res += '</div><div class="h5">'
res += html.safe_str(
_("Running command:")
)
res += _("Running command:")
res += '</b><br><span class="pg-bg-cmd enable-selection">'
res += html.safe_str(cmd)
replace_next = False
def cmdArg(x):
if x:
x = x.replace('\\', '\\\\')
x = x.replace('"', '\\"')
x = x.replace('""', '\\"')
return ' "' + html.safe_str(x) + '"'
return ''
for arg in args:
if arg and len(arg) >= 2 and arg[:2] == '--':
if arg == '--command':
replace_next = True
res += ' ' + arg
elif replace_next:
if self.storage:
arg = arg.replace(self.storage, '<STORAGE_DIR>')
res += ' "' + html.safe_str(arg) + '"'
else:
res += cmdArg(arg)
res += self._cmd
res += '</span></div>'
return res
@ -151,6 +152,33 @@ def script():
)
def filename_with_file_manager_path(_file, _present=False):
"""
Args:
file: File name returned from client file manager
Returns:
Filename to use for backup with full path taken from preference
"""
# Set file manager directory from preference
storage_dir = get_storage_directory()
if storage_dir:
_file = os.path.join(storage_dir, _file.lstrip(u'/').lstrip(u'\\'))
elif not os.path.isabs(_file):
_file = os.path.join(document_dir(), _file)
if not _present:
# Touch the file to get the short path of the file on windows.
with open(_file, 'a'):
pass
else:
if not os.path.isfile(_file):
return None
return fs_short_path(_file)
@blueprint.route('/create_job/<int:sid>', methods=['POST'])
@login_required
def create_import_export_job(sid):
@ -175,10 +203,8 @@ def create_import_export_job(sid):
id=sid).first()
if server is None:
return make_json_response(
success=0,
errormsg=_("Couldn't find the given server")
)
return bad_request(errormsg=_("Couldn't find the given server"))
# To fetch MetaData for the server
from pgadmin.utils.driver import get_driver
@ -188,10 +214,7 @@ def create_import_export_job(sid):
connected = conn.connected()
if not connected:
return make_json_response(
success=0,
errormsg=_("Please connect to the server first...")
)
return bad_request(errormsg=_("Please connect to the server first..."))
# Get the utility path from the connection manager
utility = manager.utility('sql')
@ -200,21 +223,16 @@ def create_import_export_job(sid):
storage_dir = get_storage_directory()
if 'filename' in data:
if os.name == 'nt':
data['filename'] = data['filename'].replace('/', '\\')
if storage_dir:
storage_dir = storage_dir.replace('/', '\\')
data['filename'] = data['filename'].replace('\\', '\\\\')
data['filename'] = os.path.join(storage_dir, data['filename'].lstrip('/'))
elif storage_dir:
data['filename'] = os.path.join(storage_dir, data['filename'].lstrip('/'))
else:
data['filename'] = data['filename']
_file = filename_with_file_manager_path(data['filename'], data['is_import'])
if not _file:
return bad_request(errormsg=_('Please specify a valid file'))
if IS_WIN:
_file = _file.replace('\\', '/')
data['filename'] = _file
else:
return make_json_response(
data={'status': False, 'info': 'Please specify a valid file'}
)
return bad_request(errormsg=_('Please specify a valid file'))
cols = None
icols = None
@ -255,33 +273,32 @@ def create_import_export_job(sid):
ignore_column_list=icols
)
args = [
'--host', server.host, '--port', str(server.port),
'--username', server.username, '--dbname', data['database'],
'--command', query
]
args = ['--command', query]
try:
p = BatchProcess(
desc=Message(
desc=IEMessage(
sid,
data['schema'],
data['table'],
data['database'],
storage_dir
storage_dir,
utility, *args
),
cmd=utility, args=args
)
manager.export_password_env(p.id)
p.start()
def export_pg_env(env):
env['PGHOST'] = server.host
env['PGPORT'] = str(server.port)
env['PGUSER'] = server.username
env['PGDATABASE'] = data['database']
p.start(export_pg_env)
jid = p.id
except Exception as e:
current_app.logger.exception(e)
return make_json_response(
status=410,
success=0,
errormsg=str(e)
)
return bad_request(errormsg=str(e))
# Return response
return make_json_response(

View File

@ -17,7 +17,8 @@ from flask import render_template, request, current_app, \
from flask_babel import gettext as _
from flask_security import login_required, current_user
from pgadmin.misc.bgprocess.processes import BatchProcess, IProcessDesc
from pgadmin.utils import PgAdminModule, get_storage_directory, html
from pgadmin.utils import PgAdminModule, get_storage_directory, html, \
fs_short_path, document_dir
from pgadmin.utils.ajax import make_json_response, bad_request
from config import PG_DEFAULT_DRIVER
@ -58,9 +59,26 @@ blueprint = RestoreModule(
class RestoreMessage(IProcessDesc):
def __init__(self, _sid, _bfile):
def __init__(self, _sid, _bfile, *_args):
self.sid = _sid
self.bfile = _bfile
self.cmd = ''
def cmdArg(x):
if x:
x = html.safe_str(x)
x = x.replace('\\', '\\\\')
x = x.replace('"', '\\"')
x = x.replace('""', '\\"')
return ' "' + x + '"'
return ''
for arg in _args:
if arg and len(arg) >= 2 and arg[:2] == '--':
self.cmd += ' ' + arg
else:
self.cmd += cmdArg(arg)
@property
def message(self):
@ -95,30 +113,7 @@ class RestoreMessage(IProcessDesc):
)
res += '</b><br><span class="pg-bg-cmd enable-selection">'
res += html.safe_str(cmd)
def cmdArg(x):
if x:
x = x.replace('\\', '\\\\')
x = x.replace('"', '\\"')
x = x.replace('""', '\\"')
return ' "' + html.safe_str(x) + '"'
return ''
idx = 0
no_args = len(args)
for arg in args:
if idx < no_args - 1:
if arg[:2] == '--':
res += ' ' + arg
else:
res += cmdArg(arg)
idx += 1
if no_args > 1:
res += ' "' + html.safe_str(arg) + '"'
res += self.cmd
res += '</span></div>'
return res
@ -143,7 +138,7 @@ def script():
)
def filename_with_file_manager_path(file):
def filename_with_file_manager_path(_file):
"""
Args:
file: File name returned from client file manager
@ -155,9 +150,14 @@ def filename_with_file_manager_path(file):
storage_dir = get_storage_directory()
if storage_dir:
return os.path.join(storage_dir, file.lstrip('/'))
_file = os.path.join(storage_dir, _file.lstrip(u'/').lstrip(u'\\'))
elif not os.path.isabs(_file):
_file = os.path.join(document_dir(), _file)
return file
if not os.path.isfile(_file):
return None
return fs_short_path(_file)
@blueprint.route('/create_job/<int:sid>', methods=['POST'])
@ -179,7 +179,13 @@ def create_restore_job(sid):
else:
data = json.loads(request.data, encoding='utf-8')
backup_file = filename_with_file_manager_path(data['file'])
_file = filename_with_file_manager_path(data['file'])
if _file is None:
return make_json_response(
success=0,
errormsg=_("File couldn't be found!")
)
# Fetch the server details like hostname, port, roles etc
server = Server.query.filter_by(
@ -261,7 +267,7 @@ def create_restore_job(sid):
return False
args.extend([
'--host', server.host, '--port', server.port,
'--host', server.host, '--port', str(server.port),
'--username', server.username, '--no-password'
])
@ -300,11 +306,17 @@ def create_restore_job(sid):
set_multiple('trigger_funcs', '--function')
set_multiple('indexes', '--index')
args.append(backup_file)
args.append(fs_short_path(_file))
try:
p = BatchProcess(
desc=RestoreMessage(sid, data['file']),
desc=RestoreMessage(
sid,
data['file'].encode('utf-8') if hasattr(
data['file'], 'encode'
) else data['file'],
*args
),
cmd=utility, args=args
)
manager.export_password_env(p.id)

View File

@ -131,3 +131,105 @@ class PgAdminModule(Blueprint):
menu_items = dict((key, sorted(value, key=attrgetter('priority')))
for key, value in menu_items.items())
return menu_items
import os
import sys
IS_PY2 = (sys.version_info[0] == 2)
IS_WIN = (os.name == 'nt')
sys_encoding = sys.getdefaultencoding()
if not sys_encoding or sys_encoding == 'ascii':
# Fall back to 'utf-8', if we couldn't determine the default encoding,
# or 'ascii'.
sys_encoding = 'utf-8'
fs_encoding = sys.getfilesystemencoding()
if not fs_encoding or fs_encoding == 'ascii':
# Fall back to 'utf-8', if we couldn't determine the file-system encoding,
# or 'ascii'.
fs_encoding = 'utf-8'
def u(_s, _encoding=sys_encoding):
if IS_PY2:
if isinstance(_s, str):
return unicode(_s, _encoding)
return _s
def file_quote(_p):
if IS_PY2:
if isinstance(_p, unicode):
return _p.encode(fs_encoding)
return _p
if IS_WIN:
import ctypes
from ctypes import wintypes
if IS_PY2:
def env(name):
if IS_PY2:
# Make sure string argument is unicode
name = unicode(name)
n = ctypes.windll.kernel32.GetEnvironmentVariableW(name, None, 0)
if n == 0:
return None
buf= ctypes.create_unicode_buffer(u'\0'*n)
ctypes.windll.kernel32.GetEnvironmentVariableW(name, buf, n)
return buf.value
else:
def env(name):
if name in os.environ:
return os.environ[name]
return None
_GetShortPathNameW = ctypes.windll.kernel32.GetShortPathNameW
_GetShortPathNameW.argtypes = [
wintypes.LPCWSTR, wintypes.LPWSTR, wintypes.DWORD
]
_GetShortPathNameW.restype = wintypes.DWORD
def fs_short_path(_path):
"""
Gets the short path name of a given long path.
http://stackoverflow.com/a/23598461/200291
"""
buf_size = len(_path)
while True:
res = ctypes.create_unicode_buffer(buf_size)
needed = _GetShortPathNameW(_path, res, buf_size)
if buf_size >= needed:
return res.value
else:
buf_size += needed
def document_dir():
CSIDL_PERSONAL = 5 # My Documents
SHGFP_TYPE_CURRENT = 0 # Get current, not default value
buf = ctypes.create_unicode_buffer(wintypes.MAX_PATH)
ctypes.windll.shell32.SHGetFolderPathW(
None, CSIDL_PERSONAL, None, SHGFP_TYPE_CURRENT, buf
)
return buf.value
else:
def env(name):
if name in os.environ:
return os.environ[name]
return None
def fs_short_path(_path):
return _path
def document_dir():
return os.path.realpath(os.path.expanduser(u'~/'))

View File

@ -1491,8 +1491,6 @@ class ServerManager(object):
database = self.db
elif did in self.db_info:
database = self.db_info[did]['datname']
if hasattr(str, 'decode'):
database = database.decode('utf-8')
else:
maintenance_db_id = u'DB:{0}'.format(self.db)
if maintenance_db_id in self.connections:
@ -1510,9 +1508,6 @@ WHERE db.oid = {0}""".format(did))
if status and len(res['rows']) > 0:
for row in res['rows']:
self.db_info[did] = row
if hasattr(str, 'decode'):
self.db_info[did]['datname'] = \
self.db_info[did]['datname'].decode('utf-8')
database = self.db_info[did]['datname']
if did not in self.db_info:
@ -1843,7 +1838,10 @@ class Driver(BaseDriver):
# Returns in bytes, we need to convert it in string
if isinstance(res, bytes):
try:
res = res.decode()
try:
res = res.decode()
except UnicodeDecodeError:
res = res.decode(sys.getfilesystemencoding())
except UnicodeDecodeError:
res = res.decode('utf-8')

View File

@ -10,9 +10,14 @@
"""Utilities for HTML"""
import cgi
from pgadmin.utils import IS_PY2
def safe_str(x):
return cgi.escape(x).encode(
'ascii', 'xmlcharrefreplace'
).decode()
try:
x = x.encode('ascii', 'xmlcharrefreplace') if hasattr(x, 'encode') else x
if not IS_PY2:
x = x.decode('utf-8')
except:
pass
return cgi.escape(x)

View File

@ -37,7 +37,11 @@ def get_storage_directory():
if len(username) == 0 or username[0].isdigit():
username = 'pga_user_' + username
storage_dir = os.path.join(storage_dir, username)
storage_dir = os.path.join(
storage_dir.decode('utf-8') if hasattr(storage_dir, 'decode') \
else storage_dir,
username
)
if not os.path.exists(storage_dir):
os.makedirs(storage_dir, int('700', 8))

View File

@ -193,7 +193,7 @@ class _Preference(object):
if pref is None:
pref = UserPrefTable(
uid=current_user.id, pid=self.pid, value=str(value)
uid=current_user.id, pid=self.pid, value=value
)
db.session.add(pref)
else:

View File

@ -143,7 +143,10 @@ class CachingSessionManager(SessionManager):
def put(self, session):
self.parent.put(session)
if session.sid in self._cache:
del self._cache[session.sid]
try:
del self._cache[session.sid]
except:
pass
self._cache[session.sid] = session
self._normalize()

View File

@ -22,14 +22,20 @@ from flask import Flask
from flask_security import Security, SQLAlchemyUserDatastore
from flask_security.utils import encrypt_password
from pgadmin.model import db, Role, User, Server, \
ServerGroup, Version, Keys
# We need to include the root directory in sys.path to ensure that we can
# find everything we need when running in the standalone runtime.
root = os.path.dirname(os.path.realpath(__file__))
if sys.path[0] != root:
sys.path.insert(0, root)
# Configuration settings
import config
# Get the config database schema version. We store this in pgadmin.model
# as it turns out that putting it in the config files isn't a great idea
from pgadmin.model import SCHEMA_VERSION
from pgadmin.model import db, Role, User, Server, ServerGroup, Version, Keys, \
SCHEMA_VERSION
from pgadmin.utils.versioned_template_loader import VersionedTemplateLoader
config.SETTINGS_SCHEMA_VERSION = SCHEMA_VERSION
@ -44,7 +50,7 @@ def do_setup(app):
"""Create a new settings database from scratch"""
if config.SERVER_MODE is False:
print("NOTE: Configuring authentication for DESKTOP mode.")
print(u"NOTE: Configuring authentication for DESKTOP mode.")
email = config.DESKTOP_USER
p1 = ''.join([
random.choice(string.ascii_letters + string.digits)
@ -52,7 +58,7 @@ def do_setup(app):
])
else:
print("NOTE: Configuring authentication for SERVER mode.\n")
print(u"NOTE: Configuring authentication for SERVER mode.\n")
if all(value in os.environ for value in
['PGADMIN_SETUP_EMAIL', 'PGADMIN_SETUP_PASSWORD']):
@ -64,9 +70,11 @@ def do_setup(app):
p1 = os.environ['PGADMIN_SETUP_PASSWORD']
else:
# Prompt the user for their default username and password.
print("""
Enter the email address and password to use for the initial pgAdmin user \
account:\n""")
print(
u"Enter the email address and password to use for the initial "
u"pgAdmin user account:\n"
)
email_filter = re.compile(
"^[a-zA-Z0-9.!#$%&'*+\/=?^_`{|}~-]+@[a-zA-Z0-9]"
"(?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9]"
@ -74,7 +82,7 @@ def do_setup(app):
email = input("Email address: ")
while email == '' or not email_filter.match(email):
print('Invalid email address. Please try again.')
print(u'Invalid email address. Please try again.')
email = input("Email address: ")
def pprompt():
@ -83,10 +91,11 @@ def do_setup(app):
p1, p2 = pprompt()
while p1 != p2 or len(p1) < 6:
if p1 != p2:
print('Passwords do not match. Please try again.')
print(u'Passwords do not match. Please try again.')
else:
print(
'Password must be at least 6 characters. Please try again.')
u'Password must be at least 6 characters. Please try again.'
)
p1, p2 = pprompt()
# Setup Flask-Security
@ -134,9 +143,9 @@ def do_setup(app):
db.session.commit()
# Done!
print("")
print(u"")
print(
"The configuration database has been created at {0}".format(
u"The configuration database has been created at {0}".format(
config.SQLITE_PATH
)
)
@ -154,19 +163,19 @@ def do_upgrade(app, datastore, version):
# Pre-flight checks
if int(version.value) > int(config.SETTINGS_SCHEMA_VERSION):
print("""
print(u"""
The database schema version is {0}, whilst the version required by the \
software is {1}.
Exiting...""".format(version.value, config.SETTINGS_SCHEMA_VERSION))
sys.exit(1)
elif int(version.value) == int(config.SETTINGS_SCHEMA_VERSION):
print("""
print(u"""
The database schema version is {0} as required.
Exiting...""".format(version.value))
sys.exit(1)
app.logger.info(
"NOTE: Upgrading database schema from version %d to %d." %
u"NOTE: Upgrading database schema from version %d to %d." %
(version.value, config.SETTINGS_SCHEMA_VERSION)
)
@ -394,19 +403,24 @@ if __name__ == '__main__':
'sqlite:///' + config.SQLITE_PATH.replace('\\', '/')
db.init_app(app)
print("pgAdmin 4 - Application Initialisation")
print("======================================\n")
print(u"pgAdmin 4 - Application Initialisation")
print(u"======================================\n")
from pgadmin.utils import u, fs_encoding, file_quote
local_config = os.path.join(
os.path.dirname(os.path.realpath(__file__)),
'config_local.py'
os.path.dirname(os.path.realpath(u(__file__, fs_encoding))),
u'config_local.py'
)
# Check if the database exists. If it does, tell the user and exit.
if os.path.isfile(config.SQLITE_PATH):
print("""
The configuration database '%s' already exists.
Entering upgrade mode...""" % config.SQLITE_PATH)
print(
u"The configuration database '{0}' already exists.".format(
config.SQLITE_PATH
)
)
print(u"Entering upgrade mode...")
# Setup Flask-Security
user_datastore = SQLAlchemyUserDatastore(db, User, Role)
@ -417,18 +431,18 @@ Entering upgrade mode...""" % config.SQLITE_PATH)
# Pre-flight checks
if int(version.value) > int(config.SETTINGS_SCHEMA_VERSION):
print("""
print(u"""
The database schema version is %d, whilst the version required by the \
software is %d.
Exiting...""" % (version.value, config.SETTINGS_SCHEMA_VERSION))
sys.exit(1)
elif int(version.value) == int(config.SETTINGS_SCHEMA_VERSION):
print("""
print(u"""
The database schema version is %d as required.
Exiting...""" % (version.value))
sys.exit(1)
print("NOTE: Upgrading database schema from version %d to %d." % (
print(u"NOTE: Upgrading database schema from version %d to %d." % (
version.value, config.SETTINGS_SCHEMA_VERSION
))
do_upgrade(app, user_datastore, version)
@ -441,12 +455,15 @@ Exiting...""" % (version.value))
app.config.from_object(config)
directory = os.path.dirname(config.SQLITE_PATH)
if not os.path.exists(directory):
os.makedirs(directory, int('700', 8))
db_file = os.open(config.SQLITE_PATH, os.O_CREAT, int('600', 8))
os.close(db_file)
print("""
print(u"""
The configuration database - '{0}' does not exist.
Entering initial setup mode...""".format(config.SQLITE_PATH))
do_setup(app)