vj4/model/system.py

Summary

Maintainability
A
0 mins
Test Coverage
import random

from pymongo import errors
from pymongo import ReturnDocument

from vj4 import db
from vj4 import error
from vj4.util import argmethod


EXPECTED_DB_VERSION = 1


@argmethod.wrap
async def inc_user_counter():
  """Increments the user counter.

  Returns:
    Integer value after increment.
  """
  coll = db.coll('system')
  doc = await coll.find_one_and_update(filter={'_id': 'user_counter'},
                                       update={'$inc': {'value': 1}},
                                       upsert=True,
                                       return_document=ReturnDocument.AFTER)
  return doc['value']


@argmethod.wrap
async def inc_pid_counter():
  """Increments the problem ID counter.

  Returns:
    Integer value before increment.
  """
  coll = db.coll('system')
  await coll.update_one(filter={'_id': 'pid_counter'},
                        update={'$setOnInsert': {'value': 1000}}, upsert=True)
  doc = await coll.find_one_and_update(filter={'_id': 'pid_counter'},
                                       update={'$inc': {'value': 1}})
  return doc['value']


async def acquire_lock(lock_name: str):
  lock_value = random.randint(1, 0xFFFFFFFF)
  coll = db.coll('system')
  try:
    await coll.update_one(filter={'_id': 'lock_' + lock_name, 'value': 0},
                          update={'$set': {'value': lock_value}},
                          upsert=True)
  except errors.DuplicateKeyError:
    return None
  return lock_value


async def release_lock(lock_name: str, lock_value: int):
  coll = db.coll('system')
  result = await coll.update_one(filter={'_id': 'lock_' + lock_name, 'value': lock_value},
                                 update={'$set': {'value': 0}})
  if result.matched_count == 0:
    return None
  return True


async def release_lock_anyway(lock_name: str):
  coll = db.coll('system')
  await coll.update_one(filter={'_id': 'lock_' + lock_name},
                        update={'$set': {'value': 0}})
  return True


async def acquire_upgrade_lock():
  lock = await acquire_lock('upgrade')
  if not lock:
    raise error.UpgradeLockAcquireError()
  return lock


async def release_upgrade_lock(lock: int):
  success = await release_lock('upgrade', lock)
  if not success:
    raise error.UpgradeLockReleaseError()
  return True


@argmethod.wrap
async def release_upgrade_lock_anyway():
  return await release_lock_anyway('upgrade')


@argmethod.wrap
async def get_db_version():
  coll = db.coll('system')
  doc = await coll.find_one({'_id': 'db_version'})
  if doc is None:
    return 0
  else:
    return doc['value']


async def set_db_version(version: int):
  coll = db.coll('system')
  result = await coll.update_one(filter={'_id': 'db_version'},
                                 update={'$set': {'value': version}},
                                 upsert=True)
  return result.modified_count


async def ensure_db_version(allowed_version=None):
  if allowed_version is None:
    allowed_version = EXPECTED_DB_VERSION
  current_version = await get_db_version()
  if current_version != allowed_version:
    raise error.DatabaseVersionMismatchError(current_version, allowed_version)


@argmethod.wrap
async def setup():
  """
  Set up for fresh install
  """
  coll = db.coll('system')
  fdoc = await coll.find_one({'_id': 'user_counter'})
  if fdoc:
    # skip if not fresh install
    return
  await set_db_version(EXPECTED_DB_VERSION)


@argmethod.wrap
async def ensure_indexes():
  coll = db.coll('system')
  await coll.update_one(filter={'_id': 'user_counter'},
                        update={'$setOnInsert': {'value': 1}},
                        upsert=True)


if __name__ == '__main__':
  argmethod.invoke_by_args()