conan-io/conan

View on GitHub
conans/client/store/localdb.py

Summary

Maintainability
A
1 hr
Test Coverage
import os
import sqlite3
from contextlib import contextmanager
from sqlite3 import OperationalError

from conans.errors import ConanException
from conans.util import encrypt

REMOTES_USER_TABLE = "users_remotes"


class LocalDB(object):

    def __init__(self, dbfile, encryption_key):
        self.dbfile = dbfile
        self.encryption_key = encryption_key

    def _encode(self, value):
        if value and self.encryption_key:
            return encrypt.encode(value, self.encryption_key)
        return value

    def _decode(self, value):
        if value and self.encryption_key:
            return encrypt.decode(value, self.encryption_key)
        return value

    def clean(self):
        with self._connect() as connection:
            try:
                cursor = connection.cursor()
                cursor.execute("DELETE FROM %s" % REMOTES_USER_TABLE)
                try:
                    # https://github.com/ghaering/pysqlite/issues/109
                    connection.isolation_level = None
                    cursor.execute('VACUUM')  # Make sure the DB is cleaned, drop doesn't do that
                except OperationalError:
                    pass
            except Exception as e:
                raise ConanException("Could not initialize local sqlite database", e)

    @staticmethod
    def create(dbfile, encryption_key=None):
        # Create the database file if it doesn't exist
        if not os.path.exists(dbfile):
            par = os.path.dirname(dbfile)
            if not os.path.exists(par):
                os.makedirs(par)
            db = open(dbfile, 'w+')
            db.close()

        db = LocalDB(dbfile, encryption_key=encryption_key)
        with db._connect() as connection:
            try:
                cursor = connection.cursor()
                cursor.execute("create table if not exists %s "
                               "(remote_url TEXT UNIQUE, user TEXT, "
                               "token TEXT, refresh_token TEXT)" % REMOTES_USER_TABLE)
            except Exception as e:
                message = "Could not initialize local sqlite database"
                raise ConanException(message, e)

        return db

    @contextmanager
    def _connect(self):
        connection = sqlite3.connect(self.dbfile, detect_types=sqlite3.PARSE_DECLTYPES)
        connection.text_factory = str
        try:
            yield connection
        finally:
            connection.close()

    def get_login(self, remote_url):
        """ Returns login credentials. This method is also in charge of expiring them. """
        with self._connect() as connection:
            try:
                statement = connection.cursor()
                statement.execute('select user, token, refresh_token from %s where remote_url="%s"'
                                  % (REMOTES_USER_TABLE, remote_url))
                rs = statement.fetchone()
                if not rs:
                    return None, None, None
                name = rs[0]
                token = self._decode(rs[1])
                refresh_token = self._decode(rs[2])
                return name, token, refresh_token
            except Exception:
                raise ConanException("Couldn't read login\n Try removing '%s' file" % self.dbfile)

    def get_username(self, remote_url):
        return self.get_login(remote_url)[0]

    def store(self, user, token, refresh_token, remote_url):
        """ Login is a tuple of (user, token) """
        with self._connect() as connection:
            try:
                token = self._encode(token)
                refresh_token = self._encode(refresh_token)
                statement = connection.cursor()
                statement.execute("INSERT OR REPLACE INTO %s (remote_url, user, token, "
                                  "refresh_token) "
                                  "VALUES (?, ?, ?, ?)" % REMOTES_USER_TABLE,
                                  (remote_url, user, token, refresh_token))
                connection.commit()
            except Exception as e:
                raise ConanException("Could not store credentials %s" % str(e))