More db fixes

This commit is contained in:
Ken Roberts 2017-06-09 05:12:39 -07:00
parent 80d30bf272
commit 4b22def91d
8 changed files with 199 additions and 33 deletions

View File

@ -25,12 +25,15 @@ The :mod:`db` module provides the core database functionality for OpenLP
"""
import logging
import os
from copy import copy
from urllib.parse import quote_plus as urlquote
from sqlalchemy import Table, MetaData, Column, types, create_engine
from sqlalchemy.engine.url import make_url
from sqlalchemy.exc import SQLAlchemyError, InvalidRequestError, DBAPIError, OperationalError
from sqlalchemy.orm import scoped_session, sessionmaker, mapper
from sqlalchemy.pool import NullPool
from alembic.migration import MigrationContext
from alembic.operations import Operations
@ -40,6 +43,66 @@ from openlp.core.lib.ui import critical_error_message_box
log = logging.getLogger(__name__)
def database_exists(url):
"""Check if a database exists.
:param url: A SQLAlchemy engine URL.
Performs backend-specific testing to quickly determine if a database
exists on the server. ::
database_exists('postgres://postgres@localhost/name') #=> False
create_database('postgres://postgres@localhost/name')
database_exists('postgres://postgres@localhost/name') #=> True
Supports checking against a constructed URL as well. ::
engine = create_engine('postgres://postgres@localhost/name')
database_exists(engine.url) #=> False
create_database(engine.url)
database_exists(engine.url) #=> True
Borrowed from SQLAlchemy_Utils since we only need this one function.
"""
url = copy(make_url(url))
database = url.database
if url.drivername.startswith('postgresql'):
url.database = 'template1'
else:
url.database = None
engine = create_engine(url)
if engine.dialect.name == 'postgresql':
text = "SELECT 1 FROM pg_database WHERE datname='{db}'".format(db=database)
return bool(engine.execute(text).scalar())
elif engine.dialect.name == 'mysql':
text = ("SELECT SCHEMA_NAME FROM INFORMATION_SCHEMA.SCHEMATA "
"WHERE SCHEMA_NAME = '{db}'".format(db=database))
return bool(engine.execute(text).scalar())
elif engine.dialect.name == 'sqlite':
if database:
return database == ':memory:' or os.path.exists(database)
else:
# The default SQLAlchemy database is in memory,
# and :memory is not required, thus we should support that use-case
return True
else:
text = 'SELECT 1'
try:
url.database = database
engine = create_engine(url)
engine.execute(text)
return True
except (ProgrammingError, OperationalError):
return False
def init_db(url, auto_flush=True, auto_commit=False, base=None):
"""
Initialise and return the session and metadata for a database
@ -144,7 +207,12 @@ def upgrade_db(url, upgrade):
:param url: The url of the database to upgrade.
:param upgrade: The python module that contains the upgrade instructions.
"""
if not database_exists(url):
log.warn("Database {db} doesn't exist - skipping upgrade checks".format(db=url))
return (0, 0)
log.debug('Checking upgrades for DB {db}'.format(db=url))
session, metadata = init_db(url)
class Metadata(BaseModel):

View File

@ -118,7 +118,7 @@ PJLINK_VALID_CMD = {
},
'LKUP': {'version': ['2', ],
'description': translate('OpenLP.PJLinkConstants',
'UDP Status notify. Includes MAC address.')
'UDP Status - Projector is now available on network. Includes MAC address.')
},
'MVOL': {'version': ['2', ],
'description': translate('OpenLP.PJLinkConstants',

View File

@ -80,25 +80,8 @@ class PJLink(QtNetwork.QTcpSocket):
projectorNoAuthentication = QtCore.pyqtSignal(str) # PIN set and no authentication needed
projectorReceivedData = QtCore.pyqtSignal() # Notify when received data finished processing
projectorUpdateIcons = QtCore.pyqtSignal() # Update the status icons on toolbar
# New commands available in PJLink Class 2
pjlink_future = [
'ACKN', # UDP Reply to 'SRCH'
'FILT', # Get current filter usage time
'FREZ', # Set freeze/unfreeze picture being projected
'INNM', # Get Video source input terminal name
'IRES', # Get Video source resolution
'LKUP', # UPD Linkup status notification
'MVOL', # Set microphone volume
'RFIL', # Get replacement air filter model number
'RLMP', # Get lamp replacement model number
'RRES', # Get projector recommended video resolution
'SNUM', # Get projector serial number
'SRCH', # UDP broadcast search for available projectors on local network
'SVER', # Get projector software version
'SVOL', # Set speaker volume
'TESTMEONLY' # For testing when other commands have been implemented
]
# New commands available in PJLink Class 2
pjlink_udp_commands = [
'ACKN',
'ERST', # Class 1 or 2
@ -130,6 +113,7 @@ class PJLink(QtNetwork.QTcpSocket):
self.port = port
self.pin = pin
super().__init__()
self.mac_adx = None if 'mac_adx' not in kwargs else kwargs['mac_adx']
self.dbid = None
self.location = None
self.notes = None

View File

@ -0,0 +1,85 @@
# -*- coding: utf-8 -*-
# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4
###############################################################################
# OpenLP - Open Source Lyrics Projection #
# --------------------------------------------------------------------------- #
# Copyright (c) 2008-2017 OpenLP Developers #
# --------------------------------------------------------------------------- #
# This program is free software; you can redistribute it and/or modify it #
# under the terms of the GNU General Public License as published by the Free #
# Software Foundation; version 2 of the License. #
# #
# This program is distributed in the hope that it will be useful, but WITHOUT #
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or #
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for #
# more details. #
# #
# You should have received a copy of the GNU General Public License along #
# with this program; if not, write to the Free Software Foundation, Inc., 59 #
# Temple Place, Suite 330, Boston, MA 02111-1307 USA #
###############################################################################
"""
:mod:`openlp.core.lib.projector.pjlink2` module provides the PJLink Class 2
updates from PJLink Class 1.
This module only handles the UDP socket functionality. Command/query/status
change messages will still be processed by the PJLink 1 module.
Currently, the only variance is the addition of a UDP "search" command to
query the local network for Class 2 capable projectors,
and UDP "notify" messages from projectors to connected software of status
changes (i.e., power change, input change, error changes).
Differences between Class 1 and Class 2 PJLink specifications are as follows.
New Functionality:
* Search - UDP Query local network for Class 2 capabable projector(s).
* Status - UDP Status change with connected projector(s). Status change
messages consist of:
* Initial projector power up when network communication becomes available
* Lamp off/standby to warmup or on
* Lamp on to cooldown or off/standby
* Input source select change completed
* Error status change (i.e., fan/lamp/temp/cover open/filter/other error(s))
New Commands:
* Query serial number of projector
* Query version number of projector software
* Query model number of replacement lamp
* Query model number of replacement air filter
* Query current projector screen resolution
* Query recommended screen resolution
* Query name of specific input terminal (video source)
* Adjust projector microphone in 1-step increments
* Adjust projector speacker in 1-step increments
Extended Commands:
* Addition of INTERNAL terminal (video source) for a total of 6 types of terminals.
* Number of terminals (video source) has been expanded from [1-9]
to [1-9a-z] (Addition of 26 terminals for each type of input).
See PJLink Class 2 Specifications for details.
http://pjlink.jbmia.or.jp/english/dl_class2.html
Section 5-1 PJLink Specifications
Section 5-5 Guidelines for Input Terminals
"""
import logging
log = logging.getLogger(__name__)
log.debug('pjlink2 loaded')
from PyQt5 import QtCore, QtNetwork
class PJLinkUDP(QtNetwork.QTcpSocket):
"""
Socket service for handling datagram (UDP) sockets.
"""
log.debug('PJLinkUDP loaded')
# Class varialbe for projector list. Should be replaced by ProjectorManager's
# projector list after being loaded there.
projector_list = None
projectors_found = None # UDP search found list

View File

@ -26,7 +26,8 @@ backend for the projector setup.
import logging
# Not all imports used at this time, but keep for future upgrades
from sqlalchemy import Column, types
from sqlalchemy import Table, Column, types, inspect
from sqlalchemy.exc import NoSuchTableError
from sqlalchemy.sql.expression import null
from openlp.core.common.db import drop_columns
@ -44,7 +45,7 @@ def upgrade_1(session, metadata):
"""
Version 1 upgrade - old db might/might not be versioned.
"""
pass
log.debug('Skipping upgrade_1 of projector DB - not used')
def upgrade_2(session, metadata):
@ -53,6 +54,7 @@ def upgrade_2(session, metadata):
Update Projector() table to include new data defined in PJLink version 2 changes
mac_adx: Column(String(18))
serial_no: Column(String(30))
sw_version: Column(String(30))
model_filter: Column(String(30))
@ -61,10 +63,10 @@ def upgrade_2(session, metadata):
:param session: DB session instance
:param metadata: Metadata of current DB
"""
new_op = get_upgrade_op(session)
if 'serial_no' not in [t.name for t in metadata.tables.values()]:
projector_table = Table('projector', metadata, autoload=True)
if 'mac_adx' not in [col.name for col in projector_table.c.values()]:
log.debug("Upgrading projector DB to version '2'")
new_op = get_upgrade_op(session)
new_op.add_column('projector', Column('mac_adx', types.String(18), server_default=null()))
new_op.add_column('projector', Column('serial_no', types.String(30), server_default=null()))
new_op.add_column('projector', Column('sw_version', types.String(30), server_default=null()))

View File

@ -39,6 +39,7 @@ from openlp.core.lib.projector.constants import ERROR_MSG, ERROR_STRING, E_AUTHE
S_INITIALIZE, S_NOT_CONNECTED, S_OFF, S_ON, S_STANDBY, S_WARMUP
from openlp.core.lib.projector.db import ProjectorDB
from openlp.core.lib.projector.pjlink1 import PJLink
from openlp.core.lib.projector.pjlink2 import PJLinkUDP
from openlp.core.ui.projector.editform import ProjectorEditForm
from openlp.core.ui.projector.sourceselectform import SourceSelectTabs, SourceSelectSingle
@ -278,6 +279,10 @@ class ProjectorManager(OpenLPMixin, RegistryMixin, QtWidgets.QWidget, UiProjecto
"""
Manage the projectors.
"""
projector_list = []
pjlink_udp = PJLinkUDP()
pjlink_udp.projector_list = projector_list
def __init__(self, parent=None, projectordb=None):
"""
Basic initialization.
@ -289,7 +294,7 @@ class ProjectorManager(OpenLPMixin, RegistryMixin, QtWidgets.QWidget, UiProjecto
super().__init__(parent)
self.settings_section = 'projector'
self.projectordb = projectordb
self.projector_list = []
self.projector_list = self.__class__.projector_list
self.source_select_form = None
def bootstrap_initialise(self):
@ -987,7 +992,7 @@ class ProjectorItem(QtCore.QObject):
self.poll_time = None
self.socket_timeout = None
self.status = S_NOT_CONNECTED
super(ProjectorItem, self).__init__()
super().__init__()
def not_implemented(function):

View File

@ -32,7 +32,7 @@ from openlp.core.common.db import drop_columns
from openlp.core.lib.db import get_upgrade_op
log = logging.getLogger(__name__)
__version__ = 6
__version__ = 7
# TODO: When removing an upgrade path the ftw-data needs updating to the minimum supported version
@ -52,6 +52,7 @@ def upgrade_1(session, metadata):
:param metadata:
"""
op = get_upgrade_op(session)
metadata.reflect()
if 'media_files_songs' in [t.name for t in metadata.tables.values()]:
op.drop_table('media_files_songs')
op.add_column('media_files', Column('song_id', types.Integer(), server_default=null()))
@ -119,7 +120,7 @@ def upgrade_6(session, metadata):
"""
Version 6 upgrade
This version corrects the errors in upgrades 4 and 5
This version corrects the errors in upgrade 4
"""
op = get_upgrade_op(session)
# Move upgrade 4 to here and correct it (authors_songs table, not songs table)
@ -137,7 +138,17 @@ def upgrade_6(session, metadata):
op.execute('INSERT INTO authors_songs_tmp SELECT author_id, song_id, "" FROM authors_songs')
op.drop_table('authors_songs')
op.rename_table('authors_songs_tmp', 'authors_songs')
def upgrade_7(session, metadata):
"""
Version 7 upgrade
Corrects table error in upgrade 5
"""
# Move upgrade 5 here to correct it
op = get_upgrade_op(session)
metadata.reflect()
if 'songs_songbooks' not in [t.name for t in metadata.tables.values()]:
# Create the mapping table (songs <-> songbooks)
op.create_table(

View File

@ -25,17 +25,26 @@ backend for the SongsUsage plugin
"""
import logging
from sqlalchemy import Column, types
from sqlalchemy import Table, Column, types
from openlp.core.lib.db import get_upgrade_op
log = logging.getLogger(__name__)
__version__ = 1
__version__ = 2
def upgrade_1(session, metadata):
"""
Version 1 upgrade.
Version 1 upgrade
Skip due to possible missed update from a 2.4-2.6 upgrade
"""
pass
def upgrade_2(session, metadata):
"""
Version 2 upgrade.
This upgrade adds two new fields to the songusage database
@ -43,5 +52,7 @@ def upgrade_1(session, metadata):
:param metadata: SQLAlchemy MetaData object
"""
op = get_upgrade_op(session)
op.add_column('songusage_data', Column('plugin_name', types.Unicode(20), server_default=''))
op.add_column('songusage_data', Column('source', types.Unicode(10), server_default=''))
songusage_table = Table('songusage_data', metadata, autoload=True)
if 'plugin_name' not in [col.name for col in songusage_table.c.values()]:
op.add_column('songusage_data', Column('plugin_name', types.Unicode(20), server_default=''))
op.add_column('songusage_data', Column('source', types.Unicode(10), server_default=''))