from unittest.mock import Mock
from ..shared import crypto
import io
import logging
import queue
import shutil
import unittest
import unittest.mock
import pathlib
import tempfile

from s5.server.server import S5Server

from . import client
from . import items

from .callbacks import TestCallbacks

from ..shared.conventions import ITEM_TYPES

logger = logging.getLogger(__name__)

TEMP_DIRECTORY = pathlib.Path(tempfile.mkdtemp("Test" + __name__))

def setUpModule():
    assert TEMP_DIRECTORY.exists()
    assert list(TEMP_DIRECTORY.iterdir()) == []

def tearDownModule():
    shutil.rmtree(str(TEMP_DIRECTORY))

class TestReloadClient(unittest.TestCase):

    def test_storeLoad(self):
        dir = TEMP_DIRECTORY / "reloadClient"
        cb = TestCallbacks()
        cb.dataDir = dir
        c = client.S5Client(cb)
        c.initializeNew()
        itm = c.newItem('urn:x-s5:file(text/plain)')

        itm.saveNewContent('Test S5 Item'.encode('UTF-8'))

        itemId = itm.itemId

        del itm
        del c

        c = client.S5Client(cb)

        itm = c.getItem(itemId)

        self.assertEqual(itm.getContentBytes().decode('utf-8'), "Test S5 Item")

class TestS5Client(unittest.TestCase):

    @classmethod
    def setUpClass(cls):
        cls.c = TEMP_DIRECTORY / "clientDir"
        assert not cls.c.exists()
        cb = TestCallbacks()
        cb.dataDir = cls.c
        c = client.S5Client(cb)
        c.initializeNew()

    def test_initUserKey(self):
        cb = TestCallbacks()
        cb.dataDir = TEMP_DIRECTORY / 'UserKeyTest'
        c = client.S5Client(cb)
        c.initializeNew()
        c._generateAndStoreUserKey()
        c.userKey = None
        k = c._getUserPrivateKey()
        self.assertEqual(k['type'], 'private')

    def test_newItem(self):
        cb = TestCallbacks()
        cb.dataDir = self.c
        c = client.S5Client(cb)

        itm = c.newItem('urn:x-s5:file(text/plain)')

        itm.saveNewContent('Test S5 Item'.encode('UTF-8'))

        itemId = itm.itemId

        i2 = c.getItem(itemId)
        b = i2.getContentBytes()
        s = b.decode("UTF-8")
        self.assertEqual(s, "Test S5 Item")

    def test_newItemNotSaved(self):
        d = TEMP_DIRECTORY / 'NotSavedTest'
        cb = TestCallbacks()
        cb.dataDir = d
        c = client.S5Client(cb)

        c.initializeNew()
        itm = c.newItem(ITEM_TYPES.FILE)
        itemId = itm.itemId

        del c
        del itm

        c = client.S5Client(cb)
        itm = c.getItem(itemId)

        itm.saveNewContent(b'Test Data')

        del c
        del itm

        c = client.S5Client(cb)
        itm = c.getItem(itemId)

        self.assertEqual(b'Test Data', itm.getContentBytes())

    def test_itemNotExisting(self):
        cb = TestCallbacks()
        cb.dataDir = self.c
        c = client.S5Client(cb)
        with self.assertRaises(KeyError):
            itm = c.getItem('35')

    def test_itemWeakRef(self):
        cb = TestCallbacks()
        cb.dataDir = self.c
        c = client.S5Client(cb)
        itm = c.newItem('urn:x-s5:file:text/plain')

        itm.saveNewContent('Test S5 Item'.encode('UTF-8'))

        itemId = itm.itemId
        itemObjectId = id(itm)

        itm2 = c.getItem(itemId)
        self.assertEqual(id(itm2), itemObjectId)

        del itm
        del itm2
        # GarbageCollection should remove the item

        itm = c.getItem(itemId)

        self.assertNotEqual(id(itm), itemObjectId)

class TestCatalogMixin(unittest.TestCase):

    class CatalogClient(client.CatalogMixin, client.S5Client):
        pass

    def test_itemByName(self):
        tmp = TEMP_DIRECTORY / 'TestCatalog'
        cb = TestCallbacks()
        cb.dataDir = tmp
        client = self.CatalogClient(cb)
        client.initializeNew()
        i = client.newItem(ITEM_TYPES.JSON)
        i.setContent([5])
        i.save()
        client.putItemByPath(i, ['JSON1'])

        with self.assertRaises(KeyError):
            client.getItemByPath(['unkown name'])

        i = client.newItem(ITEM_TYPES.JSON)
        i.setContent([1, 2, 3])
        i.save()

        with self.assertRaises(KeyError):
            # can not create parents without flag
            client.putItemByPath(i, ['level1', 'level2', 'level3'])

        client.putItemByPath(
            i, ['level1', 'level2', 'level3'], create_parents=True)

        i = client.newItem(ITEM_TYPES.JSON)
        i.setContent([5])
        i.save()

        with self.assertRaises(ValueError):
            # can not overwrite without Flag
            client.putItemByPath(i, ['level1', 'level2', 'level3'])

        client.putItemByPath(
            i, ['level1', 'level2', 'level3'], overwrite_existing=True)

        del client
        del i

        cb = TestCallbacks()
        cb.dataDir = tmp
        client = self.CatalogClient(cb)

        i = client.getItemByPath(['level1'])

        self.assertEqual(i.getContentType(), ITEM_TYPES.MAP)

        i = i['level2']['level3']

        self.assertEqual(i.getContentType(), ITEM_TYPES.JSON)
        self.assertEqual(i.getContent()[0], 5)

class TestIterationMixin(unittest.TestCase):

    class IterateCatalogClient(client.IterationMixin, client.CatalogMixin,
                               client.S5Client):
        pass

    def test_walkItemTree(self):
        cb = TestCallbacks()
        cb.dataDir = TEMP_DIRECTORY / 'TestTreeToWalk'
        c = self.IterateCatalogClient(cb)
        c.initializeNew()

        i = c.newItem(ITEM_TYPES.JSON)
        i.setContent([1, 2, 3])
        i.save()
        c.putItemByPath(i, ['d1', 'd1.1', 'f1.1.1'], create_parents=True)
        c.putItemByPath(i, ['d1', 'd1.2', 'f1.2.1'], create_parents=True)
        c.putItemByPath(i, ['d1', 'd1.2', 'f1.2.2'], create_parents=True)
        c.putItemByPath(i, ['d2', 'f2.1'], create_parents=True)
        c.putItemByPath(c.getRootItem(), ['d1', 'd1.1', 'rootLoop'])

        allPaths = []
        path = []

        def visit(key, down, up, **_):
            if key is None:
                key = 'root'
            if down:
                path.append(key)
            if down and up:
                # append leafes
                allPaths.append(list(path))
            if up:
                path.pop()
            return True

        def handleLoop(key, **_):
            allPaths.append(path + [key, "LOOOP"])
            return False

        c.walkItemTree(c.getRootItem(), visit, handleLoop)

        self.assertEqual(sorted(allPaths),
                         [['root', 'd1', 'd1.1', 'f1.1.1'],
                          ['root', 'd1', 'd1.1', 'rootLoop', 'LOOOP'],
                          ['root', 'd1', 'd1.2', 'f1.2.1'],
                          ['root', 'd1', 'd1.2', 'f1.2.2'],
                          ['root', 'd2', 'f2.1']])

    def test_iterateItemTree(self):
        """ Test if iterateItemTree iterates, and all ids onbly once """
        cb = TestCallbacks()
        cb.dataDir = (TEMP_DIRECTORY / 'TestTreeToIterateOver')
        c = self.IterateCatalogClient(cb)
        c.initializeNew()
        i = c.newItem(ITEM_TYPES.JSON)
        i.setContent([1, 2, 3])
        i.save()
        c.putItemByPath(i, ['d1', 'd1.1', 'f1.1.1'], create_parents=True)
        c.putItemByPath(i, ['d1', 'd1.2', 'f1.2.1'], create_parents=True)
        c.putItemByPath(i, ['d1', 'd1.2', 'f1.2.2'], create_parents=True)
        c.putItemByPath(i, ['d2', 'f2.1'], create_parents=True)
        r = c.getRootItem()
        c.putItemByPath(r, ['d1', 'd1.1', 'rootLoop'])
        it = c.iterateItemTree(r)

        self.assertEqual(next(it), r.itemId)

        self.assertIn(next(it), [r['d1'].itemId, r['d2'].itemId])
        l = list(it)
        self.assertEqual(len(l), 7)  # d2 d1.1 d1.2 f1.1.1 f1.2.1 f1.2.2 f2.1


#!/usr/bin/env python
# coding: utf-8

from __future__ import (unicode_literals, division, absolute_import,
                        print_function)

import hashlib, importlib, os, sys
from datetime import datetime
from functools import partial
from time import mktime

from calibre.constants import islinux, isosx, iswindows
from calibre.devices.usbms.driver import debug_print
from calibre.ebooks.BeautifulSoup import BeautifulSoup
from calibre.gui2.dialogs.message_box import MessageBox
from calibre.gui2 import show_restart_warning
from calibre.gui2.ui import get_gui
from calibre.utils.config import JSONConfig

from calibre_plugins.marvin_manager.appearance import (AnnotationsAppearance,
    default_elements, default_timestamp)

from calibre_plugins.marvin_manager.common_utils import (Logger,
    existing_annotations, get_cc_mapping, get_icon, move_annotations, set_cc_mapping)

try:
    from PyQt5.Qt import (Qt, QCheckBox, QComboBox, QFont, QFontMetrics, QFrame,
                          QGridLayout, QGroupBox, QFileDialog, QIcon,
                          QLabel, QLineEdit, QPlainTextEdit, QPushButton,
                          QSizePolicy, QSpacerItem, QThread, QTimer, QToolButton,
                          QVBoxLayout, QWidget,
                          pyqtSignal)
except ImportError:
    from PyQt4.Qt import (Qt, QCheckBox, QComboBox, QFont, QFontMetrics, QFrame,
                          QGridLayout, QGroupBox, QFileDialog, QIcon,
                          QLabel, QLineEdit, QPlainTextEdit, QPushButton,
                          QSizePolicy, QSpacerItem, QThread, QTimer, QToolButton,
                          QVBoxLayout, QWidget,
                          pyqtSignal)

plugin_prefs = JSONConfig('plugins/Marvin XD')

class ConfigWidget(QWidget, Logger):
    '''
    Config dialog for Marvin Manager
    '''

    WIZARD_PROFILES = {
        'Annotations': {
            'label': 'mm_annotations',
            'datatype': 'comments',
            'display': {},
            'is_multiple': False
        },
        'Collections': {
            'label': 'mm_collections',
            'datatype': 'text',
            'display': {u'is_names': False},
            'is_multiple': True
        },
        'Last read': {
            'label': 'mm_date_read',
            'datatype': 'datetime',
            'display': {},
            'is_multiple': False
        },
        'Locked': {
            'label': 'mm_locked',
            'datatype': 'bool',
            'display': {},
            'is_multiple': False
        },
        'Progress': {
            'label': 'mm_progress',
            'datatype': 'float',
            'display': {u'number_format': u'{0:.0f}%'},
            'is_multiple': False
        },
        'Read': {
            'label': 'mm_read',
            'datatype': 'bool',
            'display': {},
            'is_multiple': False
        },
        'Reading list': {
            'label': 'mm_reading_list',
            'datatype': 'bool',
            'display': {},
            'is_multiple': False
        },
        'Word count': {
            'label': 'mm_word_count',
            'datatype': 'int',
            'display': {u'number_format': u'{0:n}'},
            'is_multiple': False
        }
    }

    def __init__(self, plugin_action):
        QWidget.__init__(self)
        self.parent = plugin_action

        self.gui = get_gui()
        self.icon = plugin_action.icon
        self.opts = plugin_action.opts
        self.prefs = plugin_prefs
        self.resources_path = plugin_action.resources_path
        self.verbose = plugin_action.verbose

        self.restart_required = False

        self._log_location()

        self.l = QGridLayout()
        self.setLayout(self.l)
        self.column1_layout = QVBoxLayout()
        self.l.addLayout(self.column1_layout, 0, 0)
        self.column2_layout = QVBoxLayout()
        self.l.addLayout(self.column2_layout, 0, 1)

        # ----------------------------- Column 1 -----------------------------
        # ~~~~~~~~ Create the Custom fields options group box ~~~~~~~~
        self.cfg_custom_fields_gb = QGroupBox(self)
        self.cfg_custom_fields_gb.setTitle('Custom column assignments')
        self.column1_layout.addWidget(self.cfg_custom_fields_gb)

        self.cfg_custom_fields_qgl = QGridLayout(self.cfg_custom_fields_gb)
        current_row = 0

        # ++++++++ Labels + HLine ++++++++
        self.marvin_source_label = QLabel("Marvin source")
        self.cfg_custom_fields_qgl.addWidget(self.marvin_source_label, current_row, 0)
        self.calibre_destination_label = QLabel("calibre destination")
        self.cfg_custom_fields_qgl.addWidget(self.calibre_destination_label, current_row, 1)
        current_row += 1
        self.sd_hl = QFrame(self.cfg_custom_fields_gb)
        self.sd_hl.setFrameShape(QFrame.HLine)
        self.sd_hl.setFrameShadow(QFrame.Raised)
        self.cfg_custom_fields_qgl.addWidget(self.sd_hl, current_row, 0, 1, 3)
        current_row += 1

        # ++++++++ Annotations ++++++++
        self.cfg_annotations_label = QLabel('Annotations')
        self.cfg_annotations_label.setAlignment(Qt.AlignLeft)
        self.cfg_custom_fields_qgl.addWidget(self.cfg_annotations_label, current_row, 0)

        self.annotations_field_comboBox = QComboBox(self.cfg_custom_fields_gb)
        self.annotations_field_comboBox.setObjectName('annotations_field_comboBox')
        self.annotations_field_comboBox.setToolTip('Select a custom column to store Marvin annotations')
        self.cfg_custom_fields_qgl.addWidget(self.annotations_field_comboBox, current_row, 1)

        self.cfg_highlights_wizard = QToolButton()
        self.cfg_highlights_wizard.setIcon(QIcon(I('wizard.png')))
        self.cfg_highlights_wizard.setToolTip("Create a custom column to store Marvin annotations")
        self.cfg_highlights_wizard.clicked.connect(partial(self.launch_cc_wizard, 'Annotations'))
        self.cfg_custom_fields_qgl.addWidget(self.cfg_highlights_wizard, current_row, 2)
        current_row += 1

        # ++++++++ Collections ++++++++
        self.cfg_collections_label = QLabel('Collections')
        self.cfg_collections_label.setAlignment(Qt.AlignLeft)
        self.cfg_custom_fields_qgl.addWidget(self.cfg_collections_label, current_row, 0)

        self.collection_field_comboBox = QComboBox(self.cfg_custom_fields_gb)
        self.collection_field_comboBox.setObjectName('collection_field_comboBox')
        self.collection_field_comboBox.setToolTip('Select a custom column to store Marvin collection assignments')
        self.cfg_custom_fields_qgl.addWidget(self.collection_field_comboBox, current_row, 1)

        self.cfg_collections_wizard = QToolButton()
        self.cfg_collections_wizard.setIcon(QIcon(I('wizard.png')))
        self.cfg_collections_wizard.setToolTip("Create a custom column for Marvin collection assignments")
        self.cfg_collections_wizard.clicked.connect(partial(self.launch_cc_wizard, 'Collections'))
        self.cfg_custom_fields_qgl.addWidget(self.cfg_collections_wizard, current_row, 2)
        current_row += 1

        # ++++++++ Last read ++++++++
        self.cfg_date_read_label = QLabel("Last read")
        self.cfg_date_read_label.setAlignment(Qt.AlignLeft)
        self.cfg_custom_fields_qgl.addWidget(self.cfg_date_read_label, current_row, 0)

        self.date_read_field_comboBox = QComboBox(self.cfg_custom_fields_gb)
        self.date_read_field_comboBox.setObjectName('date_read_field_comboBox')
        self.date_read_field_comboBox.setToolTip('Select a custom column to store Last read date')
        self.cfg_custom_fields_qgl.addWidget(self.date_read_field_comboBox, current_row, 1)

        self.cfg_collections_wizard = QToolButton()
        self.cfg_collections_wizard.setIcon(QIcon(I('wizard.png')))
        self.cfg_collections_wizard.setToolTip("Create a custom column to store Last read date")
        self.cfg_collections_wizard.clicked.connect(partial(self.launch_cc_wizard, 'Last read'))
        self.cfg_custom_fields_qgl.addWidget(self.cfg_collections_wizard, current_row, 2)
        current_row += 1

        # ++++++++ Locked ++++++++
        self.cfg_locked_label = QLabel("Locked")
        self.cfg_locked_label.setAlignment(Qt.AlignLeft)
        self.cfg_custom_fields_qgl.addWidget(self.cfg_locked_label, current_row, 0)

        self.locked_field_comboBox = QComboBox(self.cfg_custom_fields_gb)
        self.locked_field_comboBox.setObjectName('locked_field_comboBox')
        self.locked_field_comboBox.setToolTip('Select a custom column to store Locked status')
        self.cfg_custom_fields_qgl.addWidget(self.locked_field_comboBox, current_row, 1)

        self.cfg_locked_wizard = QToolButton()
        self.cfg_locked_wizard.setIcon(QIcon(I('wizard.png')))
        self.cfg_locked_wizard.setToolTip("Create a custom column to store Locked status")
        self.cfg_locked_wizard.clicked.connect(partial(self.launch_cc_wizard, 'Locked'))
        self.cfg_custom_fields_qgl.addWidget(self.cfg_locked_wizard, current_row, 2)
        current_row += 1

        # ++++++++ Progress ++++++++
        self.cfg_progress_label = QLabel('Progress')
        self.cfg_progress_label.setAlignment(Qt.AlignLeft)
        self.cfg_custom_fields_qgl.addWidget(self.cfg_progress_label, current_row, 0)

        self.progress_field_comboBox = QComboBox(self.cfg_custom_fields_gb)
        self.progress_field_comboBox.setObjectName('progress_field_comboBox')
        self.progress_field_comboBox.setToolTip('Select a custom column to store Marvin reading progress')
        self.cfg_custom_fields_qgl.addWidget(self.progress_field_comboBox, current_row, 1)

        self.cfg_progress_wizard = QToolButton()
        self.cfg_progress_wizard.setIcon(QIcon(I('wizard.png')))
        self.cfg_progress_wizard.setToolTip("Create a custom column to store Marvin reading progress")
        self.cfg_progress_wizard.clicked.connect(partial(self.launch_cc_wizard, 'Progress'))
        self.cfg_custom_fields_qgl.addWidget(self.cfg_progress_wizard, current_row, 2)
        current_row += 1

        # ++++++++ Read flag ++++++++
        self.cfg_read_label = QLabel('Read')
        self.cfg_read_label.setAlignment(Qt.AlignLeft)
        self.cfg_custom_fields_qgl.addWidget(self.cfg_read_label, current_row, 0)

        self.read_field_comboBox = QComboBox(self.cfg_custom_fields_gb)
        self.read_field_comboBox.setObjectName('read_field_comboBox')
        self.read_field_comboBox.setToolTip('Select a custom column to store Marvin Read status')
        self.cfg_custom_fields_qgl.addWidget(self.read_field_comboBox, current_row, 1)

        self.cfg_read_wizard = QToolButton()
        self.cfg_read_wizard.setIcon(QIcon(I('wizard.png')))
        self.cfg_read_wizard.setToolTip("Create a custom column to store Marvin Read status")
        self.cfg_read_wizard.clicked.connect(partial(self.launch_cc_wizard, 'Read'))
        self.cfg_custom_fields_qgl.addWidget(self.cfg_read_wizard, current_row, 2)
        current_row += 1

        # ++++++++ Reading list flag ++++++++
        self.cfg_reading_list_label = QLabel('Reading list')
        self.cfg_reading_list_label.setAlignment(Qt.AlignLeft)
        self.cfg_custom_fields_qgl.addWidget(self.cfg_reading_list_label, current_row, 0)

        self.reading_list_field_comboBox = QComboBox(self.cfg_custom_fields_gb)
        self.reading_list_field_comboBox.setObjectName('reading_list_field_comboBox')
        self.reading_list_field_comboBox.setToolTip('Select a custom column to store Marvin Reading list status')
        self.cfg_custom_fields_qgl.addWidget(self.reading_list_field_comboBox, current_row, 1)

        self.cfg_reading_list_wizard = QToolButton()
        self.cfg_reading_list_wizard.setIcon(QIcon(I('wizard.png')))
        self.cfg_reading_list_wizard.setToolTip("Create a custom column to store Marvin Reading list status")
        self.cfg_reading_list_wizard.clicked.connect(partial(self.launch_cc_wizard, 'Reading list'))
        self.cfg_custom_fields_qgl.addWidget(self.cfg_reading_list_wizard, current_row, 2)
        current_row += 1

        # ++++++++ Word count ++++++++
        self.cfg_word_count_label = QLabel('Word count')
        self.cfg_word_count_label.setAlignment(Qt.AlignLeft)
        self.cfg_custom_fields_qgl.addWidget(self.cfg_word_count_label, current_row, 0)

        self.word_count_field_comboBox = QComboBox(self.cfg_custom_fields_gb)
        self.word_count_field_comboBox.setObjectName('word_count_field_comboBox')
        self.word_count_field_comboBox.setToolTip('Select a custom column to store Marvin word counts')
        self.cfg_custom_fields_qgl.addWidget(self.word_count_field_comboBox, current_row, 1)

        self.cfg_word_count_wizard = QToolButton()
        self.cfg_word_count_wizard.setIcon(QIcon(I('wizard.png')))
        self.cfg_word_count_wizard.setToolTip("Create a custom column to store Marvin word counts")
        self.cfg_word_count_wizard.clicked.connect(partial(self.launch_cc_wizard, 'Word count'))
        self.cfg_custom_fields_qgl.addWidget(self.cfg_word_count_wizard, current_row, 2)
        current_row += 1

        self.spacerItem1 = QSpacerItem(20, 20, QSizePolicy.Minimum, QSizePolicy.Expanding)
        self.column1_layout.addItem(self.spacerItem1)

        # ----------------------------- Column 2 -----------------------------
        # ~~~~~~~~ Create the CSS group box ~~~~~~~~
        self.cfg_css_options_gb = QGroupBox(self)
        self.cfg_css_options_gb.setTitle('CSS')
        self.column2_layout.addWidget(self.cfg_css_options_gb)
        self.cfg_css_options_qgl = QGridLayout(self.cfg_css_options_gb)

        current_row = 0

        # ++++++++ Annotations appearance ++++++++
        self.annotations_icon = QIcon(os.path.join(self.resources_path, 'icons', 'annotations_hiliter.png'))
        self.cfg_annotations_appearance_toolbutton = QToolButton()
        self.cfg_annotations_appearance_toolbutton.setIcon(self.annotations_icon)
        self.cfg_annotations_appearance_toolbutton.clicked.connect(self.configure_appearance)
        self.cfg_css_options_qgl.addWidget(self.cfg_annotations_appearance_toolbutton, current_row, 0)
        self.cfg_annotations_label = ClickableQLabel("Book notes, Bookmark notes and Annotations")
        self.cfg_annotations_label.clicked.connect(self.configure_appearance)
        self.cfg_css_options_qgl.addWidget(self.cfg_annotations_label, current_row, 1)
        current_row += 1

        # ++++++++ Injected CSS ++++++++
        self.css_editor_icon = QIcon(I('format-text-heading.png'))
        self.cfg_css_editor_toolbutton = QToolButton()
        self.cfg_css_editor_toolbutton.setIcon(self.css_editor_icon)
        self.cfg_css_editor_toolbutton.clicked.connect(self.edit_css)
        self.cfg_css_options_qgl.addWidget(self.cfg_css_editor_toolbutton, current_row, 0)
        self.cfg_css_editor_label = ClickableQLabel("Articles, Vocabulary")
        self.cfg_css_editor_label.clicked.connect(self.edit_css)
        self.cfg_css_options_qgl.addWidget(self.cfg_css_editor_label, current_row, 1)

        """
        # ~~~~~~~~ Create the Dropbox syncing group box ~~~~~~~~
        self.cfg_dropbox_syncing_gb = QGroupBox(self)
        self.cfg_dropbox_syncing_gb.setTitle('Dropbox')
        self.column2_layout.addWidget(self.cfg_dropbox_syncing_gb)
        self.cfg_dropbox_syncing_qgl = QGridLayout(self.cfg_dropbox_syncing_gb)
        current_row = 0

        # ++++++++ Syncing enabled checkbox ++++++++
        self.dropbox_syncing_checkbox = QCheckBox('Enable Dropbox updates')
        self.dropbox_syncing_checkbox.setObjectName('dropbox_syncing')
        self.dropbox_syncing_checkbox.setToolTip('Refresh custom column content from Marvin metadata')
        self.cfg_dropbox_syncing_qgl.addWidget(self.dropbox_syncing_checkbox,
            current_row, 0, 1, 3)
        current_row += 1

        # ++++++++ Dropbox folder picker ++++++++
        self.dropbox_folder_icon = QIcon(os.path.join(self.resources_path, 'icons', 'dropbox.png'))
        self.cfg_dropbox_folder_toolbutton = QToolButton()
        self.cfg_dropbox_folder_toolbutton.setIcon(self.dropbox_folder_icon)
        self.cfg_dropbox_folder_toolbutton.setToolTip("Specify Dropbox folder location on your computer")
        self.cfg_dropbox_folder_toolbutton.clicked.connect(self.select_dropbox_folder)
        self.cfg_dropbox_syncing_qgl.addWidget(self.cfg_dropbox_folder_toolbutton,
            current_row, 1)

        # ++++++++ Dropbox location lineedit ++++++++
        self.dropbox_location_lineedit = QLineEdit()
        self.dropbox_location_lineedit.setPlaceholderText("Dropbox folder location")
        self.cfg_dropbox_syncing_qgl.addWidget(self.dropbox_location_lineedit,
            current_row, 2)
        """

        # ~~~~~~~~ Create the General options group box ~~~~~~~~
        self.cfg_runtime_options_gb = QGroupBox(self)
        self.cfg_runtime_options_gb.setTitle('General options')
        self.column2_layout.addWidget(self.cfg_runtime_options_gb)
        self.cfg_runtime_options_qvl = QVBoxLayout(self.cfg_runtime_options_gb)

        # ++++++++ Temporary markers: Duplicates ++++++++
        self.duplicate_markers_checkbox = QCheckBox('Apply temporary markers to duplicate books')
        self.duplicate_markers_checkbox.setObjectName('apply_markers_to_duplicates')
        self.duplicate_markers_checkbox.setToolTip('Books with identical content will be flagged in the Library window')
        self.cfg_runtime_options_qvl.addWidget(self.duplicate_markers_checkbox)

        # ++++++++ Temporary markers: Updated ++++++++
        self.updated_markers_checkbox = QCheckBox('Apply temporary markers to books with updated content')
        self.updated_markers_checkbox.setObjectName('apply_markers_to_updated')
        self.updated_markers_checkbox.setToolTip('Books with updated content will be flagged in the Library window')
        self.cfg_runtime_options_qvl.addWidget(self.updated_markers_checkbox)

        # ++++++++ Auto refresh checkbox ++++++++
        self.auto_refresh_checkbox = QCheckBox('Automatically refresh custom column content')
        self.auto_refresh_checkbox.setObjectName('auto_refresh_at_startup')
        self.auto_refresh_checkbox.setToolTip('Update calibre custom column when Marvin XD is opened')
        self.cfg_runtime_options_qvl.addWidget(self.auto_refresh_checkbox)

        # ++++++++ Progress as percentage checkbox ++++++++
        self.reading_progress_checkbox = QCheckBox('Show reading progress as percentage')
        self.reading_progress_checkbox.setObjectName('show_progress_as_percentage')
        self.reading_progress_checkbox.setToolTip('Display percentage in Progress column')
        self.cfg_runtime_options_qvl.addWidget(self.reading_progress_checkbox)

        # ~~~~~~~~ Create the Debug options group box ~~~~~~~~
        self.cfg_debug_options_gb = QGroupBox(self)
        self.cfg_debug_options_gb.setTitle('Debug options')
        self.column2_layout.addWidget(self.cfg_debug_options_gb)
        self.cfg_debug_options_qvl = QVBoxLayout(self.cfg_debug_options_gb)

        # ++++++++ Debug logging checkboxes ++++++++
        self.debug_plugin_checkbox = QCheckBox('Enable debug logging for Marvin XD')
        self.debug_plugin_checkbox.setObjectName('debug_plugin_checkbox')
        self.debug_plugin_checkbox.setToolTip('Print plugin diagnostic messages to console')
        self.cfg_debug_options_qvl.addWidget(self.debug_plugin_checkbox)

        self.debug_libimobiledevice_checkbox = QCheckBox('Enable debug logging for libiMobileDevice')
        self.debug_libimobiledevice_checkbox.setObjectName('debug_libimobiledevice_checkbox')
        self.debug_libimobiledevice_checkbox.setToolTip('Print libiMobileDevice diagnostic messages to console')
        self.cfg_debug_options_qvl.addWidget(self.debug_libimobiledevice_checkbox)

        self.spacerItem2 = QSpacerItem(20, 20, QSizePolicy.Minimum, QSizePolicy.Expanding)
        self.column2_layout.addItem(self.spacerItem2)

        # ~~~~~~~~ End of construction zone ~~~~~~~~
        self.resize(self.sizeHint())

        # ~~~~~~~~ Populate/restore config options ~~~~~~~~
        #  Annotations comboBox
        self.populate_annotations()
        self.populate_collections()
        self.populate_date_read()
        self.populate_locked()
        self.populate_progress()
        self.populate_read()
        self.populate_reading_list()
        self.populate_word_count()

        """
        # Restore Dropbox settings, hook changes
        dropbox_syncing = self.prefs.get('dropbox_syncing', False)
        self.dropbox_syncing_checkbox.setChecked(dropbox_syncing)
        self.set_dropbox_syncing(dropbox_syncing)
        self.dropbox_syncing_checkbox.clicked.connect(partial(self.set_dropbox_syncing))
        self.dropbox_location_lineedit.setText(self.prefs.get('dropbox_folder', ''))
        """

        # Restore general settings
        self.duplicate_markers_checkbox.setChecked(self.prefs.get('apply_markers_to_duplicates', True))
        self.updated_markers_checkbox.setChecked(self.prefs.get('apply_markers_to_updated', True))
        self.auto_refresh_checkbox.setChecked(self.prefs.get('auto_refresh_at_startup', False))
        self.reading_progress_checkbox.setChecked(self.prefs.get('show_progress_as_percentage', False))

        # Restore debug settings, hook changes
        self.debug_plugin_checkbox.setChecked(self.prefs.get('debug_plugin', False))
        self.debug_plugin_checkbox.stateChanged.connect(self.set_restart_required)
        self.debug_libimobiledevice_checkbox.setChecked(self.prefs.get('debug_libimobiledevice', False))
        self.debug_libimobiledevice_checkbox.stateChanged.connect(self.set_restart_required)

        # Hook changes to Annotations comboBox
#         self.annotations_field_comboBox.currentIndexChanged.connect(
#             partial(self.save_combobox_setting, 'annotations_field_comboBox'))
#        self.connect(self.annotations_field_comboBox,
#                     SIGNAL('currentIndexChanged(const QString &)'),
#                     self.annotations_destination_changed)
        self.annotations_field_comboBox.currentIndexChanged.connect(self.annotations_destination_changed)
        # Launch the annotated_books_scanner
        field = get_cc_mapping('annotations', 'field', None)
        self.annotated_books_scanner = InventoryAnnotatedBooks(self.gui, field)
        self.annotated_books_scanner.signal.connect(self.inventory_complete)
        QTimer.singleShot(1, self.start_inventory)

    def annotations_destination_changed(self, qs_new_destination_name):
        '''
        If the destination field changes, move all existing annotations from old to new
        '''
        self._log_location(str(qs_new_destination_name))
        self._log("self.eligible_annotations_fields: %s" % self.eligible_annotations_fields)

        old_destination_field = get_cc_mapping('annotations', 'field', None)
        old_destination_name = get_cc_mapping('annotations', 'combobox', None)

        self._log("old_destination_field: %s" % old_destination_field)
        self._log("old_destination_name: %s" % old_destination_name)

#        new_destination_name = unicode(qs_new_destination_name)
        # Signnls available have changed. Now receivin an indec rather than a name. Can get the name
        # from the combobox
        new_destination_name = unicode(self.annotations_field_comboBox.currentText())
        self._log("new_destination_name: %s" % repr(new_destination_name))

        if old_destination_name == new_destination_name:
            self._log_location("old_destination_name = new_destination_name, no changes")
            return

        if new_destination_name == '':
            self._log_location("annotations storage disabled")
            set_cc_mapping('annotations', field=None, combobox=new_destination_name)
            return

        new_destination_field = self.eligible_annotations_fields[new_destination_name]

        if existing_annotations(self.parent, old_destination_field):
            command = self.launch_new_destination_dialog(old_destination_name, new_destination_name)

            if command == 'move':
                set_cc_mapping('annotations', field=new_destination_field, combobox=new_destination_name)

                if self.annotated_books_scanner.isRunning():
                    self.annotated_books_scanner.wait()
                move_annotations(self, self.annotated_books_scanner.annotation_map,
                    old_destination_field, new_destination_field)

            elif command == 'change':
                # Keep the updated destination field, but don't move annotations
                pass

            elif command == 'cancel':
                # Restore previous destination
                self.annotations_field_comboBox.blockSignals(True)
                old_index = self.annotations_field_comboBox.findText(old_destination_name)
                self.annotations_field_comboBox.setCurrentIndex(old_index)
                self.annotations_field_comboBox.blockSignals(False)

        else:
            # No existing annotations, just update prefs
            self._log("no existing annotations, updating destination to '{0}'".format(new_destination_name))
            set_cc_mapping('annotations', field=new_destination_field, combobox=new_destination_name)

    def configure_appearance(self):
        '''
        '''
        self._log_location()
        appearance_settings = {
                                'appearance_css': default_elements,
                                'appearance_hr_checkbox': False,
                                'appearance_timestamp_format': default_timestamp
                              }

        # Save, hash the original settings
        original_settings = {}
        osh = hashlib.md5()
        for setting in appearance_settings:
            original_settings[setting] = plugin_prefs.get(setting, appearance_settings[setting])
            osh.update(repr(plugin_prefs.get(setting, appearance_settings[setting])))

        # Display the Annotations appearance dialog
        aa = AnnotationsAppearance(self, self.annotations_icon, plugin_prefs)
        cancelled = False
        if aa.exec_():
            # appearance_hr_checkbox and appearance_timestamp_format changed live to prefs during previews
            plugin_prefs.set('appearance_css', aa.elements_table.get_data())
            # Generate a new hash
            nsh = hashlib.md5()
            for setting in appearance_settings:
                nsh.update(repr(plugin_prefs.get(setting, appearance_settings[setting])))
        else:
            for setting in appearance_settings:
                plugin_prefs.set(setting, original_settings[setting])
            nsh = osh

        # If there were changes, and there are existing annotations,
        # and there is an active Annotations field, offer to re-render
        field = get_cc_mapping('annotations', 'field', None)
        if osh.digest() != nsh.digest() and existing_annotations(self.parent, field):
            title = 'Update annotations?'
            msg = '<p>Update existing annotations to new appearance settings?</p>'
            d = MessageBox(MessageBox.QUESTION,
                           title, msg,
                           show_copy_button=False)
            self._log_location("QUESTION: %s" % msg)
            if d.exec_():
                self._log_location("Updating existing annotations to modified appearance")

                # Wait for indexing to complete
                while not self.annotated_books_scanner.isFinished():
                    Application.processEvents()

                move_annotations(self, self.annotated_books_scanner.annotation_map,
                    field, field, window_title="Updating appearance")

    def edit_css(self):
        '''
        '''
        self._log_location()
        from calibre_plugins.marvin_manager.book_status import dialog_resources_path
        klass = os.path.join(dialog_resources_path, 'css_editor.py')
        if os.path.exists(klass):
            sys.path.insert(0, dialog_resources_path)
            this_dc = importlib.import_module('css_editor')
            sys.path.remove(dialog_resources_path)
            dlg = this_dc.CSSEditorDialog(self, 'css_editor')
            dlg.initialize(self)
            dlg.exec_()

    def get_eligible_custom_fields(self, eligible_types=[], is_multiple=None):
        '''
        Discover qualifying custom fields for eligible_types[]
        '''
        #self._log_location(eligible_types)

        eligible_custom_fields = {}
        for cf in self.gui.current_db.custom_field_keys():
            cft = self.gui.current_db.metadata_for_field(cf)['datatype']
            cfn = self.gui.current_db.metadata_for_field(cf)['name']
            cfim = self.gui.current_db.metadata_for_field(cf)['is_multiple']
            #self._log("cf: %s  cft: %s  cfn: %s cfim: %s" % (cf, cft, cfn, cfim))
            if cft in eligible_types:
                if is_multiple is not None:
                    if bool(cfim) == is_multiple:
                        eligible_custom_fields[cfn] = cf
                else:
                    eligible_custom_fields[cfn] = cf
        return eligible_custom_fields

    def inventory_complete(self, msg):
        self._log_location(msg)

    def launch_cc_wizard(self, column_type):
        '''
        '''
        def _update_combo_box(comboBox, destination, previous):
            '''
            '''
            cb = getattr(self, comboBox)
            cb.blockSignals(True)
            all_items = [str(cb.itemText(i))
                         for i in range(cb.count())]
            if previous and previous in all_items:
                all_items.remove(previous)
            all_items.append(destination)

            cb.clear()
            cb.addItems(sorted(all_items, key=lambda s: s.lower()))

            # Select the new destination in the comboBox
            idx = cb.findText(destination)
            if idx > -1:
                cb.setCurrentIndex(idx)

            cb.blockSignals(False)

        from calibre_plugins.marvin_manager.book_status import dialog_resources_path

        klass = os.path.join(dialog_resources_path, 'cc_wizard.py')
        if os.path.exists(klass):
            #self._log("importing CC Wizard dialog from '%s'" % klass)
            sys.path.insert(0, dialog_resources_path)
            this_dc = importlib.import_module('cc_wizard')
            sys.path.remove(dialog_resources_path)
            dlg = this_dc.CustomColumnWizard(self,
                                             column_type,
                                             self.WIZARD_PROFILES[column_type],
                                             verbose=True)
            dlg.exec_()

            if dlg.modified_column:
                self._log("modified_column: %s" % dlg.modified_column)

                self.restart_required = True

                destination = dlg.modified_column['destination']
                label = dlg.modified_column['label']
                previous = dlg.modified_column['previous']
                source = dlg.modified_column['source']

                if source == "Annotations":
                    _update_combo_box("annotations_field_comboBox", destination, previous)

                    # Add/update the new destination so save_settings() can find it
                    self.eligible_annotations_fields[destination] = label

                    # Save manually in case user cancels
                    set_cc_mapping('annotations', combobox=destination, field=label)

                elif source == 'Collections':
                    _update_combo_box("collection_field_comboBox", destination, previous)

                    # Add/update the new destination so save_settings() can find it
                    self.eligible_collection_fields[destination] = label

                    # Save manually in case user cancels
                    set_cc_mapping('collections', combobox=destination, field=label)

                elif source == 'Last read':
                    _update_combo_box("date_read_field_comboBox", destination, previous)

                    # Add/update the new destination so save_settings() can find it
                    self.eligible_date_read_fields[destination] = label

                    # Save manually in case user cancels
                    set_cc_mapping('date_read', combobox=destination, field=label)

                elif source == 'Locked':
                    _update_combo_box("locked_field_comboBox", destination, previous)

                    # Add/update the new destination so save_settings() can find it
                    self.eligible_locked_fields[destination] = label

                    # Save manually in case user cancels
                    set_cc_mapping('locked', combobox=destination, field=label)

                elif source == "Progress":
                    _update_combo_box("progress_field_comboBox", destination, previous)

                    # Add/update the new destination so save_settings() can find it
                    self.eligible_progress_fields[destination] = label

                    # Save manually in case user cancels
                    set_cc_mapping('progress', combobox=destination, field=label)

                elif source == "Read":
                    _update_combo_box("read_field_comboBox", destination, previous)

                    # Add/update the new destination so save_settings() can find it
                    self.eligible_read_fields[destination] = label

                    # Save manually in case user cancels
                    set_cc_mapping('read', combobox=destination, field=label)

                elif source == "Reading list":
                    _update_combo_box("reading_list_field_comboBox", destination, previous)

                    # Add/update the new destination so save_settings() can find it
                    self.eligible_reading_list_fields[destination] = label

                    # Save manually in case user cancels
                    set_cc_mapping('reading_list', combobox=destination, field=label)

                elif source == "Word count":
                    _update_combo_box("word_count_field_comboBox", destination, previous)

                    # Add/update the new destination so save_settings() can find it
                    self.eligible_word_count_fields[destination] = label

                    # Save manually in case user cancels
                    set_cc_mapping('word_count', combobox=destination, field=label)
        else:
            self._log("ERROR: Can't import from '%s'" % klass)

    def launch_new_destination_dialog(self, old, new):
        '''
        Return 'move', 'change' or 'cancel'
        '''
        from calibre_plugins.marvin_manager.book_status import dialog_resources_path
        self._log_location()
        klass = os.path.join(dialog_resources_path, 'new_destination.py')
        if os.path.exists(klass):
            self._log("importing new destination dialog from '%s'" % klass)
            sys.path.insert(0, dialog_resources_path)
            this_dc = importlib.import_module('new_destination')
            sys.path.remove(dialog_resources_path)
            dlg = this_dc.NewDestinationDialog(self, old, new)
            dlg.exec_()
            return dlg.command

    def populate_annotations(self):
        datatype = self.WIZARD_PROFILES['Annotations']['datatype']
        self.eligible_annotations_fields = self.get_eligible_custom_fields([datatype])
        self.annotations_field_comboBox.addItems([''])
        ecf = sorted(self.eligible_annotations_fields.keys(), key=lambda s: s.lower())
        self.annotations_field_comboBox.addItems(ecf)

        # Retrieve stored value
        existing = get_cc_mapping('annotations', 'combobox')
        if existing:
            ci = self.annotations_field_comboBox.findText(existing)
            self.annotations_field_comboBox.setCurrentIndex(ci)

    def populate_collections(self):
        datatype = self.WIZARD_PROFILES['Collections']['datatype']
        self.eligible_collection_fields = self.get_eligible_custom_fields([datatype],
                                                                          is_multiple=True)
        self.collection_field_comboBox.addItems([''])
        ecf = sorted(self.eligible_collection_fields.keys(), key=lambda s: s.lower())
        self.collection_field_comboBox.addItems(ecf)

        # Retrieve stored value
        existing = get_cc_mapping('collections', 'combobox')
        if existing:
            ci = self.collection_field_comboBox.findText(existing)
            self.collection_field_comboBox.setCurrentIndex(ci)

    def populate_date_read(self):
        #self.eligible_date_read_fields = self.get_eligible_custom_fields(['datetime'])
        datatype = self.WIZARD_PROFILES['Last read']['datatype']
        self.eligible_date_read_fields = self.get_eligible_custom_fields([datatype])
        self.date_read_field_comboBox.addItems([''])
        ecf = sorted(self.eligible_date_read_fields.keys(), key=lambda s: s.lower())
        self.date_read_field_comboBox.addItems(ecf)

        # Retrieve stored value
        existing = get_cc_mapping('date_read', 'combobox')
        if existing:
            ci = self.date_read_field_comboBox.findText(existing)
            self.date_read_field_comboBox.setCurrentIndex(ci)

    def populate_locked(self):
        datatype = self.WIZARD_PROFILES['Locked']['datatype']
        self.eligible_locked_fields = self.get_eligible_custom_fields([datatype])
        self.locked_field_comboBox.addItems([''])
        ecf = sorted(self.eligible_locked_fields.keys(), key=lambda s: s.lower())
        self.locked_field_comboBox.addItems(ecf)

        # Retrieve stored value
        existing = get_cc_mapping('locked', 'combobox')
        if existing:
            ci = self.locked_field_comboBox.findText(existing)
            self.locked_field_comboBox.setCurrentIndex(ci)

    def populate_progress(self):
        #self.eligible_progress_fields = self.get_eligible_custom_fields(['float'])
        datatype = self.WIZARD_PROFILES['Progress']['datatype']
        self.eligible_progress_fields = self.get_eligible_custom_fields([datatype])
        self.progress_field_comboBox.addItems([''])
        ecf = sorted(self.eligible_progress_fields.keys(), key=lambda s: s.lower())
        self.progress_field_comboBox.addItems(ecf)

        # Retrieve stored value
        existing = get_cc_mapping('progress', 'combobox')
        if existing:
            ci = self.progress_field_comboBox.findText(existing)
            self.progress_field_comboBox.setCurrentIndex(ci)

    def populate_read(self):
        datatype = self.WIZARD_PROFILES['Read']['datatype']
        self.eligible_read_fields = self.get_eligible_custom_fields([datatype])
        self.read_field_comboBox.addItems([''])
        ecf = sorted(self.eligible_read_fields.keys(), key=lambda s: s.lower())
        self.read_field_comboBox.addItems(ecf)

        # Retrieve stored value
        existing = get_cc_mapping('read', 'combobox')
        if existing:
            ci = self.read_field_comboBox.findText(existing)
            self.read_field_comboBox.setCurrentIndex(ci)

    def populate_reading_list(self):
        datatype = self.WIZARD_PROFILES['Reading list']['datatype']
        self.eligible_reading_list_fields = self.get_eligible_custom_fields([datatype])
        self.reading_list_field_comboBox.addItems([''])
        ecf = sorted(self.eligible_reading_list_fields.keys(), key=lambda s: s.lower())
        self.reading_list_field_comboBox.addItems(ecf)

        # Retrieve stored value
        existing = get_cc_mapping('reading_list', 'combobox')
        if existing:
            ci = self.reading_list_field_comboBox.findText(existing)
            self.reading_list_field_comboBox.setCurrentIndex(ci)

    def populate_word_count(self):
        #self.eligible_word_count_fields = self.get_eligible_custom_fields(['int'])
        datatype = self.WIZARD_PROFILES['Word count']['datatype']
        self.eligible_word_count_fields = self.get_eligible_custom_fields([datatype])
        self.word_count_field_comboBox.addItems([''])
        ecf = sorted(self.eligible_word_count_fields.keys(), key=lambda s: s.lower())
        self.word_count_field_comboBox.addItems(ecf)

        # Retrieve stored value
        existing = get_cc_mapping('word_count', 'combobox')
        if existing:
            ci = self.word_count_field_comboBox.findText(existing)
            self.word_count_field_comboBox.setCurrentIndex(ci)

    """
    def select_dropbox_folder(self):
        '''
        '''
        self._log_location()
        dropbox_location = QFileDialog.getExistingDirectory(
            self,
            "Dropbox folder",
            os.path.expanduser("~"),
            QFileDialog.ShowDirsOnly | QFileDialog.DontResolveSymlinks)
        self.dropbox_location_lineedit.setText(unicode(dropbox_location))

    def set_dropbox_syncing(self, state):
        '''
        Called when checkbox changes state, or when restoring state
        Set enabled state of Dropbox folder picker to match
        '''
        self.cfg_dropbox_folder_toolbutton.setEnabled(state)
        self.dropbox_location_lineedit.setEnabled(state)
    """

    def set_restart_required(self, state):
        '''
        Set restart_required flag to show show dialog when closing dialog
        '''
        self.restart_required = True

    """
    def save_combobox_setting(self, cb, index):
        '''
        Apply changes immediately
        '''
        cf = str(getattr(self, cb).currentText())
        self._log_location("%s => %s" % (cb, repr(cf)))

        if cb == 'annotations_field_comboBox':
            field = None
            if cf:
                field = self.eligible_annotations_fields[cf]
            set_cc_mapping('annotations', combobox=cf, field=field)
    """

    def save_settings(self):
        self._log_location()

        # Annotations field
        cf = unicode(self.annotations_field_comboBox.currentText())
        field = None
        if cf:
            field = self.eligible_annotations_fields[cf]
        set_cc_mapping('annotations', combobox=cf, field=field)

        # Collections field
        cf = unicode(self.collection_field_comboBox.currentText())
        field = None
        if cf:
            field = self.eligible_collection_fields[cf]
        set_cc_mapping('collections', combobox=cf, field=field)

        # Date read field
        cf = unicode(self.date_read_field_comboBox.currentText())
        field = None
        if cf:
            field = self.eligible_date_read_fields[cf]
        set_cc_mapping('date_read', combobox=cf, field=field)

        # Locked field
        cf = unicode(self.locked_field_comboBox.currentText())
        field = None
        if cf:
            field = self.eligible_locked_fields[cf]
        set_cc_mapping('locked', combobox=cf, field=field)

        # Progress field
        cf = unicode(self.progress_field_comboBox.currentText())
        field = None
        if cf:
            field = self.eligible_progress_fields[cf]
        set_cc_mapping('progress', combobox=cf, field=field)

        # Read field
        cf = unicode(self.read_field_comboBox.currentText())
        field = None
        if cf:
            field = self.eligible_read_fields[cf]
        set_cc_mapping('read', combobox=cf, field=field)

        # Reading list field
        cf = unicode(self.reading_list_field_comboBox.currentText())
        field = None
        if cf:
            field = self.eligible_reading_list_fields[cf]
        set_cc_mapping('reading_list', combobox=cf, field=field)

        # Word count field
        cf = unicode(self.word_count_field_comboBox.currentText())
        field = None
        if cf:
            field = self.eligible_word_count_fields[cf]
        set_cc_mapping('word_count', combobox=cf, field=field)

        '''
        # Save Dropbox settings
        self.prefs.set('dropbox_syncing', self.dropbox_syncing_checkbox.isChecked())
        self.prefs.set('dropbox_folder', unicode(self.dropbox_location_lineedit.text()))
        '''

        # Save general settings
        self.prefs.set('apply_markers_to_duplicates', self.duplicate_markers_checkbox.isChecked())
        self.prefs.set('apply_markers_to_updated', self.updated_markers_checkbox.isChecked())
        self.prefs.set('auto_refresh_at_startup', self.auto_refresh_checkbox.isChecked())
        self.prefs.set('show_progress_as_percentage', self.reading_progress_checkbox.isChecked())

        # Save debug settings
        self.prefs.set('debug_plugin', self.debug_plugin_checkbox.isChecked())
        self.prefs.set('debug_libimobiledevice', self.debug_libimobiledevice_checkbox.isChecked())

        # If restart needed, inform user
        if self.restart_required:
            do_restart = show_restart_warning('Restart calibre for the changes to be applied.',
                                              parent=self.gui)
            if do_restart:
                self.gui.quit(restart=True)

    def start_inventory(self):
        self._log_location()
        self.annotated_books_scanner.start()

class ClickableQLabel(QLabel):

    clicked = pyqtSignal(object)
    def __init__(self, parent):
        QLabel.__init__(self, parent)

    def mouseReleaseEvent(self, event):
        self.clicked.emit('clicked')

class InventoryAnnotatedBooks(QThread, Logger):

    signal = pyqtSignal(object, object)#SIGNAL()

    def __init__(self, gui, field, get_date_range=False):
        QThread.__init__(self, gui)
        self.annotation_map = []
        self.cdb = gui.current_db
        self.get_date_range = get_date_range
        self.newest_annotation = 0
        self.oldest_annotation = mktime(datetime.today().timetuple())
        self.field = field

    def run(self):
        if self.field is not None:
            self.find_all_annotated_books()
        else:
            self._log_location("No annotations field specified")
        if self.annotation_map and self.get_date_range:
            self.get_annotations_date_range()
        self.signal.emit("inventory_complete", "{0} annotated books".format(len(self.annotation_map)))

    def find_all_annotated_books(self):
        '''
        Find all annotated books in library
        '''
        self._log_location("field: {0}".format(self.field))
        cids = self.cdb.search_getting_ids('formats:EPUB', '')
        for cid in cids:
            mi = self.cdb.get_metadata(cid, index_is_id=True)
            raw = mi.get_user_metadata(self.field, False)
            if raw['#value#'] is not None:
                soup = BeautifulSoup(raw['#value#'])
                if soup.find('div', 'user_annotations') is not None:
                    self.annotation_map.append(mi.id)

    def get_annotations_date_range(self):
        '''
        Find oldest, newest annotation in annotated books
        initial values of self.oldest, self.newest are reversed to allow update comparisons
        if no annotations, restore to correct values
        '''
        annotations_found = False

        for cid in self.annotation_map:
            mi = self.cdb.get_metadata(cid, index_is_id=True)
            if self.field == 'Comments':
                soup = BeautifulSoup(mi.comments)
            else:
                soup = BeautifulSoup(mi.get_user_metadata(self.field, False)['#value#'])

            uas = soup.findAll('div', 'annotation')
            for ua in uas:
                annotations_found = True
                timestamp = float(ua.find('td', 'timestamp')['uts'])
                if timestamp < self.oldest_annotation:
                    self.oldest_annotation = timestamp
                if timestamp > self.newest_annotation:
                    self.newest_annotation = timestamp

        if not annotations_found:
            temp = self.newest_annotation
            self.newest_annotation = self.oldest_annotation
            self.oldest_annotation = temp

# For testing ConfigWidget, run from command line:
# cd ~/Documents/calibredev/Marvin_Manager
# calibre-debug config.py
# Search 'Marvin'
if __name__ == '__main__':
    try:
        from PyQt5.Qt import QApplication
    except ImportError:
        from PyQt4.Qt import QApplication

    from calibre.gui2.preferences import test_widget
    app = QApplication([])
    test_widget('Advanced', 'Plugins')


#!/usr/bin/env python3

import altair as alt
import pandas  # http://pandas.pydata.org
import numpy
import datetime

from fileops import save, getChartHTML
from filters import *
from logic import *

name = "graphblast-pull-pr-200901"
# begin user settings for this script
roots = ["../gunrock-output/v1-0-0", "../gunrock-output/v0-5-1/cc"]
fnFilterInputFiles = [
    fileEndsWithJSON,
    fileNotInArchiveDir,
]

fnPreprocessDF = [
    mergeAlgorithmIntoPrimitive,
    lowercasePrimitives,
    mergeMTEPSToAvgMTEPS,
    lambda df: merge(df, dst="num-vertices", src="num_vertices", delete=True),
    lambda df: merge(df, dst="num-edges", src="num_edges", delete=True),
    mergeElapsedIntoAvgProcessTime,
    mergeGunrockVersionWithUnderscoreIntoHyphen,
    normalizePRByIterations,
    renameColumnsWithMinus,
    equateNVIDIAGPUs,
]
fnFilterDFRows = [
    undirectedOnly,
    selectAnyOfThese("primitive", ["pr", "bfs", "sssp", "tc", "cc"]),
    selectAnyOfThese("gpuinfo_name", ["TITAN V", "Tesla K40/80"]),
    # get rid of PR push
    lambda df: df[(df["primitive"] != "pr") | (df["pull"] == True)],
    # soc-ork soc-lj h09 i04 rmat-22 rmat-23 rmat-24 rgg road_usa
    selectAnyOfThese(
        "dataset",
        [
            "cit-Patents",
            "coAuthorsCiteseer",
            "coPapersDBLP",
            "com-orkut",
            "hollywood-2009",
            "indochina-2004",
            "rgg_n_2_24_s0",
            "rmat_n22_e64.000000",
            "rmat_n23_e32.000000",
            "rmat_n24_e16.000000",
            "road_central",
            "road_usa",
            "soc-LiveJournal1",
            "soc-orkut",
        ],
    ),
    keepFastestAvgProcessTime(
        [
            "primitive",
            "dataset",
            "undirected",
            "gpuinfo_name",
        ],
        sortBy="avg_process_time",
    ),
]
fnPostprocessDF = [
    addJSONDetailsLink,
    gunrockVersionGPU,
]
# end user settings for this script

# actual program logic
# do not modify

# choose input files
df = filesToDF(roots=roots, fnFilterInputFiles=fnFilterInputFiles)
for fn in fnPreprocessDF:  # alter entries / compute new entries
    df = fn(df)
for fn in fnFilterDFRows:  # remove rows
    df = fn(df)
for fn in fnPostprocessDF:  # alter entries / compute new entries
    df = fn(df)

# end actual program logic

columnsOfInterest = [
    "primitive",
    "dataset",
    "avg_mteps",
    "avg_process_time",
    "engine",
    "gunrock_version",
    "gpuinfo_name",
    "pull",
    "num_vertices",
    "num_edges",
    "undirected",
    "time",
    "details",
]
# would prefer a cleanup call https://github.com/altair-viz/altair/issues/183
# without this, output is gigantic
df = (keepTheseColumnsOnly(columnsOfInterest))(df)

c = alt.Chart(df)
plotname = name

save(
    chart=c,
    df=df,
    plotname=plotname,
    formats=[
        "tablehtml",
        "tablemd",
    ],
    sortby=[
        "primitive",
        "dataset",
        "gpuinfo_name",
        "undirected",
        "gunrock_version",
    ],
    columns=columnsOfInterest,
)

columnsOfInterest = [
    "primitive",
    "dataset",
    "avg_process_time",
    "gpuinfo_name",
    "undirected",
    "details",
]

df = (keepTheseColumnsOnly(columnsOfInterest))(df)

df = df[df["undirected"] == True]

save(
    chart=c,
    df=df,
    plotname=plotname + "_abbrev",
    formats=[
        "tablehtml",
        "tablemd",
    ],
    sortby=[
        "primitive",
        "dataset",
        "gpuinfo_name",
    ],
    columns=columnsOfInterest,
)

# pandoc -i graphblast-pull-pr-200901_table.html -t gfm -o graphblast-pull-pr-200901_table.md


from tkinter import *
import requests
import json

class GUI(object):
    def __init__(self, master=None):
        self.fontePadrao = ("Arial", "10", "bold")

        # CONTAINERS
        self.primeiroContainer = Frame(master)
        self.primeiroContainer["pady"] = 10
        self.primeiroContainer.pack()

        self.segundoContainer = Frame(master)
        self.segundoContainer["padx"] = 20
        self.segundoContainer.pack()

        self.terceiroContainer = Frame(master)
        self.terceiroContainer["padx"] = 20
        self.terceiroContainer.pack()

        self.quartoContainer = Frame(master)
        self.quartoContainer["padx"] = 20
        self.quartoContainer.pack()

        self.quintoContainer = Frame(master)
        self.quintoContainer["padx"] = 20
        self.quintoContainer.pack()

        self.sextoContainer = Frame(master)
        self.sextoContainer["pady"] = 5
        self.sextoContainer["padx"] = 20
        self.sextoContainer.pack()

        # GUI TITLE
        self.titulo = Label(self.primeiroContainer, text="DOJOT -> DEVICE")
        self.titulo["font"] = ("Arial", "10", "bold")
        self.titulo.pack()

        # HOST
        self.hostLabel = Label(self.segundoContainer,text="Host:", font=self.fontePadrao)
        self.hostLabel.pack(side=LEFT)

        self.host = Entry(self.segundoContainer)
        self.host["width"] = 30
        self.host["font"] = self.fontePadrao
        self.host.pack(side=LEFT)

        # PORT
        self.portLabel = Label(self.segundoContainer,text="Port:", font=self.fontePadrao)
        self.portLabel.pack(side=LEFT)

        self.port = Entry(self.segundoContainer)
        self.port["width"] = 30
        self.port["font"] = self.fontePadrao
        self.port.pack(side=LEFT)

        # TOKEN
        self.tokenLabel = Label(self.terceiroContainer,text="Token:", font=self.fontePadrao)
        self.tokenLabel.pack(side=LEFT)

        self.token = Entry(self.terceiroContainer)
        self.token["width"] = 30
        self.token["font"] = self.fontePadrao
        self.token.pack(side=LEFT)

        # DEVICE ID
        self.deviceidLabel = Label(self.quartoContainer,text="Device Id:", font=self.fontePadrao)
        self.deviceidLabel.pack(side=LEFT)

        self.deviceid = Entry(self.quartoContainer)
        self.deviceid["width"] = 30
        self.deviceid["font"] = self.fontePadrao
        self.deviceid.pack(side=LEFT)

        # TOPIC
        self.topicLabel = Label(self.quintoContainer,text="Topic:", font=self.fontePadrao)
        self.topicLabel.pack(side=LEFT)

        self.topic = Entry(self.quintoContainer)
        self.topic["width"] = 30
        self.topic["font"] = self.fontePadrao
        self.topic.pack(side=LEFT)

        #PAYLOAD
        self.payloadLabel = Label(self.quintoContainer, text="Speed:", font=self.fontePadrao)
        self.payloadLabel.pack(side=LEFT)

        self.payload = Entry(self.quintoContainer)
        self.payload["width"] = 30
        self.payload["font"] = self.fontePadrao
        self.payload.pack(side=LEFT)

        # AUTH BUTTON
        self.autenticar = Button(self.sextoContainer)
        print(self.autenticar['command'])
        self.autenticar["text"] = "Send"
        self.autenticar["font"] = ("Arial", "8", "bold")
        self.autenticar["width"] = 12
        self.autenticar["command"] = self.verificaPub
        self.autenticar.pack()

        # HELP BUTTON
        self.helps = Button(self.sextoContainer)
      #  print(self.helps['command'])
        self.helps["text"] = "Help"
        self.helps["font"] = ("Arial", "8", "bold")
        self.helps["width"] = 12
        self.helps["command"] = self.ajuda
        self.helps.pack()

        self.mensagem = Label(self.sextoContainer, text="", font=self.fontePadrao)
        self.mensagem.pack()

        self.ajuda = Label(self.sextoContainer, text="", font=self.fontePadrao)
        self.ajuda.pack()

    # Metodo verificar payload
    def verificaPub(self):
      host = self.host.get()
      port = self.port.get()
      token = self.token.get()
      deviceid = self.deviceid.get()
      payload = self.payload.get()
      topic = self.topic.get()

      url = "http://{}:{}/device/{}/actuate".format(host,port,deviceid)
      Token = "Bearer {}".format(token)

      headers = {
        'Authorization':Token,
        'Content-Type':'application/json'
      }

      data = json.dumps( { "attrs": { topic: float(payload)}})

      response = requests.put(url, headers=headers, data=data)

    def ajuda(self):

      self.ajuda["text"] = """Host: Dojot IP     Port: 8000
                            Token: device-generated token
                            Device Id: device-generated Device ID
                            Topic: pubTimer -> speed publishing on Dojot
                            pubMove -> conveyor speed
                            Speed: time in seconds"""

root = Tk()
GUI(root)
root.mainloop()


'''
个人中心->上传头像
'''
import  requests
#  上传一个图片文件
url = "http://www.httpbin.org/post"
file_path = r"C:\Users\DELL\Desktop\人脸识别的测试用例.png"
# with open(file_path,mode='rb') as f:
#     # 二元组：'name':file-tuple,  file-tuple为('filename', fileobj)
#     file = {"人脸识别的测试用例.png":(file_path,f)}
#     # 三元组 'name':file-tuple,  file-tuple为('filename', fileobj,'content_type')
#     # content_type是MIME类型
#     # file = {"人脸识别的测试用例.png": (file_path, f,'image/png')}
#     r = requests.post(url,files = file)
#     print(r.text)
#  上传多个文件
# url = "http://www.httpbin.org/post"
# file_path1 = r"C:\Users\DELL\Desktop\人脸识别的测试用例.png"
# file_path2 = r"C:\Users\DELL\Desktop\ttt.txt"
# with open(file_path1,mode='rb') as f1:
#     with open(file_path2, mode='r',encoding='UTF-8') as f2:
#         files = {"人脸识别的测试用例.png":(file_path1,f1,"image/png"),
#                  "ttt.txt":(file_path2,f2,"text/plain")}
#         r = requests.post(url, files=files)
#         print(r.text)

#  使用data参数上传文件，一次只能传一个文件
with open(file_path,mode='rb') as f:
    r = requests.post(url,data = f)
    print(r.text)


import cv2
import numpy as np
import imutils
import time
import cv2

prototxt = "MobileNetSSD_deploy.prototxt.txt"
model = "MobileNetSSD_deploy.caffemodel"
confThresh = 0.2

CLASSES = ["background", "aeroplane", "bicycle", "bird", "boat", "bottle", "bus", "car", "cat", "chair",
           "cow", "diningtable", "dog", "horse", "motorbike", "person", "pottedplant", "sheep", "sofa", "train", "tvmonitor", "mobilephone"]
COLORS = np.random.uniform(0,255,size=(len(CLASSES), 3))

print("Loading Model...")
net = cv2.dnn.readNetFromCaffe(prototxt,model)
print("Model Loaded...")
print("Starting Camera Feed...")

cam = cv2.VideoCapture(0)
time.sleep(2.0)

while True:
    _,img = cam.read()
    img = imutils.resize(img,width = 500)

    (h,w) = img.shape[:2]
    imResizeBlob = cv2.resize(img,(300,300))
    blob = cv2.dnn.blobFromImage(imResizeBlob, 0.007843, (300,300), 127.5)

    net.setInput(blob)
    detections = net.forward()
    detShape = detections.shape[2]
    for i in np.arange(0,detShape):
        confidence = detections[0, 0, i, 2]
        if confidence > confThresh:
            idx = int(detections[0, 0, i, 1])
            box = detections[0, 0, i, 3:7] * np.array([w, h, w, h])
            (startX, startY, endX, endY) = box.astype("int")

            label = "{}: {:.2f}%".format(CLASSES[idx], confidence*100)
            cv2.rectangle(img, (startX,startY), (endX, endY), COLORS[idx], 2)
            if startY - 15 > 15:
                y = startY - 15
            else:
                startY + 15
            cv2.putText(img, label, (startX, y),
                        cv2.FONT_HERSHEY_SIMPLEX, 0.7, COLORS[idx], 2)

            if CLASSES[idx] == "bottle":
                cv2.putText(img, "I need Water", (10, 20), cv2.FONT_HERSHEY_SIMPLEX, 0.7, 2)

    cv2.imshow("Frame", img)
    key = cv2.waitKey(1)
    if key == ord("w"):
        break

cam.release()
cv2.destroyAllWindows()

Real Time Object Detection using PreTrained Model/Assignment.py


#!/usr/bin/python3
""" """
import requests
from sys import argv

if __name__ == "__main__":
    try:
        resps = requests.get(argv[1])
        resps.raise_for_status()
        print(resps.text)
    except:
        print("Error code: {}".format(resps.status_code))


from __future__ import annotations

from typing import TYPE_CHECKING

from django.http import HttpResponse
from django.shortcuts import get_object_or_404, render, redirect
from django.views.decorators.http import require_http_methods

from core.models import User
from core.business_logic.services import subscribe_and_unsubscribe
from core.business_logic.dto import SubscriberDTO

if TYPE_CHECKING:
    from django.http import HttpRequest

@require_http_methods(request_method_list=["GET","POST"])
def subscriber_controller(request: HttpRequest, username: str) -> HttpResponse:
    user = get_object_or_404(User, username=username)
    subscriber_user = request.user
    if request.method == "POST":
        data = SubscriberDTO(username=user.username, subscriber_username=subscriber_user.username)
        subscribe_and_unsubscribe(data=data)
    current_page = request.META.get('HTTP_REFERER')
    return redirect(current_page)

@require_http_methods(request_method_list=["GET"])
def followings_controller(request: HttpRequest, username: str) -> HttpResponse:
    user = get_object_or_404(User, username=username)
    following_users = user.subscriptions.all()

    context = {
        "user": user,
        "following_users": following_users,
    }

    return render(request=request, template_name='followings.html', context=context)

@require_http_methods(request_method_list=["GET"])
def followers_controller(request: HttpRequest, username: str) -> HttpResponse:
    user = get_object_or_404(User, username=username)
    followers = user.subscriber.all()

    context = {
        "user": user,
        "followers": followers,
    }

    return render(request=request, template_name='followers.html', context=context)


# source https://www.youtube.com/playlist?list=PLEsfXFp6DpzRyxnU-vfs3vk-61Wpt7bOS

import cv2
import os

# source: https://stackoverflow.com/a/44659589
def image_resize(image, width = None, height = None, inter = cv2.INTER_AREA):
    # initialize the dimensions of the image to be resized and
    # grab the image size
    dim = None
    (h, w) = image.shape[:2]
    # if both the width and height are None, then return the
    # original image
    if width is None and height is None:
        return image
    # check to see if the width is None
    if width is None:
        # calculate the ratio of the height and construct the
        # dimensions
        r = height / float(h)
        dim = (int(w * r), height)
    # otherwise, the height is None
    else:
        # calculate the ratio of the width and construct the
        # dimensions
        r = width / float(w)
        dim = (width, int(h * r))

    # resize the image
    resized = cv2.resize(image, dim, interpolation = inter)
    # return the resized image
    return resized
def alpha_blend(frame_1, frame_2, mask):
    alpha = mask/255.0
    blended = cv2.convertScaleAbs(frame_1*(1-alpha) + frame_2*alpha)
    return blended

def apply_circle_focus_blur(frame, intensity=0.2):
    frame_h, frame_w, frame_c = frame.shape
    y = int(frame_h/2)
    x = int(frame_w/2)

    mask = np.zeros((frame_h, frame_w, 4), dtype='uint8')
    cv2.circle(mask, (x, y), int(y/2), (255,255,255), -1, cv2.LINE_AA)
    mask = cv2.GaussianBlur(mask, (21,21),11 )

    blured = cv2.GaussianBlur(frame, (21,21), 11)
    blended = alpha_blend(frame, blured, 255-mask)
    frame = cv2.cvtColor(blended, cv2.COLOR_BGRA2BGR)
    return frame

def portrait_mode(frame):
#    cv2.imshow('frame', frame)
    gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
    _, mask = cv2.threshold(gray, 120,255,cv2.THRESH_BINARY)
    mask = cv2.cvtColor(mask, cv2.COLOR_GRAY2BGRA)
    blured = cv2.GaussianBlur(frame, (21,21), 11)
    blended = alpha_blend(frame, blured, mask)
    frame = cv2.cvtColor(blended, cv2.COLOR_BGRA2BGR)
    return frame

class CFEVideoConf(object):
    # Standard Video Dimensions Sizes
    STD_DIMENSIONS =  {
        "360p": (480, 360),
        "480p": (640, 480),
        "720p": (1280, 720),
        "1080p": (1920, 1080),
        "4k": (3840, 2160),
    }
    # Video Encoding, might require additional installs
    # Types of Codes: http://www.fourcc.org/codecs.php
    VIDEO_TYPE = {
        'avi': cv2.VideoWriter_fourcc(*'XVID'),
        #'mp4': cv2.VideoWriter_fourcc(*'H264'),
        'mp4': cv2.VideoWriter_fourcc(*'XVID'),
    }

    width           = 640
    height          = 480
    dims            = (640, 480)
    capture         = None
    video_type      = None
    def __init__(self, capture, filepath, res="480p", *args, **kwargs):
        self.capture = capture
        self.filepath = filepath
        self.width, self.height = self.get_dims(res=res)
        self.video_type = self.get_video_type()

    # Set resolution for the video capture
    # Function adapted from https://kirr.co/0l6qmh
    def change_res(self, width, height):
        self.capture.set(3, width)
        self.capture.set(4, height)

    def get_dims(self, res='480p'):
        width, height = self.STD_DIMENSIONS['480p']
        if res in self.STD_DIMENSIONS:
            width, height = self.STD_DIMENSIONS[res]
        self.change_res(width, height)
        self.dims = (width, height)
        return width, height

    def get_video_type(self):
        filename, ext = os.path.splitext(self.filepath)
        if ext in self.VIDEO_TYPE:
          return  self.VIDEO_TYPE[ext]
        return self.VIDEO_TYPE['avi']


from rimu import document, options
from rimu.options import RenderOptions
from typing import Optional

def render(source: str, opts: Optional[RenderOptions] = None) -> str:
    '''Exported render() API.'''
    if opts is None:
        opts = RenderOptions()
    # Implicit first-call API initialisation.
    if options.safeMode == -1:
        document.init()
    options.updateFrom(opts)
    return document.render(source)


from dataclasses import dataclass

import pytest

from anaximander.descriptors.attributes import Attribute

def test_attribute():
    x = Attribute("x", retriever=lambda s: getattr(s, "_x"))
    y = Attribute("y", retriever=lambda s: getattr(s, "_y"), transformer=round)

    # Test on simple class

    class C:
        _x: float = 0.99
        _y: float = 0.99

    setattr(C, "x", x)
    setattr(C, "y", y)

    assert isinstance(C.x, Attribute)
    c = C()
    assert c.x == 0.99
    assert c.y == 1

    with pytest.raises(AttributeError):
        c.x = 1

    # Test with metaclass

    class Meta(type):
        _x: float = 0.01
        _y: float = 0.01

    setattr(Meta, "x", x)
    setattr(Meta, "y", y)

    @dataclass
    class C(metaclass=Meta):
        _x: float
        _y: float = 0.99

    assert C.x == 0.01
    assert C.y == 1

    # This doesn't raise because the supplied values are attributes
    setattr(C, "x", x)
    setattr(C, "y", y)

    # However the return values remain the same, driven by the metaclass

    assert C.x == 0.01
    assert C.y == 1

    c = C(1)
    assert c.x == 1
    assert c.y == 1

    c._y = 1.99
    assert C.y == 1
    assert c.y == 2


from django.db import models
from django.db.models import BooleanField, Exists, OuterRef, Q, Value
from users.models import User

class RecipeQuerySet(models.QuerySet):
    """
    Кастомный queryset для модели Recipe.
    Добавлены поля 'is_favorited' и 'is_in_shopping_cart'.
    """

    def annotated(self, user):
        """Ожидает на вход экземпляр модели 'user'."""

        # Проверяем все возможные ситуации с параметром 'user'.
        if (not isinstance(user, User)
                or user is None
                or not user.is_authenticated):
            return self.annotate(
                is_favorited=Value(False, output_field=BooleanField()),
                is_in_shopping_cart=Value(False, output_field=BooleanField())
            )
        return self.annotate(
            is_favorited=Exists(
                self.filter(
                    # Существует ли связка рецепт-любимый рецепт-юзер.
                    Q(recipe_favorite__recipe=OuterRef('pk'))
                    & Q(recipe_favorite__user=user)
                )),
            is_in_shopping_cart=Exists(
                self.filter(
                    # Существует ли связка рецепт-корзина-юзер.
                    Q(in_shopping_cart__recipe=OuterRef('pk'))
                    & Q(in_shopping_cart__user=user)
                ))
        )

class RecipeManager(models.Manager):
    """
    Кастомный менеджер для queryset'а Recipe.
    Метод annotated() принимает в качестве аргумента экземпляр юзера,
    либо None по умолчанию.
    Дальнейшая проверка юзера реализована в RecipeQueryset.
    """

    def get_queryset(self):
        return RecipeQuerySet(self.model, using=self._db)

    def annotated(self, user=None):
        return self.get_queryset().annotated(user)


import spacy
from spacy.matcher import Matcher
nlp = spacy.load('en_core_web_sm')

# 1
story = ''
with open('..\\Material\\TextFiles\\owlcreek.txt', 'r') as f:
    story = f.read()
doc = nlp(story)
print(doc[:36])

# 2
print(len(doc))

# 3
sents = list(doc.sents)
print(len(sents))

# 4
print(sents[1])

# 5
for t in sents[1]:
    print(f'{t.text:{10}} {t.pos_:{10}} {t.lemma_:{10}}  {t.dep_:{10}}')

# 6
matcher = Matcher(nlp.vocab)
pattern = [[{'LOWER': 'swimming'}, {'IS_SPACE':True, 'OP':'*'}, {'LOWER': 'vigorously'}]]
matcher.add('Swimming',pattern)
found_matches = matcher(doc)
print(found_matches)

# 7
for found in found_matches:
    print(str(doc[found[1]-5:found[2]+5]).replace('\n', ' '))


import sqlite3
def delete_data():
    with sqlite3.connect("Coffe_shop.db") as db:
        cursor=db.cursor()
        sql="""Select * from Product"""
        cursor.execute(sql)
        products=cursor.fetchall()
        for prod in products:
            print(prod)
        i=input("Wich prod do you want to delete: ")

        sql="""Delete from Product where ProductID=?"""
        cursor.execute(sql,i)
        sql = """Select * from Product"""
        cursor.execute(sql)
        products = cursor.fetchall()
        for prod in products:
            print(prod)
        db.commit()
if __name__ == '__main__':
    delete_data()


#!/usr/bin/python
#
# lhm0101.py : log_hander_module for python
# -----------------------------------------
#
### Libraries #################################################################
#
import sys
import io
import os
import socket
import subprocess
import shlex
import pwd
#
from datetime  import datetime
from time      import time, sleep, gmtime, strftime
from threading import Thread
#
### Variables #################################################################
#
#
### Functions #################################################################
#------------------------------------------------------------------------------
#
# sub : print_log_header
#
def print_log_header(wrklog, appl_name):
#    sys_name         =  socket.getfqdn()	# <-very slow!
    currentFuncName = lambda n=0: sys._getframe(n + 1).f_code.co_name
#
    sys_name         =  socket.gethostname()
    pgm_name         =  os.path.basename(sys.argv[0])
    curfunc          =  currentFuncName(1)		#(current function caller is currentFuncName(1))
    pid              =  os.getpid()
    uid              =  os.getuid()
    user_name        =  pwd.getpwuid(uid)[0]
    pgm_pwd          =  os.getcwd()

    current_datetime = datetime.now()
    line_dashes  =  "--------------------------------------------------------------------------------\n"
    line_titel   =  "\nSYSTEM - PROGRAMM - FILE INFORMATION \n------------------------------------ \n\n"
    line_pgminfo1 =  "Program call ..................: " + pgm_name + "\n"
    line_pgminfo2 =  "Start-Date and Time ...........: " + str(current_datetime) + "\n\n"
    line_pgminfo3 =  "System name ...................: " + sys_name + "\n"
    line_pgminfo4 =  "Process ID ....................: " + str(pid) + "\n"
    line_pgminfo5 =  "User name .....................: " + user_name + "\n"
    line_pgminfo6 =  "Working Directory .............: " + pgm_pwd + "\n"
    line_pgminfo7 =  "Application Name ..............: " + appl_name + "\n"
    line_pgminfo8 =  "Program Name...................: " + pgm_name + "\n"
    line_pgminfo9 =  "Function Name..................: " + curfunc + "\n"
    line_pgminfo10 =  "Logfile Name...................: " + wrklog   + "\n\n"
    line_pgminfo11 = "\n"
    try:
        File = open(wrklog, 'w') 			# Open binary output file
        File.writelines(line_dashes) 			# Write byte string
        File.writelines(line_titel) 			# Write byte string
        File.writelines(line_pgminfo1) 			# Write byte string
        File.writelines(line_pgminfo2) 			# Write byte string
        File.writelines(line_pgminfo3) 			# Write byte string
        File.writelines(line_pgminfo4) 			# Write byte string
        File.writelines(line_pgminfo5) 			# Write byte string
        File.writelines(line_pgminfo6) 			# Write byte string
        File.writelines(line_pgminfo7) 			# Write byte string
        File.writelines(line_pgminfo8) 			# Write byte string
        File.writelines(line_pgminfo9) 			# Write byte string
        File.writelines(line_pgminfo10) 		# Write byte string
        File.writelines(line_dashes) 			# Write byte string
        File.writelines(line_pgminfo11)			# Write byte string
        File.close()					#
        #os.chmod(wrklog, 33270)				# chmod to 766
    except Exception as e:
        print ("Log_Header write was not successful.")
        print(e)
        raise(e)
    return
#
#------------------------------------------------------------------------------
#
# sub : print_log_line
#
def print_log_line(wrklog,statyp,status,statxt):
    pgm_name =  (sys.argv[0])
    currentFuncName = lambda n=0: sys._getframe(n + 1).f_code.co_name
#    curfunc          =  currentFuncName(1) +"_"+ currentFuncName(2) +"+"+ currentFuncName(3) +"+"+ currentFuncName(4)+"+"+ currentFuncName(5)		#(current function caller is currentFuncName(1))
    curfunc          =  currentFuncName(1)
    current_datetime = datetime.now()
#    string = str(current_datetime) + ":" + statyp + ":" + pgm_name + ":" + status + ":" + statxt + "\n"
    string = str(current_datetime) + ":" + statyp + ":" + curfunc + ":" + status + ":" + statxt + "\n"
    try:
        File = open(wrklog, 'a') 			# Open binary output file
        File.writelines(string)	 			# Write byte string
        File.close()					#
    except Exception as e:
        print ("Log_Line write was not successful.")
        print(e)
        raise(e)
    return
#
#------------------------------------------------------------------------------
#
# sub : print_log_footer
#
def print_log_footer(wrklog,start_time):
    current_datetime = datetime.now()
    line_endtim =  "\nEnd-Date and Time: ............: " ":" + str(current_datetime) + ""
    line_elatim =  "\nElapsed Time: .................: " ":" + str(time() - start_time) + "\n"
    line_dashes  =  "\n--------------------------------------------------------------------------------\n\n"
    try:
        File = open(wrklog, 'a') 			# Open binary output file
        File.writelines(line_endtim) 			# Write byte string
        File.writelines(line_elatim) 			# Write byte string
        File.writelines(line_dashes) 			# Write byte string
        File.close()					#
        os.chmod(wrklog, 33270)				# chmod to 766
    except Exception as e:
        print ("Log_Footer write was not successful.")
        print(e)
        raise(e)
    return
#
### END #######################################################################


from datetime import datetime
from typing import Any, Dict
from uuid import UUID

from oauth.backends import UnionIdBackend
from rest_framework_simplejwt.serializers import PasswordField
from users.models import User
from zq_auth_sdk import UserNotFoundException
from zq_django_util.exceptions import ApiException
from zq_django_util.response import ResponseType
from zq_django_util.utils.auth.backends import OpenIdBackend
from zq_django_util.utils.auth.serializers import (
    OpenIdLoginSerializer as DefaultOpenIdLoginSerializer,
)

import server.business.ziqiang.auth as zq_auth
from server.business.wechat.wxa import get_openid

def generate_token_result(
    user: User,
    user_id_field: str,
    expire_time: datetime,
    access: str,
    refresh: str,
) -> dict:
    return dict(
        id=getattr(user, user_id_field),
        username=user.username,
        is_active=user.is_active,
        is_staff=user.is_staff,
        expire_time=expire_time,
        access=access,
        refresh=refresh,
    )

class OpenIdLoginSerializer(DefaultOpenIdLoginSerializer):
    """
    OpenID Token 获取序列化器 (直接用 openid 获取登录 token，用于测试)
    """

    backend = OpenIdBackend(User)  # 自定义验证后端，用于指定不同类型的用户模型

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)

    def generate_token_result(
        self,
        user: User,
        user_id_field: str,
        expire_time: datetime,
        access: str,
        refresh: str,
    ) -> dict:
        return generate_token_result(
            user, user_id_field, expire_time, access, refresh
        )

class WechatLoginSerializer(OpenIdLoginSerializer):
    """
    微信登录序列化器
    """

    code = PasswordField(label="前端获取code")  # 前端传入 code

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)

        self.fields.pop("openid")  # 删除 openid 字段

    def get_open_id(self, attrs: Dict[str, Any]) -> str:
        """
        重写获取 open_id 方法
        """
        return get_openid(attrs["code"])

class UnionIdLoginSerializer(DefaultOpenIdLoginSerializer):
    """
    自强Auth union id 登录序列化器
    """

    backend = UnionIdBackend(User)
    openid = None
    openid_field = "union_id"
    union_id = PasswordField(label="union_id")  # union_id

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)

    def generate_token_result(
        self,
        user: User,
        user_id_field: str,
        expire_time: datetime,
        access: str,
        refresh: str,
    ) -> dict:
        return generate_token_result(
            user, user_id_field, expire_time, access, refresh
        )

    def get_open_id(self, attrs: Dict[str, Any]) -> UUID:
        """
        重写获取 open_id 方法
        """
        return UUID(attrs["union_id"])

    def handle_new_openid(self, union_id: UUID) -> User:
        """
        重写处理新 union id 方法
        """
        try:
            user_data = zq_auth.fetch_user_info(union_id)
            data = {
                "union_id": union_id,
                "username": f"{user_data['name']}_{user_data['student_id']}",
                "name": user_data["name"],
                "phone": user_data["phone"],
                "student_id": user_data["student_id"],
                "contact": [{"type": "phone", "value": user_data["phone"]}],
            }
            instance = User.objects.create(
                **data, is_active=True, is_staff=False
            )
            return instance
        except UserNotFoundException:
            raise ApiException(ResponseType.ThirdServiceError, "用户不存在")

class ZqAuthLoginSerializer(UnionIdLoginSerializer):
    """
    自强Auth登录序列化器
    """

    code = PasswordField(label="前端获取code")  # 前端传入 code
    union_id = None

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)

    def get_open_id(self, attrs: Dict[str, Any]) -> UUID:
        """
        重写获取 open_id 方法
        """
        return zq_auth.get_union_id(attrs["code"])


import logging
from datetime import datetime, timedelta
from dateutil.tz import tzutc

import pytest

from services.core.ActuatorAgent.actuator import agent
from services.core.ActuatorAgent.actuator.agent import ActuatorAgent
from services.core.ActuatorAgent.actuator.scheduler import RequestResult, DeviceState
from services.core.ActuatorAgent.tests.actuator_fixtures import MockedAsyncResult, get_actuator_agent
from volttrontesting.utils.utils import AgentMock
from volttron.platform.vip.agent import Agent

pytestmark = [pytest.mark.actuator_unit, pytest.mark.unit]

PEER = "peer-1"
SENDER = "sender-1"
HEADERS = {"requesterID": "id-12345"}
MESSAGE = "message-1"
BUS = "bus-1"
GET_TOPIC = "devices/actuators/get/somepath/actuationpoint"
SET_TOPIC = "devices/actuators/set/somepath/actuationpoint"
REQUEST_TOPIC = "devices/actuators/schedule/request"
REVERT_DEVICE_TOPIC = "devices/actuators/revert/device/somedevicepath"
REVERT_POINT_TOPIC = "actuators/revert/point/somedevicepath/someactuationpoint"

agent._log = logging.getLogger("test_logger")
ActuatorAgent.__bases__ = (AgentMock.imitate(Agent, Agent()),)

def test_handle_get_should_succeed():
    with get_actuator_agent() as actuator_agent:
        actuator_agent.handle_get(PEER, SENDER, BUS, GET_TOPIC, HEADERS, MESSAGE)

        actuator_agent.vip.rpc.call.assert_called_once()
        actuator_agent.vip.pubsub.publish.assert_called_once()

def test_handle_get_should_handle_standard_error(caplog):
    with get_actuator_agent(vip_identity=None) as actuator_agent:
        actuator_agent.handle_get(PEER, SENDER, BUS, GET_TOPIC, HEADERS, MESSAGE)

        actuator_agent.vip.rpc.call.assert_not_called()
        actuator_agent.vip.pubsub.publish.assert_called_once()
        assert (
            caplog.records[-1].message
            == "Actuator Agent Error: {'type': 'AttributeError', "
            "'value': \"'ActuatorAgent' object has no attribute 'driver_vip_identity'\"}"
        )

@pytest.mark.parametrize(
    "sender, device_state",
    [
        (
            SENDER,
            {"somepath": DeviceState("sender-1", "task-id-1", "anytime")},
        ),
        (
            "pubsub.compat",
            {"somepath": DeviceState("pubsub.compat", "task-id-1", "anytime")},
        ),
    ],
)
def test_handle_set_should_succeed(sender, device_state):
    with get_actuator_agent(vip_rpc_call_res=MockedAsyncResult({"foo": "bar"}), device_state=device_state) as actuator_agent:

        actuator_agent.handle_set(PEER, sender, BUS, SET_TOPIC, HEADERS, MESSAGE)

        actuator_agent.vip.rpc.call.assert_called_once()
        actuator_agent.vip.pubsub.publish.assert_called()

def test_handle_set_should_return_none_on_none_message(caplog):
    with get_actuator_agent(vip_identity=None) as actuator_agent:
        result = actuator_agent.handle_set(PEER, SENDER, BUS, SET_TOPIC, HEADERS, None)

        assert result is None
        actuator_agent.vip.pubsub.publish.assert_called_once()
        actuator_agent.vip.rpc.call.assert_not_called()
        assert (
            caplog.records[-1].message
            == "ValueError: {'type': 'ValueError', 'value': 'missing argument'}"
        )

def test_handle_set_should_handle_type_error_on_invalid_sender(caplog):
    with get_actuator_agent(vip_identity=None) as actuator_agent:
        actuator_agent.handle_set(PEER, None, BUS, SET_TOPIC, HEADERS, MESSAGE)

        actuator_agent.vip.rpc.call.assert_not_called()
        actuator_agent.vip.pubsub.publish.assert_called_once()
        assert (
            caplog.records[-1].message == "Actuator Agent Error: {'type': 'TypeError', "
            "'value': 'Agent id must be a nonempty string'}"
        )

def test_handle_set_should_handle_lock_error(caplog):
    with get_actuator_agent(vip_identity=None) as actuator_agent:
        actuator_agent.handle_set(PEER, SENDER, BUS, SET_TOPIC, HEADERS, MESSAGE)

        actuator_agent.vip.rpc.call.assert_not_called()
        actuator_agent.vip.pubsub.publish.assert_called_once()
        assert (
            caplog.records[-1].message == "Actuator Agent Error: {'type': 'LockError', "
            "'value': 'caller (sender-1) does not have this lock'}"
        )

def test_handle_revert_point_should_succeed():
    device_state = {
        "actuators/revert/point/somedevicepath": DeviceState(
            "sender-1", "task-id-1", "anytime"
        )
    }

    with get_actuator_agent(device_state=device_state, vip_rpc_call_res=MockedAsyncResult({"foo": "bar"})) as actuator_agent:
        actuator_agent.handle_revert_point(
            PEER, SENDER, BUS, REVERT_POINT_TOPIC, HEADERS, MESSAGE
        )

        actuator_agent.vip.rpc.call.assert_called_once()
        actuator_agent.vip.pubsub.publish.assert_called_once()

def test_handle_revert_point_should_handle_lock_error(caplog):
    with get_actuator_agent(vip_identity=None) as actuator_agent:
        actuator_agent.handle_revert_point(
            PEER, SENDER, BUS, REVERT_POINT_TOPIC, HEADERS, MESSAGE
        )

        actuator_agent.vip.rpc.call.assert_not_called()
        actuator_agent.vip.pubsub.publish.assert_called_once()
        assert (
            caplog.records[-1].message == "Actuator Agent Error: {'type': 'LockError', "
            "'value': 'caller does not have this lock'}"
        )

def test_handle_revert_device_should_succeed():
    device_state = {
        "somedevicepath": DeviceState("sender-1", "task-id-1", "anytime")
    }

    with get_actuator_agent(device_state=device_state,
                            vip_rpc_call_res=MockedAsyncResult({"foo": "bar"})) as actuator_agent:
        actuator_agent.handle_revert_device(
            PEER, SENDER, BUS, REVERT_DEVICE_TOPIC, HEADERS, MESSAGE
        )

        actuator_agent.vip.rpc.call.assert_called_once()
        actuator_agent.vip.pubsub.publish.assert_called_once()

def test_handle_revert_device_should_handle_lock_error(caplog):
   with get_actuator_agent(vip_identity=None) as actuator_agent:
        actuator_agent.handle_revert_device(
            PEER, SENDER, BUS, REVERT_DEVICE_TOPIC, HEADERS, MESSAGE
        )

        actuator_agent.vip.rpc.call.assert_not_called()
        actuator_agent.vip.pubsub.publish.assert_called_once()
        assert (
            caplog.records[-1].message == "Actuator Agent Error: {'type': 'LockError', "
            "'value': 'caller does not have this lock'}"
        )

@pytest.mark.parametrize(
    "priority, success",
    [
        ("HIGH", True),
        ("LOW", True),
        ("LOW_PREEMPT", True),
        ("HIGH", False),
        ("LOW", False),
        ("LOW_PREEMPT", False),
    ],
)
def test_handle_schedule_request_should_succeed_on_new_schedule_request_type(
    priority, success
):
    headers = {
        "type": "NEW_SCHEDULE",
        "requesterID": "id-123",
        "taskID": "12345",
        "priority": priority,
    }

    with get_actuator_agent(slot_requests_res=RequestResult(success, {}, "")) as actuator_agent:
        actuator_agent.handle_schedule_request(
            PEER, SENDER, BUS, REQUEST_TOPIC, headers, create_message()
        )

        actuator_agent.vip.pubsub.publish.assert_called()

@pytest.mark.parametrize("success", [True, False])
def test_handle_schedule_request_should_succeed_on_cancel_schedule_request_type(success):
    headers = {"type": "CANCEL_SCHEDULE", "requesterID": "id-123", "taskID": "12345"}

    with get_actuator_agent(slot_requests_res=RequestResult(success, {}, "")) as actuator_agent:
        actuator_agent.handle_schedule_request(
            PEER, SENDER, BUS, REQUEST_TOPIC, headers, create_message()
        )

        actuator_agent.vip.pubsub.publish.assert_called()

@pytest.mark.parametrize("invalid_request_type", ["bad request type", None])
def test_handle_schedule_request_should_log_invalid_request_type(
    invalid_request_type, caplog
):
    headers = {
        "type": invalid_request_type,
        "requesterID": "id-123",
        "taskID": "12345",
        "priority": "HIGH",
    }

    with get_actuator_agent(vip_identity=None) as actuator_agent:
        actuator_agent.handle_schedule_request(
            PEER, SENDER, BUS, REQUEST_TOPIC, headers, create_message()
        )

        actuator_agent.vip.pubsub.publish.assert_called()
        assert caplog.records[-1].message == "handle-schedule_request, invalid request type"

def create_message():
    start = str(datetime.now(tz=tzutc()) + timedelta(seconds=10))
    end = str(datetime.now(tz=tzutc()) + timedelta(seconds=20))
    return ["campus/building/device1", start, end]


"""

"""
import datetime

import requests
import json
from requests.models import Response

from Constants import Constants

"""
def build_simple_api_call( url: str, id: int ) -> str:
    return url + str(id)

def get_team_info( id: int ) -> str:
    team_info_url: str = build_simple_api_call( Constants.URL_TEAM_INFO, id )
    response: Response = requests.request( Constants.HTTP_GET, team_info_url, headers=Constants.HEADERS )
    return json.dumps( response.json() )
"""

# ROUTINES TO READ GAME DATA FROM API AND SAVE IT TO DISK
def build_date_api_call( url: str, year: int, month: int, day: int ) -> str:
    # ASSERTIONS ABOUT INPUT
    assert( year < 2100 )
    assert( year > 0 )
    assert( year < 100 or year > 1948 )

    assert( month > 0 )
    assert( month < 13 )

    assert( day > 0 )
    assert( day < 32 )

    # TURN YEAR INTO A STRING
    year_str: str = str(year)
    if ( year < 99 ):
        year_str = "20" + year_str

    # TURN MONTH INTO A STRING
    month_str: str = str(month)
    if ( month < 10 ):
        month_str = "0" + month_str

    # TURN DAY INTO A STRING
    day_str: str = str(day)
    if ( day < 10 ):
        day_str = "0" + day_str

    date_str: str = year_str + "-" + month_str + "-" + day_str
    return url + date_str

def get_games_for( year: int, month: int, day: int ):
    games_by_day_url: str = build_date_api_call( Constants.URL_GAMES_ON_DAY, year, month, day )
    response: Response = requests.request( Constants.HTTP_GET, games_by_day_url, headers=Constants.HEADERS)
    return json.dumps( response.json() )

def get_yesterdays_games():
    yes: datetime = datetime.datetime.today() - datetime.timedelta(days=1)
    return get_games_for( yes.year, yes.month, yes.day )

def save_yesterdays_games_to_disk( file_contents: str ):
    yes: datetime = datetime.datetime.today() - datetime.timedelta(days=1)
    filename: str = build_date_api_call( Constants.GAME_DATA_DIR, yes.year, yes.month, yes.day ) + ".json"
    file = open( filename, "w+" )
    file.write(file_contents)
    file.close()

# ROUTINES TO READ AN RSS FILE OFF THE WEB AND SAVE IT TO DISK
def read_rss_url( url: str ):
    response: Response = requests.request( Constants.HTTP_GET, url )
    return response.text

def save_rss_to_disk( info: str, url: str, idx: int ):
    yes: datetime = datetime.datetime.today()
    filename: str = build_date_api_call( Constants.RSS_DATA_DIR, yes.year, yes.month, yes.day ) +\
                                    "_" + str(idx) +\
                                    ".rss"

    file = open( filename, "w+" )
    file.write( info )
    file.close()

count: int = 0
for url in Constants.RSS_URLS:
    count += 1
    text: str = read_rss_url( url )
    save_rss_to_disk( text, url, count )

# GET DATA FOR YESTERDAY'S GAMES
output: str = get_yesterdays_games()
save_yesterdays_games_to_disk(output)

# READ FROM RSS FEEDS