from sys import exit, argv
from PySide2.QtWidgets import (QApplication, QLabel, QPushButton,
                               QVBoxLayout, QHBoxLayout, QWidget, QTableView,
                               QAbstractItemView, QHeaderView, QMenuBar, QMenu, QDockWidget, QSplitter, QSizePolicy, QMessageBox)
from PySide2.QtCore import Slot, Qt, QModelIndex, QTimer
from PySide2.QtCharts import QtCharts
from time import time

# from PySide2.QtWidgets import (QWidget)
from about import About
from hwmon_interface import get_sensor_value, get_sensor_label, get_list_of_sensors

from tablemodel import TableModel

class MyWidget(QWidget):
    def __init__(self):
        QWidget.__init__(self)

        self.sensortypes = ["in", "curr", "fan", "temp"]
        self.chartView = {}

        self.createMenu()
        self.tableModel = TableModel()

        # self.dock = QDockWidget()

        self.tableView = QTableView()
        self.tableView.setModel(self.tableModel)
        # self.tableView.setSortingEnabled(True)
        self.tableView.setSelectionBehavior(QAbstractItemView.SelectRows)
        # self.tableView.horizontalHeader().setStretchLastSection(True)
        self.tableView.verticalHeader().hide()
        self.tableView.setEditTriggers(QAbstractItemView.NoEditTriggers)
        self.tableView.setSelectionMode(QAbstractItemView.SingleSelection)

        self.tableView.horizontalHeader().setSectionResizeMode(0, QHeaderView.ResizeToContents)
        self.tableView.horizontalHeader().setSectionResizeMode(1, QHeaderView.ResizeToContents)
        # self.tableView.setSizePolicy(QSizePolicy.horizontalPolicy(ds))
        # self.dock.setWidget(self.tableView)

        self.mainLayout = QHBoxLayout()
        self.mainLayout.setMenuBar(self.menuBar)
        # self.mainLayout.addWidget(self.dock)

        self.layout = QVBoxLayout()

        self.widget = QWidget()
        self.widget.setLayout(self.layout)

        self.splitter = QSplitter()
        self.splitter.addWidget(self.tableView)
        self.splitter.addWidget(self.widget)

        self.mainLayout.addWidget(self.splitter)

        self.setLayout(self.mainLayout)

        timer = QTimer(self)
        timer.timeout.connect(self.update_sensors_values)
        timer.setInterval(1000)
        timer.start()

        for sensorType in self.sensortypes:
            chart_view = QtCharts.QChartView()
            self.layout.addWidget(chart_view)

            self.chartView[sensorType] = chart_view

        self.chartSeries = {}

        self.get_sensors()

        self.updateNumber = 0

    @Slot()
    def magic(self):
        self.update_sensors_values()

    @Slot()
    def get_sensors(self):
        sensors = get_list_of_sensors()

        for sensor in sensors:
            self.tableModel.addSensor(sensor)

            series = QtCharts.QLineSeries()
            series.setName(sensor.label)

            self.chartSeries[sensor.name] = series

            self.chartView[sensor.type].chart().addSeries(series)
            self.chartView[sensor.type].chart().createDefaultAxes()
            self.chartView[sensor.type].chart().legend().setAlignment(Qt.AlignLeft)
            self.chartView[sensor.type].chart().axisX().setRange(0, 100)
            self.chartView[sensor.type].chart().axisY().setRange(0, 0)
            self.chartView[sensor.type].show()
        #
        # for sensor in sensors:
        #     # Step 1: create the  row
        #     self.tableModel.insertRows(0)
        #
        #     # Step 2: get the index of the newly created row and use it.
        #     # to set the name
        #     ix = self.tableModel.index(0, 0, QModelIndex())
        #     self.tableModel.setData(ix, sensor.label, Qt.EditRole)
        #
        #     # Step 3: lather, rinse, repeat for the address.
        #     ix = self.tableModel.index(0, 1, QModelIndex())
        #     self.tableModel.setData(ix, sensor.current_value, Qt.EditRole)

    def update_sensors_values(self):
        for index, sensor in enumerate(self.tableModel.sensors):
            sensor.setCurrentValue(get_sensor_value(sensor.name))
            self.tableModel.dataChanged.emit(index, index, 0)

            chart = self.chartView[sensor.type].chart()
            if sensor.getScaledValue() > chart.axisY(self.chartSeries[sensor.name]).max():
                chart.axisY(self.chartSeries[sensor.name]).setMax(round(sensor.getScaledValue() + 5, -1))

            self.chartSeries[sensor.name].append(self.updateNumber, sensor.getScaledValue())

        self.updateNumber += 1

    def createMenu(self):
        self.menuBar = QMenuBar()

        self.fileMenu = QMenu("&File", self)
        self.exitAction = self.fileMenu.addAction("E&xit")
        self.exitAction.triggered.connect(self.close)

        self.menuBar.addMenu(self.fileMenu)

        self.helpMenu = QMenu("&Help", self)
        self.aboutAction = self.helpMenu.addAction("&About")
        self.aboutAction.triggered.connect(self.about)

        self.menuBar.addMenu(self.helpMenu)

    def about(self):
        # form = About()
        # form.show()
        QMessageBox.about(self, "About", "Asus sensor monitor v1")

if __name__ == "__main__":
    app = QApplication(argv)

    widget = MyWidget()
    widget.resize(800, 600)
    widget.show()
    widget.setWindowTitle("Sensor monitoring")

    exit(app.exec_())


from __future__ import annotations

import os
import sys
import argparse

def main():
    parser = argparse.ArgumentParser(
        description="Add copyright text to the front of source files")
    parser.add_argument("--src", metavar="<path>",
                        help="Source directory.", type=str, required=True)
    parser.add_argument("--copyright", metavar="<path>",
                        help="Copyright file.", type=str, required=True)
    parser.add_argument(
        "--verbose", help="Show progress.", action="store_true")
    args = parser.parse_args()

    src_dir: str = args.src
    if not os.path.isabs(src_dir):
        src_dir = os.path.abspath(src_dir)

    copyright_file: str = args.copyright

    if not os.path.isdir(src_dir):
        print("Source directory doesn't exist.")
        sys.exit(-1)

    if not os.path.isfile(copyright_file):
        print("Copyright file doesn't exist.")
        sys.exit(-1)

    # Collect files
    source_files: list[str] = []
    for root, _, files in os.walk(src_dir):
        for file in files:
            if file.endswith((".h", ".hpp", ".cpp", ".cc")):
                source_files.append(os.path.join(root, file))

    with open(copyright_file, "r", encoding="utf-8-sig") as f:
        copyright_lines = f.readlines()

    copyright_comments = [
        "/****************************************************************************\n"]
    copyright_comments.append(" *\n")
    for line in copyright_lines:
        if not line.endswith("\n"):
            line += "\n"
        copyright_comments.append(f" * {line}")
    copyright_comments.append(" *\n")
    copyright_comments.append(
        " ****************************************************************************/\n")

    count = 0
    total = len(source_files)
    for file in source_files:
        count += 1
        if args.verbose:
            print(f"[{count}/{total}] Process {file}")

        with open(file, "r", encoding="utf-8-sig") as f:
            lines = f.readlines()

        # Find header guard and prepend the comment in front
        header_guard_found: bool = False
        is_comment: bool = False
        while len(lines) > 0:
            line = lines[0].lstrip().rstrip()

            # End of comment
            if "*/" in line and is_comment:
                is_comment = False
                del (lines[0])
                continue

            # Empty or comment
            if line == "" or is_comment:
                del (lines[0])
                continue

            if line.startswith("/*") and not is_comment:
                is_comment = True
                del (lines[0])
                continue

            if line.startswith("//"):
                del (lines[0])
                continue

            # Header guard
            if line.startswith("#ifndef") or line.startswith("#pragma"):
                header_guard_found = True

            break

        if not header_guard_found:
            continue

        with open(file, "w", encoding="utf-8") as f:
            f.writelines(copyright_comments)
            f.write("\n")
            f.writelines(lines)

if __name__ == "__main__":
    main()


from datetime import datetime

from invian.common.utils import round_to_interval
from invian.services.controller.data.storage.redis import RedisStorage
from invian.services.controller.domains.events.entities import EventEntity

__all__ = ("EventsRepository",)

class EventsRepository:
    def __init__(self, storage: RedisStorage, interval: int) -> None:
        self.__storage = storage
        self.__interval = interval

    async def get_for_period(self, period: datetime) -> list[int]:
        return [int(payload) for payload in await self.__storage.connection.lrange(self.__get_key(period), 0, -1)]

    async def create(self, event: EventEntity) -> None:
        key = self.__get_key(event.datetime)
        await self.__storage.connection.rpush(key, event.payload)
        await self.__storage.connection.expire(key, self.__interval)

    def __get_key(self, period: datetime) -> str:
        return f"events-{int(round_to_interval(period, self.__interval).timestamp())}"


import codecs
import collections
import enum

import tensorflow as tf  # pytype: disable=import-error

# Values for padding, unknown words and a root.
PAD = '<p>'
PAD_ID = 0

UNKNOWN = '<u>'
UNKNOWN_ID = 1

ROOT = '<r>'
ROOT_ID = 2

class CoNLLAttributes(enum.Enum):
  """CoNLL attributre names and indices.

  A UD CoNLL file looks like:
  1    They     they    PRON    PRP    Case=Nom|Number=Plur       2    nsubj
  2    buy      buy     VERB    VBP    Number=Plur|PTense=Pres    0    root
  3    books    book    NOUN    NNS    Number=Plur                2    obj
  4    .        .       PUNCT   .      _                          2    punct

  For details, please see: http://universaldependencies.org/format.html.
  """

  ID = 0
  FORM = 1
  LEMMA = 2
  UPOS = 3
  XPOS = 4
  FEATS = 5
  HEAD = 6
  DEPREL = 7

def create_vocabs(filename, max_num_forms=100000):
  """Loads corpus and create vocabulary lists.

  Args:
    filename: file name of a corpus.
    max_num_forms: maximum number of tokens included.

  Returns:
    Dictionary containing named vocab dictionaries.

  """
  form_counter = collections.Counter()
  xpos_counter = collections.Counter()
  with tf.io.gfile.GFile(filename, 'rb') as f:
    for line in codecs.getreader('utf-8')(f):
      line = line.strip()
      split = line.split('\t')
      if not line.startswith('#') and split[0]:
        form_counter[split[CoNLLAttributes.FORM.value]] += 1
        xpos_counter[split[CoNLLAttributes.XPOS.value]] += 1

  special_tokens = {PAD: PAD_ID, UNKNOWN: UNKNOWN_ID, ROOT: ROOT_ID}

  # create word form vocab
  vocabs = {'forms': {}, 'xpos': {}}
  vocabs['forms'].update(special_tokens)
  vocabs['forms'].update(
      {
          form[0]: id
          for id, form in enumerate(
              form_counter.most_common(max_num_forms), start=ROOT_ID + 1
          )
      }
  )

  # create xpos vocab
  vocabs['xpos'].update(special_tokens)
  vocabs['xpos'].update(
      {
          tag[0]: id
          for id, tag in enumerate(
              xpos_counter.most_common(), start=ROOT_ID + 1
          )
      }
  )

  return vocabs

def create_token(token, attributes, vocabs):
  """Map for a token a selected subset of attributes to indices.

  Input example: CoNLL 09 representation for a token.
    ['Ms.', 'ms.', 'ms.', 'NNP', '_', '2', 'TITLE]
  Output example: Indices as defined in self._attributes, e.g., [word form,
    part-of-speech tag, and head].
    [1025, 3, 1]

  Args:
    token: CoNLL token attributes.
    attributes: selected attributes.
    vocabs: dictionary of vocabs.

  Returns:
    List of attribute ids for a token, e.g. [1025, 3] with word id and pos id.

  Raises:
    ValueError: CoNLL attribute requested but not covered by mapping.
  """
  selected_attributes = []
  for attribute in attributes:
    index = attribute.value
    if attribute == CoNLLAttributes.FORM:
      selected_attributes.append(vocabs['forms'].get(token[index], UNKNOWN_ID))
    elif attribute == CoNLLAttributes.XPOS:
      selected_attributes.append(vocabs['xpos'].get(token[index], UNKNOWN_ID))
    elif attribute == CoNLLAttributes.HEAD:
      selected_attributes.append(int(token[index]))
    else:
      raise ValueError(
          'CoNLL index %s not covered by mapping.' % str(attribute.name)
      )
  return selected_attributes

def create_sentence_with_root(attributes, vocabs):
  """Create a sentence containing a root.

  Args:
    attributes: attributes extracted from token.
    vocabs: dictionary of vocabs.

  Returns:
    A list representing a sentence containing the root only,
    e.g., [[2, 1, 0]] for root word, unknown xpos, and head 0.
  """
  # Create the token properties of an artificial root node.
  token_properties = [ROOT for _ in range(12)]  # CoNLL 09 has 12 columns.
  token_properties[CoNLLAttributes.ID.value] = '0'
  token_properties[CoNLLAttributes.HEAD.value] = '0'
  token = create_token(token_properties, attributes, vocabs)
  if len(token) == 1:
    token = token[0]
  return [token]

def sentences_from_conll_data(
    corpus_filename, vocabs, attributes, max_sentence_length=1000
):
  """Load and returns conll data in list format.

  Args:
    corpus_filename: filename of corpus.
    vocabs: dictionary of vocabs
    attributes: list of conll attributes to include into the batch
    max_sentence_length: cut off sentences longer as max tokens

  Yields:
      A sentence as a list of tokens while tokens are lists of attributes.
  """
  with tf.io.gfile.GFile(corpus_filename, 'rb') as f:
    sentence = create_sentence_with_root(attributes, vocabs)
    for line in codecs.getreader('utf-8')(f):
      line = line.strip()
      if line.startswith('#'):
        continue
      split = line.split('\t')
      if split[0]:  # Not an empty line, process next token:
        if len(sentence) < max_sentence_length:
          if len(attributes) == 1:
            sentence.append(create_token(split, attributes, vocabs)[0])
          else:
            sentence.append(create_token(split, attributes, vocabs))
      else:  # Sentences start with an empty line, yield sentence:
        yield sentence

        # Reset sentence.
        sentence = create_sentence_with_root(attributes, vocabs)
    if len(sentence) > 1:  # sentences does not only contain a root.
      yield sentence

def sentence_dataset_dict(
    filename,
    vocabs,
    attributes_input,
    attributes_target,
    batch_size,
    bucket_size,
    repeat=None,
    prefetch_size=tf.data.experimental.AUTOTUNE,
):
  """Combines sentences into a dataset of padded batches.

  Args:
    filename: file name of a corpus.
    vocabs: dictionary of dictionaries to map from strings to ids.
    attributes_input: attributes for the input.
    attributes_target: target attributes empty targets is not included.
    batch_size: the size of a batch.
    bucket_size: the size of a bucket.
    repeat: number of times the dataset is repeated.
    prefetch_size: prefetch size of the data.

  Returns:
    Returns dataset as dictionary containing the data as key value pairs.
  """
  data_keys = ['inputs']
  if attributes_target:
    data_keys.append('targets')

  def generator():
    """Generator to create the data."""
    input_generator = sentences_from_conll_data(
        filename, vocabs, attributes_input, max_sentence_length=bucket_size
    )

    if attributes_target:
      target_generator = sentences_from_conll_data(
          filename, vocabs, attributes_target, max_sentence_length=bucket_size
      )

    for inputs in input_generator:
      data = {'inputs': inputs}
      if attributes_target:
        data['targets'] = next(target_generator)
      yield data

  output_types = {k: tf.float32 for k in data_keys}
  output_shapes = {k: (None,) for k in data_keys}
  dataset = tf.data.Dataset.from_generator(
      generator, output_types=output_types, output_shapes=output_shapes
  )

  # cache the dataset in memory and repeat.
  dataset = dataset.cache()
  dataset = dataset.repeat(repeat)

  # static padding up to bucket size.
  padded_shapes = {k: [bucket_size] for k in data_keys}
  dataset = dataset.padded_batch(
      batch_size=batch_size, padded_shapes=(padded_shapes)
  )

  dataset = dataset.prefetch(prefetch_size)
  return dataset


from __future__ import unicode_literals

import json
import unittest

from mopidy.models import (
    Album, Artist, ModelJSONEncoder, Playlist, Ref, SearchResult, TlTrack,
    Track, model_json_decoder)

class GenericCopyTest(unittest.TestCase):
    def compare(self, orig, other):
        self.assertEqual(orig, other)
        self.assertNotEqual(id(orig), id(other))

    def test_copying_track(self):
        track = Track()
        self.compare(track, track.copy())

    def test_copying_artist(self):
        artist = Artist()
        self.compare(artist, artist.copy())

    def test_copying_album(self):
        album = Album()
        self.compare(album, album.copy())

    def test_copying_playlist(self):
        playlist = Playlist()
        self.compare(playlist, playlist.copy())

    def test_copying_track_with_basic_values(self):
        track = Track(name='foo', uri='bar')
        copy = track.copy(name='baz')
        self.assertEqual('baz', copy.name)
        self.assertEqual('bar', copy.uri)

    def test_copying_track_with_missing_values(self):
        track = Track(uri='bar')
        copy = track.copy(name='baz')
        self.assertEqual('baz', copy.name)
        self.assertEqual('bar', copy.uri)

    def test_copying_track_with_private_internal_value(self):
        artist1 = Artist(name='foo')
        artist2 = Artist(name='bar')
        track = Track(artists=[artist1])
        copy = track.copy(artists=[artist2])
        self.assertIn(artist2, copy.artists)

    def test_copying_track_with_invalid_key(self):
        test = lambda: Track().copy(invalid_key=True)
        self.assertRaises(TypeError, test)

    def test_copying_track_to_remove(self):
        track = Track(name='foo').copy(name=None)
        self.assertEquals(track.__dict__, Track().__dict__)

class RefTest(unittest.TestCase):
    def test_uri(self):
        uri = 'an_uri'
        ref = Ref(uri=uri)
        self.assertEqual(ref.uri, uri)
        self.assertRaises(AttributeError, setattr, ref, 'uri', None)

    def test_name(self):
        name = 'a name'
        ref = Ref(name=name)
        self.assertEqual(ref.name, name)
        self.assertRaises(AttributeError, setattr, ref, 'name', None)

    def test_invalid_kwarg(self):
        test = lambda: SearchResult(foo='baz')
        self.assertRaises(TypeError, test)

    def test_repr_without_results(self):
        self.assertEquals(
            "Ref(name=u'foo', type=u'artist', uri=u'uri')",
            repr(Ref(uri='uri', name='foo', type='artist')))

    def test_serialize_without_results(self):
        self.assertDictEqual(
            {'__model__': 'Ref', 'uri': 'uri'},
            Ref(uri='uri').serialize())

    def test_to_json_and_back(self):
        ref1 = Ref(uri='uri')
        serialized = json.dumps(ref1, cls=ModelJSONEncoder)
        ref2 = json.loads(serialized, object_hook=model_json_decoder)
        self.assertEqual(ref1, ref2)

    def test_type_constants(self):
        self.assertEqual(Ref.ALBUM, 'album')
        self.assertEqual(Ref.ARTIST, 'artist')
        self.assertEqual(Ref.DIRECTORY, 'directory')
        self.assertEqual(Ref.PLAYLIST, 'playlist')
        self.assertEqual(Ref.TRACK, 'track')

    def test_album_constructor(self):
        ref = Ref.album(uri='foo', name='bar')
        self.assertEqual(ref.uri, 'foo')
        self.assertEqual(ref.name, 'bar')
        self.assertEqual(ref.type, Ref.ALBUM)

    def test_artist_constructor(self):
        ref = Ref.artist(uri='foo', name='bar')
        self.assertEqual(ref.uri, 'foo')
        self.assertEqual(ref.name, 'bar')
        self.assertEqual(ref.type, Ref.ARTIST)

    def test_directory_constructor(self):
        ref = Ref.directory(uri='foo', name='bar')
        self.assertEqual(ref.uri, 'foo')
        self.assertEqual(ref.name, 'bar')
        self.assertEqual(ref.type, Ref.DIRECTORY)

    def test_playlist_constructor(self):
        ref = Ref.playlist(uri='foo', name='bar')
        self.assertEqual(ref.uri, 'foo')
        self.assertEqual(ref.name, 'bar')
        self.assertEqual(ref.type, Ref.PLAYLIST)

    def test_track_constructor(self):
        ref = Ref.track(uri='foo', name='bar')
        self.assertEqual(ref.uri, 'foo')
        self.assertEqual(ref.name, 'bar')
        self.assertEqual(ref.type, Ref.TRACK)

class ArtistTest(unittest.TestCase):
    def test_uri(self):
        uri = 'an_uri'
        artist = Artist(uri=uri)
        self.assertEqual(artist.uri, uri)
        self.assertRaises(AttributeError, setattr, artist, 'uri', None)

    def test_name(self):
        name = 'a name'
        artist = Artist(name=name)
        self.assertEqual(artist.name, name)
        self.assertRaises(AttributeError, setattr, artist, 'name', None)

    def test_musicbrainz_id(self):
        mb_id = 'mb-id'
        artist = Artist(musicbrainz_id=mb_id)
        self.assertEqual(artist.musicbrainz_id, mb_id)
        self.assertRaises(
            AttributeError, setattr, artist, 'musicbrainz_id', None)

    def test_invalid_kwarg(self):
        test = lambda: Artist(foo='baz')
        self.assertRaises(TypeError, test)

    def test_invalid_kwarg_with_name_matching_method(self):
        test = lambda: Artist(copy='baz')
        self.assertRaises(TypeError, test)

        test = lambda: Artist(serialize='baz')
        self.assertRaises(TypeError, test)

    def test_repr(self):
        self.assertEquals(
            "Artist(name=u'name', uri=u'uri')",
            repr(Artist(uri='uri', name='name')))

    def test_serialize(self):
        self.assertDictEqual(
            {'__model__': 'Artist', 'uri': 'uri', 'name': 'name'},
            Artist(uri='uri', name='name').serialize())

    def test_serialize_falsy_values(self):
        self.assertDictEqual(
            {'__model__': 'Artist', 'uri': '', 'name': None},
            Artist(uri='', name=None).serialize())

    def test_to_json_and_back(self):
        artist1 = Artist(uri='uri', name='name')
        serialized = json.dumps(artist1, cls=ModelJSONEncoder)
        artist2 = json.loads(serialized, object_hook=model_json_decoder)
        self.assertEqual(artist1, artist2)

    def test_to_json_and_back_with_unknown_field(self):
        artist = Artist(uri='uri', name='name').serialize()
        artist['foo'] = 'foo'
        serialized = json.dumps(artist)
        test = lambda: json.loads(serialized, object_hook=model_json_decoder)
        self.assertRaises(TypeError, test)

    def test_to_json_and_back_with_field_matching_method(self):
        artist = Artist(uri='uri', name='name').serialize()
        artist['copy'] = 'foo'
        serialized = json.dumps(artist)
        test = lambda: json.loads(serialized, object_hook=model_json_decoder)
        self.assertRaises(TypeError, test)

    def test_to_json_and_back_with_field_matching_internal_field(self):
        artist = Artist(uri='uri', name='name').serialize()
        artist['__mro__'] = 'foo'
        serialized = json.dumps(artist)
        test = lambda: json.loads(serialized, object_hook=model_json_decoder)
        self.assertRaises(TypeError, test)

    def test_eq_name(self):
        artist1 = Artist(name='name')
        artist2 = Artist(name='name')
        self.assertEqual(artist1, artist2)
        self.assertEqual(hash(artist1), hash(artist2))

    def test_eq_uri(self):
        artist1 = Artist(uri='uri')
        artist2 = Artist(uri='uri')
        self.assertEqual(artist1, artist2)
        self.assertEqual(hash(artist1), hash(artist2))

    def test_eq_musibrainz_id(self):
        artist1 = Artist(musicbrainz_id='id')
        artist2 = Artist(musicbrainz_id='id')
        self.assertEqual(artist1, artist2)
        self.assertEqual(hash(artist1), hash(artist2))

    def test_eq(self):
        artist1 = Artist(uri='uri', name='name', musicbrainz_id='id')
        artist2 = Artist(uri='uri', name='name', musicbrainz_id='id')
        self.assertEqual(artist1, artist2)
        self.assertEqual(hash(artist1), hash(artist2))

    def test_eq_none(self):
        self.assertNotEqual(Artist(), None)

    def test_eq_other(self):
        self.assertNotEqual(Artist(), 'other')

    def test_ne_name(self):
        artist1 = Artist(name='name1')
        artist2 = Artist(name='name2')
        self.assertNotEqual(artist1, artist2)
        self.assertNotEqual(hash(artist1), hash(artist2))

    def test_ne_uri(self):
        artist1 = Artist(uri='uri1')
        artist2 = Artist(uri='uri2')
        self.assertNotEqual(artist1, artist2)
        self.assertNotEqual(hash(artist1), hash(artist2))

    def test_ne_musicbrainz_id(self):
        artist1 = Artist(musicbrainz_id='id1')
        artist2 = Artist(musicbrainz_id='id2')
        self.assertNotEqual(artist1, artist2)
        self.assertNotEqual(hash(artist1), hash(artist2))

    def test_ne(self):
        artist1 = Artist(uri='uri1', name='name1', musicbrainz_id='id1')
        artist2 = Artist(uri='uri2', name='name2', musicbrainz_id='id2')
        self.assertNotEqual(artist1, artist2)
        self.assertNotEqual(hash(artist1), hash(artist2))

class AlbumTest(unittest.TestCase):
    def test_uri(self):
        uri = 'an_uri'
        album = Album(uri=uri)
        self.assertEqual(album.uri, uri)
        self.assertRaises(AttributeError, setattr, album, 'uri', None)

    def test_name(self):
        name = 'a name'
        album = Album(name=name)
        self.assertEqual(album.name, name)
        self.assertRaises(AttributeError, setattr, album, 'name', None)

    def test_artists(self):
        artist = Artist()
        album = Album(artists=[artist])
        self.assertIn(artist, album.artists)
        self.assertRaises(AttributeError, setattr, album, 'artists', None)

    def test_artists_none(self):
        self.assertEqual(set(), Album(artists=None).artists)

    def test_num_tracks(self):
        num_tracks = 11
        album = Album(num_tracks=num_tracks)
        self.assertEqual(album.num_tracks, num_tracks)
        self.assertRaises(AttributeError, setattr, album, 'num_tracks', None)

    def test_num_discs(self):
        num_discs = 2
        album = Album(num_discs=num_discs)
        self.assertEqual(album.num_discs, num_discs)
        self.assertRaises(AttributeError, setattr, album, 'num_discs', None)

    def test_date(self):
        date = '1977-01-01'
        album = Album(date=date)
        self.assertEqual(album.date, date)
        self.assertRaises(AttributeError, setattr, album, 'date', None)

    def test_musicbrainz_id(self):
        mb_id = 'mb-id'
        album = Album(musicbrainz_id=mb_id)
        self.assertEqual(album.musicbrainz_id, mb_id)
        self.assertRaises(
            AttributeError, setattr, album, 'musicbrainz_id', None)

    def test_images(self):
        image = 'data:foobar'
        album = Album(images=[image])
        self.assertIn(image, album.images)
        self.assertRaises(AttributeError, setattr, album, 'images', None)

    def test_images_none(self):
        self.assertEqual(set(), Album(images=None).images)

    def test_invalid_kwarg(self):
        test = lambda: Album(foo='baz')
        self.assertRaises(TypeError, test)

    def test_repr_without_artists(self):
        self.assertEquals(
            "Album(artists=[], images=[], name=u'name', uri=u'uri')",
            repr(Album(uri='uri', name='name')))

    def test_repr_with_artists(self):
        self.assertEquals(
            "Album(artists=[Artist(name=u'foo')], images=[], name=u'name', "
            "uri=u'uri')",
            repr(Album(uri='uri', name='name', artists=[Artist(name='foo')])))

    def test_serialize_without_artists(self):
        self.assertDictEqual(
            {'__model__': 'Album', 'uri': 'uri', 'name': 'name'},
            Album(uri='uri', name='name').serialize())

    def test_serialize_with_artists(self):
        artist = Artist(name='foo')
        self.assertDictEqual(
            {'__model__': 'Album', 'uri': 'uri', 'name': 'name',
                'artists': [artist.serialize()]},
            Album(uri='uri', name='name', artists=[artist]).serialize())

    def test_serialize_with_images(self):
        image = 'data:foobar'
        self.assertDictEqual(
            {'__model__': 'Album', 'uri': 'uri', 'name': 'name',
                'images': [image]},
            Album(uri='uri', name='name', images=[image]).serialize())

    def test_to_json_and_back(self):
        album1 = Album(uri='uri', name='name', artists=[Artist(name='foo')])
        serialized = json.dumps(album1, cls=ModelJSONEncoder)
        album2 = json.loads(serialized, object_hook=model_json_decoder)
        self.assertEqual(album1, album2)

    def test_eq_name(self):
        album1 = Album(name='name')
        album2 = Album(name='name')
        self.assertEqual(album1, album2)
        self.assertEqual(hash(album1), hash(album2))

    def test_eq_uri(self):
        album1 = Album(uri='uri')
        album2 = Album(uri='uri')
        self.assertEqual(album1, album2)
        self.assertEqual(hash(album1), hash(album2))

    def test_eq_artists(self):
        artists = [Artist()]
        album1 = Album(artists=artists)
        album2 = Album(artists=artists)
        self.assertEqual(album1, album2)
        self.assertEqual(hash(album1), hash(album2))

    def test_eq_artists_order(self):
        artist1 = Artist(name='name1')
        artist2 = Artist(name='name2')
        album1 = Album(artists=[artist1, artist2])
        album2 = Album(artists=[artist2, artist1])
        self.assertEqual(album1, album2)
        self.assertEqual(hash(album1), hash(album2))

    def test_eq_num_tracks(self):
        album1 = Album(num_tracks=2)
        album2 = Album(num_tracks=2)
        self.assertEqual(album1, album2)
        self.assertEqual(hash(album1), hash(album2))

    def test_eq_date(self):
        date = '1977-01-01'
        album1 = Album(date=date)
        album2 = Album(date=date)
        self.assertEqual(album1, album2)
        self.assertEqual(hash(album1), hash(album2))

    def test_eq_musibrainz_id(self):
        album1 = Album(musicbrainz_id='id')
        album2 = Album(musicbrainz_id='id')
        self.assertEqual(album1, album2)
        self.assertEqual(hash(album1), hash(album2))

    def test_eq(self):
        artists = [Artist()]
        album1 = Album(
            name='name', uri='uri', artists=artists, num_tracks=2,
            musicbrainz_id='id')
        album2 = Album(
            name='name', uri='uri', artists=artists, num_tracks=2,
            musicbrainz_id='id')
        self.assertEqual(album1, album2)
        self.assertEqual(hash(album1), hash(album2))

    def test_eq_none(self):
        self.assertNotEqual(Album(), None)

    def test_eq_other(self):
        self.assertNotEqual(Album(), 'other')

    def test_ne_name(self):
        album1 = Album(name='name1')
        album2 = Album(name='name2')
        self.assertNotEqual(album1, album2)
        self.assertNotEqual(hash(album1), hash(album2))

    def test_ne_uri(self):
        album1 = Album(uri='uri1')
        album2 = Album(uri='uri2')
        self.assertNotEqual(album1, album2)
        self.assertNotEqual(hash(album1), hash(album2))

    def test_ne_artists(self):
        album1 = Album(artists=[Artist(name='name1')])
        album2 = Album(artists=[Artist(name='name2')])
        self.assertNotEqual(album1, album2)
        self.assertNotEqual(hash(album1), hash(album2))

    def test_ne_num_tracks(self):
        album1 = Album(num_tracks=1)
        album2 = Album(num_tracks=2)
        self.assertNotEqual(album1, album2)
        self.assertNotEqual(hash(album1), hash(album2))

    def test_ne_date(self):
        album1 = Album(date='1977-01-01')
        album2 = Album(date='1977-01-02')
        self.assertNotEqual(album1, album2)
        self.assertNotEqual(hash(album1), hash(album2))

    def test_ne_musicbrainz_id(self):
        album1 = Album(musicbrainz_id='id1')
        album2 = Album(musicbrainz_id='id2')
        self.assertNotEqual(album1, album2)
        self.assertNotEqual(hash(album1), hash(album2))

    def test_ne(self):
        album1 = Album(
            name='name1', uri='uri1', artists=[Artist(name='name1')],
            num_tracks=1, musicbrainz_id='id1')
        album2 = Album(
            name='name2', uri='uri2', artists=[Artist(name='name2')],
            num_tracks=2, musicbrainz_id='id2')
        self.assertNotEqual(album1, album2)
        self.assertNotEqual(hash(album1), hash(album2))

class TrackTest(unittest.TestCase):
    def test_uri(self):
        uri = 'an_uri'
        track = Track(uri=uri)
        self.assertEqual(track.uri, uri)
        self.assertRaises(AttributeError, setattr, track, 'uri', None)

    def test_name(self):
        name = 'a name'
        track = Track(name=name)
        self.assertEqual(track.name, name)
        self.assertRaises(AttributeError, setattr, track, 'name', None)

    def test_artists(self):
        artists = [Artist(name='name1'), Artist(name='name2')]
        track = Track(artists=artists)
        self.assertEqual(set(track.artists), set(artists))
        self.assertRaises(AttributeError, setattr, track, 'artists', None)

    def test_artists_none(self):
        self.assertEqual(set(), Track(artists=None).artists)

    def test_composers(self):
        artists = [Artist(name='name1'), Artist(name='name2')]
        track = Track(composers=artists)
        self.assertEqual(set(track.composers), set(artists))
        self.assertRaises(AttributeError, setattr, track, 'composers', None)

    def test_composers_none(self):
        self.assertEqual(set(), Track(composers=None).composers)

    def test_performers(self):
        artists = [Artist(name='name1'), Artist(name='name2')]
        track = Track(performers=artists)
        self.assertEqual(set(track.performers), set(artists))
        self.assertRaises(AttributeError, setattr, track, 'performers', None)

    def test_performers_none(self):
        self.assertEqual(set(), Track(performers=None).performers)

    def test_album(self):
        album = Album()
        track = Track(album=album)
        self.assertEqual(track.album, album)
        self.assertRaises(AttributeError, setattr, track, 'album', None)

    def test_track_no(self):
        track_no = 7
        track = Track(track_no=track_no)
        self.assertEqual(track.track_no, track_no)
        self.assertRaises(AttributeError, setattr, track, 'track_no', None)

    def test_disc_no(self):
        disc_no = 2
        track = Track(disc_no=disc_no)
        self.assertEqual(track.disc_no, disc_no)
        self.assertRaises(AttributeError, setattr, track, 'disc_no', None)

    def test_date(self):
        date = '1977-01-01'
        track = Track(date=date)
        self.assertEqual(track.date, date)
        self.assertRaises(AttributeError, setattr, track, 'date', None)

    def test_length(self):
        length = 137000
        track = Track(length=length)
        self.assertEqual(track.length, length)
        self.assertRaises(AttributeError, setattr, track, 'length', None)

    def test_bitrate(self):
        bitrate = 160
        track = Track(bitrate=bitrate)
        self.assertEqual(track.bitrate, bitrate)
        self.assertRaises(AttributeError, setattr, track, 'bitrate', None)

    def test_musicbrainz_id(self):
        mb_id = 'mb-id'
        track = Track(musicbrainz_id=mb_id)
        self.assertEqual(track.musicbrainz_id, mb_id)
        self.assertRaises(
            AttributeError, setattr, track, 'musicbrainz_id', None)

    def test_invalid_kwarg(self):
        test = lambda: Track(foo='baz')
        self.assertRaises(TypeError, test)

    def test_repr_without_artists(self):
        self.assertEquals(
            "Track(artists=[], composers=[], name=u'name', "
            "performers=[], uri=u'uri')",
            repr(Track(uri='uri', name='name')))

    def test_repr_with_artists(self):
        self.assertEquals(
            "Track(artists=[Artist(name=u'foo')], composers=[], name=u'name', "
            "performers=[], uri=u'uri')",
            repr(Track(uri='uri', name='name', artists=[Artist(name='foo')])))

    def test_serialize_without_artists(self):
        self.assertDictEqual(
            {'__model__': 'Track', 'uri': 'uri', 'name': 'name'},
            Track(uri='uri', name='name').serialize())

    def test_serialize_with_artists(self):
        artist = Artist(name='foo')
        self.assertDictEqual(
            {'__model__': 'Track', 'uri': 'uri', 'name': 'name',
                'artists': [artist.serialize()]},
            Track(uri='uri', name='name', artists=[artist]).serialize())

    def test_serialize_with_album(self):
        album = Album(name='foo')
        self.assertDictEqual(
            {'__model__': 'Track', 'uri': 'uri', 'name': 'name',
                'album': album.serialize()},
            Track(uri='uri', name='name', album=album).serialize())

    def test_to_json_and_back(self):
        track1 = Track(
            uri='uri', name='name', album=Album(name='foo'),
            artists=[Artist(name='foo')])
        serialized = json.dumps(track1, cls=ModelJSONEncoder)
        track2 = json.loads(serialized, object_hook=model_json_decoder)
        self.assertEqual(track1, track2)

    def test_eq_uri(self):
        track1 = Track(uri='uri1')
        track2 = Track(uri='uri1')
        self.assertEqual(track1, track2)
        self.assertEqual(hash(track1), hash(track2))

    def test_eq_name(self):
        track1 = Track(name='name1')
        track2 = Track(name='name1')
        self.assertEqual(track1, track2)
        self.assertEqual(hash(track1), hash(track2))

    def test_eq_artists(self):
        artists = [Artist()]
        track1 = Track(artists=artists)
        track2 = Track(artists=artists)
        self.assertEqual(track1, track2)
        self.assertEqual(hash(track1), hash(track2))

    def test_eq_artists_order(self):
        artist1 = Artist(name='name1')
        artist2 = Artist(name='name2')
        track1 = Track(artists=[artist1, artist2])
        track2 = Track(artists=[artist2, artist1])
        self.assertEqual(track1, track2)
        self.assertEqual(hash(track1), hash(track2))

    def test_eq_album(self):
        album = Album()
        track1 = Track(album=album)
        track2 = Track(album=album)
        self.assertEqual(track1, track2)
        self.assertEqual(hash(track1), hash(track2))

    def test_eq_track_no(self):
        track1 = Track(track_no=1)
        track2 = Track(track_no=1)
        self.assertEqual(track1, track2)
        self.assertEqual(hash(track1), hash(track2))

    def test_eq_date(self):
        date = '1977-01-01'
        track1 = Track(date=date)
        track2 = Track(date=date)
        self.assertEqual(track1, track2)
        self.assertEqual(hash(track1), hash(track2))

    def test_eq_length(self):
        track1 = Track(length=100)
        track2 = Track(length=100)
        self.assertEqual(track1, track2)
        self.assertEqual(hash(track1), hash(track2))

    def test_eq_bitrate(self):
        track1 = Track(bitrate=100)
        track2 = Track(bitrate=100)
        self.assertEqual(track1, track2)
        self.assertEqual(hash(track1), hash(track2))

    def test_eq_musibrainz_id(self):
        track1 = Track(musicbrainz_id='id')
        track2 = Track(musicbrainz_id='id')
        self.assertEqual(track1, track2)
        self.assertEqual(hash(track1), hash(track2))

    def test_eq(self):
        date = '1977-01-01'
        artists = [Artist()]
        album = Album()
        track1 = Track(
            uri='uri', name='name', artists=artists, album=album, track_no=1,
            date=date, length=100, bitrate=100, musicbrainz_id='id')
        track2 = Track(
            uri='uri', name='name', artists=artists, album=album, track_no=1,
            date=date, length=100, bitrate=100, musicbrainz_id='id')
        self.assertEqual(track1, track2)
        self.assertEqual(hash(track1), hash(track2))

    def test_eq_none(self):
        self.assertNotEqual(Track(), None)

    def test_eq_other(self):
        self.assertNotEqual(Track(), 'other')

    def test_ne_uri(self):
        track1 = Track(uri='uri1')
        track2 = Track(uri='uri2')
        self.assertNotEqual(track1, track2)
        self.assertNotEqual(hash(track1), hash(track2))

    def test_ne_name(self):
        track1 = Track(name='name1')
        track2 = Track(name='name2')
        self.assertNotEqual(track1, track2)
        self.assertNotEqual(hash(track1), hash(track2))

    def test_ne_artists(self):
        track1 = Track(artists=[Artist(name='name1')])
        track2 = Track(artists=[Artist(name='name2')])
        self.assertNotEqual(track1, track2)
        self.assertNotEqual(hash(track1), hash(track2))

    def test_ne_album(self):
        track1 = Track(album=Album(name='name1'))
        track2 = Track(album=Album(name='name2'))
        self.assertNotEqual(track1, track2)
        self.assertNotEqual(hash(track1), hash(track2))

    def test_ne_track_no(self):
        track1 = Track(track_no=1)
        track2 = Track(track_no=2)
        self.assertNotEqual(track1, track2)
        self.assertNotEqual(hash(track1), hash(track2))

    def test_ne_date(self):
        track1 = Track(date='1977-01-01')
        track2 = Track(date='1977-01-02')
        self.assertNotEqual(track1, track2)
        self.assertNotEqual(hash(track1), hash(track2))

    def test_ne_length(self):
        track1 = Track(length=100)
        track2 = Track(length=200)
        self.assertNotEqual(track1, track2)
        self.assertNotEqual(hash(track1), hash(track2))

    def test_ne_bitrate(self):
        track1 = Track(bitrate=100)
        track2 = Track(bitrate=200)
        self.assertNotEqual(track1, track2)
        self.assertNotEqual(hash(track1), hash(track2))

    def test_ne_musicbrainz_id(self):
        track1 = Track(musicbrainz_id='id1')
        track2 = Track(musicbrainz_id='id2')
        self.assertNotEqual(track1, track2)
        self.assertNotEqual(hash(track1), hash(track2))

    def test_ne(self):
        track1 = Track(
            uri='uri1', name='name1', artists=[Artist(name='name1')],
            album=Album(name='name1'), track_no=1, date='1977-01-01',
            length=100, bitrate=100, musicbrainz_id='id1')
        track2 = Track(
            uri='uri2', name='name2', artists=[Artist(name='name2')],
            album=Album(name='name2'), track_no=2, date='1977-01-02',
            length=200, bitrate=200, musicbrainz_id='id2')
        self.assertNotEqual(track1, track2)
        self.assertNotEqual(hash(track1), hash(track2))

class TlTrackTest(unittest.TestCase):
    def test_tlid(self):
        tlid = 123
        tl_track = TlTrack(tlid=tlid)
        self.assertEqual(tl_track.tlid, tlid)
        self.assertRaises(AttributeError, setattr, tl_track, 'tlid', None)

    def test_track(self):
        track = Track()
        tl_track = TlTrack(track=track)
        self.assertEqual(tl_track.track, track)
        self.assertRaises(AttributeError, setattr, tl_track, 'track', None)

    def test_invalid_kwarg(self):
        test = lambda: TlTrack(foo='baz')
        self.assertRaises(TypeError, test)

    def test_positional_args(self):
        tlid = 123
        track = Track()
        tl_track = TlTrack(tlid, track)
        self.assertEqual(tl_track.tlid, tlid)
        self.assertEqual(tl_track.track, track)

    def test_iteration(self):
        tlid = 123
        track = Track()
        tl_track = TlTrack(tlid, track)
        (tlid2, track2) = tl_track
        self.assertEqual(tlid2, tlid)
        self.assertEqual(track2, track)

    def test_repr(self):
        self.assertEquals(
            "TlTrack(tlid=123, track=Track(artists=[], composers=[], "
            "performers=[], uri=u'uri'))",
            repr(TlTrack(tlid=123, track=Track(uri='uri'))))

    def test_serialize(self):
        track = Track(uri='uri', name='name')
        self.assertDictEqual(
            {'__model__': 'TlTrack', 'tlid': 123, 'track': track.serialize()},
            TlTrack(tlid=123, track=track).serialize())

    def test_to_json_and_back(self):
        tl_track1 = TlTrack(tlid=123, track=Track(uri='uri', name='name'))
        serialized = json.dumps(tl_track1, cls=ModelJSONEncoder)
        tl_track2 = json.loads(serialized, object_hook=model_json_decoder)
        self.assertEqual(tl_track1, tl_track2)

    def test_eq(self):
        tlid = 123
        track = Track()
        tl_track1 = TlTrack(tlid=tlid, track=track)
        tl_track2 = TlTrack(tlid=tlid, track=track)
        self.assertEqual(tl_track1, tl_track2)
        self.assertEqual(hash(tl_track1), hash(tl_track2))

    def test_eq_none(self):
        self.assertNotEqual(TlTrack(), None)

    def test_eq_other(self):
        self.assertNotEqual(TlTrack(), 'other')

    def test_ne_tlid(self):
        tl_track1 = TlTrack(tlid=123)
        tl_track2 = TlTrack(tlid=321)
        self.assertNotEqual(tl_track1, tl_track2)
        self.assertNotEqual(hash(tl_track1), hash(tl_track2))

    def test_ne_track(self):
        tl_track1 = TlTrack(track=Track(uri='a'))
        tl_track2 = TlTrack(track=Track(uri='b'))
        self.assertNotEqual(tl_track1, tl_track2)
        self.assertNotEqual(hash(tl_track1), hash(tl_track2))

class PlaylistTest(unittest.TestCase):
    def test_uri(self):
        uri = 'an_uri'
        playlist = Playlist(uri=uri)
        self.assertEqual(playlist.uri, uri)
        self.assertRaises(AttributeError, setattr, playlist, 'uri', None)

    def test_name(self):
        name = 'a name'
        playlist = Playlist(name=name)
        self.assertEqual(playlist.name, name)
        self.assertRaises(AttributeError, setattr, playlist, 'name', None)

    def test_tracks(self):
        tracks = [Track(), Track(), Track()]
        playlist = Playlist(tracks=tracks)
        self.assertEqual(list(playlist.tracks), tracks)
        self.assertRaises(AttributeError, setattr, playlist, 'tracks', None)

    def test_length(self):
        tracks = [Track(), Track(), Track()]
        playlist = Playlist(tracks=tracks)
        self.assertEqual(playlist.length, 3)

    def test_last_modified(self):
        last_modified = 1390942873000
        playlist = Playlist(last_modified=last_modified)
        self.assertEqual(playlist.last_modified, last_modified)
        self.assertRaises(
            AttributeError, setattr, playlist, 'last_modified', None)

    def test_with_new_uri(self):
        tracks = [Track()]
        last_modified = 1390942873000
        playlist = Playlist(
            uri='an uri', name='a name', tracks=tracks,
            last_modified=last_modified)
        new_playlist = playlist.copy(uri='another uri')
        self.assertEqual(new_playlist.uri, 'another uri')
        self.assertEqual(new_playlist.name, 'a name')
        self.assertEqual(list(new_playlist.tracks), tracks)
        self.assertEqual(new_playlist.last_modified, last_modified)

    def test_with_new_name(self):
        tracks = [Track()]
        last_modified = 1390942873000
        playlist = Playlist(
            uri='an uri', name='a name', tracks=tracks,
            last_modified=last_modified)
        new_playlist = playlist.copy(name='another name')
        self.assertEqual(new_playlist.uri, 'an uri')
        self.assertEqual(new_playlist.name, 'another name')
        self.assertEqual(list(new_playlist.tracks), tracks)
        self.assertEqual(new_playlist.last_modified, last_modified)

    def test_with_new_tracks(self):
        tracks = [Track()]
        last_modified = 1390942873000
        playlist = Playlist(
            uri='an uri', name='a name', tracks=tracks,
            last_modified=last_modified)
        new_tracks = [Track(), Track()]
        new_playlist = playlist.copy(tracks=new_tracks)
        self.assertEqual(new_playlist.uri, 'an uri')
        self.assertEqual(new_playlist.name, 'a name')
        self.assertEqual(list(new_playlist.tracks), new_tracks)
        self.assertEqual(new_playlist.last_modified, last_modified)

    def test_with_new_last_modified(self):
        tracks = [Track()]
        last_modified = 1390942873000
        new_last_modified = last_modified + 1000
        playlist = Playlist(
            uri='an uri', name='a name', tracks=tracks,
            last_modified=last_modified)
        new_playlist = playlist.copy(last_modified=new_last_modified)
        self.assertEqual(new_playlist.uri, 'an uri')
        self.assertEqual(new_playlist.name, 'a name')
        self.assertEqual(list(new_playlist.tracks), tracks)
        self.assertEqual(new_playlist.last_modified, new_last_modified)

    def test_invalid_kwarg(self):
        test = lambda: Playlist(foo='baz')
        self.assertRaises(TypeError, test)

    def test_repr_without_tracks(self):
        self.assertEquals(
            "Playlist(name=u'name', tracks=[], uri=u'uri')",
            repr(Playlist(uri='uri', name='name')))

    def test_repr_with_tracks(self):
        self.assertEquals(
            "Playlist(name=u'name', tracks=[Track(artists=[], composers=[], "
            "name=u'foo', performers=[])], uri=u'uri')",
            repr(Playlist(uri='uri', name='name', tracks=[Track(name='foo')])))

    def test_serialize_without_tracks(self):
        self.assertDictEqual(
            {'__model__': 'Playlist', 'uri': 'uri', 'name': 'name'},
            Playlist(uri='uri', name='name').serialize())

    def test_serialize_with_tracks(self):
        track = Track(name='foo')
        self.assertDictEqual(
            {'__model__': 'Playlist', 'uri': 'uri', 'name': 'name',
                'tracks': [track.serialize()]},
            Playlist(uri='uri', name='name', tracks=[track]).serialize())

    def test_to_json_and_back(self):
        playlist1 = Playlist(uri='uri', name='name')
        serialized = json.dumps(playlist1, cls=ModelJSONEncoder)
        playlist2 = json.loads(serialized, object_hook=model_json_decoder)
        self.assertEqual(playlist1, playlist2)

    def test_eq_name(self):
        playlist1 = Playlist(name='name')
        playlist2 = Playlist(name='name')
        self.assertEqual(playlist1, playlist2)
        self.assertEqual(hash(playlist1), hash(playlist2))

    def test_eq_uri(self):
        playlist1 = Playlist(uri='uri')
        playlist2 = Playlist(uri='uri')
        self.assertEqual(playlist1, playlist2)
        self.assertEqual(hash(playlist1), hash(playlist2))

    def test_eq_tracks(self):
        tracks = [Track()]
        playlist1 = Playlist(tracks=tracks)
        playlist2 = Playlist(tracks=tracks)
        self.assertEqual(playlist1, playlist2)
        self.assertEqual(hash(playlist1), hash(playlist2))

    def test_eq_last_modified(self):
        playlist1 = Playlist(last_modified=1)
        playlist2 = Playlist(last_modified=1)
        self.assertEqual(playlist1, playlist2)
        self.assertEqual(hash(playlist1), hash(playlist2))

    def test_eq(self):
        tracks = [Track()]
        playlist1 = Playlist(
            uri='uri', name='name', tracks=tracks, last_modified=1)
        playlist2 = Playlist(
            uri='uri', name='name', tracks=tracks, last_modified=1)
        self.assertEqual(playlist1, playlist2)
        self.assertEqual(hash(playlist1), hash(playlist2))

    def test_eq_none(self):
        self.assertNotEqual(Playlist(), None)

    def test_eq_other(self):
        self.assertNotEqual(Playlist(), 'other')

    def test_ne_name(self):
        playlist1 = Playlist(name='name1')
        playlist2 = Playlist(name='name2')
        self.assertNotEqual(playlist1, playlist2)
        self.assertNotEqual(hash(playlist1), hash(playlist2))

    def test_ne_uri(self):
        playlist1 = Playlist(uri='uri1')
        playlist2 = Playlist(uri='uri2')
        self.assertNotEqual(playlist1, playlist2)
        self.assertNotEqual(hash(playlist1), hash(playlist2))

    def test_ne_tracks(self):
        playlist1 = Playlist(tracks=[Track(uri='uri1')])
        playlist2 = Playlist(tracks=[Track(uri='uri2')])
        self.assertNotEqual(playlist1, playlist2)
        self.assertNotEqual(hash(playlist1), hash(playlist2))

    def test_ne_last_modified(self):
        playlist1 = Playlist(last_modified=1)
        playlist2 = Playlist(last_modified=2)
        self.assertNotEqual(playlist1, playlist2)
        self.assertNotEqual(hash(playlist1), hash(playlist2))

    def test_ne(self):
        playlist1 = Playlist(
            uri='uri1', name='name1', tracks=[Track(uri='uri1')],
            last_modified=1)
        playlist2 = Playlist(
            uri='uri2', name='name2', tracks=[Track(uri='uri2')],
            last_modified=2)
        self.assertNotEqual(playlist1, playlist2)
        self.assertNotEqual(hash(playlist1), hash(playlist2))

class SearchResultTest(unittest.TestCase):
    def test_uri(self):
        uri = 'an_uri'
        result = SearchResult(uri=uri)
        self.assertEqual(result.uri, uri)
        self.assertRaises(AttributeError, setattr, result, 'uri', None)

    def test_tracks(self):
        tracks = [Track(), Track(), Track()]
        result = SearchResult(tracks=tracks)
        self.assertEqual(list(result.tracks), tracks)
        self.assertRaises(AttributeError, setattr, result, 'tracks', None)

    def test_artists(self):
        artists = [Artist(), Artist(), Artist()]
        result = SearchResult(artists=artists)
        self.assertEqual(list(result.artists), artists)
        self.assertRaises(AttributeError, setattr, result, 'artists', None)

    def test_albums(self):
        albums = [Album(), Album(), Album()]
        result = SearchResult(albums=albums)
        self.assertEqual(list(result.albums), albums)
        self.assertRaises(AttributeError, setattr, result, 'albums', None)

    def test_invalid_kwarg(self):
        test = lambda: SearchResult(foo='baz')
        self.assertRaises(TypeError, test)

    def test_repr_without_results(self):
        self.assertEquals(
            "SearchResult(albums=[], artists=[], tracks=[], uri=u'uri')",
            repr(SearchResult(uri='uri')))

    def test_serialize_without_results(self):
        self.assertDictEqual(
            {'__model__': 'SearchResult', 'uri': 'uri'},
            SearchResult(uri='uri').serialize())

    def test_to_json_and_back(self):
        result1 = SearchResult(uri='uri')
        serialized = json.dumps(result1, cls=ModelJSONEncoder)
        result2 = json.loads(serialized, object_hook=model_json_decoder)
        self.assertEqual(result1, result2)


from tkinter import *
from tkinter import messagebox
import requests, json, os, configparser

class LivestreamerHelper:

    def __init__(self):
        print("LivestreamerHelper v1.1")
        self.streamerList = []
        self.currentGame = ""
        self.refreshed = False
        self.root = Tk()
        self.root.title("LivestreamerHelper v1.1")

        self.upperFrame = Frame(self.root)
        self.bottomFrame = Frame(self.root)

        self.leftFrame = Frame(self.bottomFrame)
        self.rightFrame = Frame(self.bottomFrame)

        self.upperFrame.pack(side = TOP)
        self.bottomFrame.pack(side = BOTTOM)

        self.leftFrame.pack(side = LEFT)
        self.rightFrame.pack(side = RIGHT)

        if os.path.isfile("LivestreamerHelper.ini") and os.access("LivestreamerHelper.ini", os.R_OK):
            self.iniExists = True
        else:
            with open("LivestreamerHelper.ini", "w+") as out:
                out.write("[settings]\n")
                out.write("amountStreamers=1000\n")
                out.write("amountGames=25\n")
                self.iniExists = True

        if self.iniExists == True:
            self.configFile = configparser.ConfigParser()
            self.configFile.read("LivestreamerHelper.ini")
            self.configAmountStreamers = self.configFile.get("settings","amountStreamers")
            self.configAmountGames = self.configFile.get("settings","amountGames")

        refreshButton = Button(self.upperFrame, text="Refresh", command=self.refresh, width=20).grid(row=0,column=0)

        configButton = Button(self.upperFrame, text="Configuration", command=self.config, width=20).grid(row=0,column=1)

        quickButton = Button(self.upperFrame, text="Quick Watch", command=self.quick_watch, width=20).grid(row=0,column=2)

        self.listbox = Listbox(self.leftFrame, selectmode=SINGLE,width=50,height=25)
        url_games = "https://api.twitch.tv/kraken/games/top?limit="+self.configAmountGames

        games = requests.get(url_games).json()

        for key, item in enumerate(games['top']):
            name = item['game']['name']
            game_id = str(item['game']['_id'])
            self.listbox.insert(END, name)
            self.listbox.bind("<<ListboxSelect>>", self.game_selected)

        self.listbox.pack()

        self.streams = Listbox(self.rightFrame, selectmode=SINGLE,width=150,height=25)
        self.streams.insert(END, "Please select a game first...")
        self.streams.pack()

        mainloop()

    def game_selected(self, event):
        self.streamerList = []
        selectedGame = self.listbox.get(self.listbox.curselection())
        selectedGame = selectedGame.replace(" ", "+")
        self.currentGame = selectedGame
        self.streams.delete(0, END)

        url_streamers = "https://api.twitch.tv/kraken/streams?limit="+self.configAmountStreamers+"&game="
        url = url_streamers + selectedGame

        streamers = requests.get(url).json()

        for skey, sitem in enumerate(streamers['streams']):
            channelname = sitem['channel']['name']
            viewers = str(sitem['viewers'])
            self.streamerList.append(channelname)

            self.streams.insert(END, channelname + " (" + viewers + " viewers) ")
            self.streams.bind("<Double-1>", self.streamer_selected)

    def streamer_selected(self, event):
        selection = self.streams.curselection()[0]
        selectedChannel = self.streamerList[selection]

        os.system("start /wait cmd /c livestreamer http://twitch.tv/" + selectedChannel + " source")

    def refresh(self):
        if self.currentGame == "":
            return False

        self.streams.delete(0, END)

        url_streamers = "https://api.twitch.tv/kraken/streams?limit="+self.configAmountStreamers+"&game="
        url = url_streamers + self.currentGame
        streamers = requests.get(url).json()

        for skey, sitem in enumerate(streamers['streams']):
            channelname = sitem['channel']['name']
            viewers = str(sitem['viewers'])
            self.streamerList.append(channelname)

            self.streams.insert(END, channelname + " (" + viewers + " viewers) ")
            self.streams.bind("<Double-1>", self.streamer_selected)

    def config(self):
        self.confTk = Tk()
        self.confTk.title("LivestreamerHelper Configuration")

        self.amountGamesLabel = Label(self.confTk, text="Amount of listed games:").grid(row=0)
        self.amountGamesInput = Entry(self.confTk)
        self.amountGamesInput.insert(END, self.configAmountGames)
        self.amountGamesInput.grid(row=0,column=1)

        self.amountStreamersLabel = Label(self.confTk, text="Amount of listed streamers:").grid(row=1,ipady=10)
        self.amountStreamersInput = Entry(self.confTk)
        self.amountStreamersInput.insert(END, self.configAmountStreamers)
        self.amountStreamersInput.grid(row=1,column=1)

        self.saveButton = Button(self.confTk, text="Save!", command=self.config_save, width=20).grid(row=2,columnspan=2)

    def config_save(self):
        self.configFile.set("settings", "amountStreamers", self.amountStreamersInput.get())
        self.configFile.set("settings", "amountGames", self.amountGamesInput.get())
        with open("LivestreamerHelper.ini", "w") as iniFile:
            self.configFile.write(iniFile)
            messagebox.showinfo("Saved!", "Your settings have been saved. Some settings will apply after the next restart!")
            self.confTk.destroy()

    def quick_watch(self):
        self.quickWatch = Tk()
        self.quickWatch.title("LivestreamerHelper Quick Watch")

        self.quickWatchLabel_1 = Label(self.quickWatch, text="http://twitch.tv/").grid(row=0,column=0)

        self.quickWatchInput = Entry(self.quickWatch)
        self.quickWatchInput.grid(row=0,column=1)

        self.quickWatchButton = Button(self.quickWatch, text="Watch!", command=self.quick_watch_now, width=20).grid(row=2, columnspan=2)

    def quick_watch_now(self):
        quick_watch_channel = self.quickWatchInput.get()
        if quick_watch_channel == "":
            return False
        else:
            self.quickWatch.destroy()
            os.system("start /wait cmd /c livestreamer http://twitch.tv/" + quick_watch_channel + " source")

if __name__ == "__main__":
    app = LivestreamerHelper()