def test_to_json(self):
        app_state = AppState()

        new_conf = DownloadConfiguration(number_of_images=9309,
                                         images_per_category=83,
                                         download_destination='481516')

        last_result = Result(failed_urls=['1', 'one'], succeeded_urls=['x'])
        progress_info = ProgressInfo(total_downloaded=192,
                                     total_failed=38,
                                     finished=False,
                                     last_result=last_result)

        position = Position(3, 1)
        counts = {'wnid1': 29, 'wnid10': 3}
        internal = InternalState(iterator_position=position,
                                 category_counts=counts,
                                 file_index=322)

        app_state.set_configuration(new_conf)
        app_state.set_progress_info(progress_info)
        app_state.set_internal_state(internal)

        app_state.add_error('Some error')

        state_data = json.loads(app_state.to_json())

        self.assertEqual(state_data['downloadPath'], '481516')
        self.assertEqual(state_data['numberOfImages'], 9309)
        self.assertEqual(state_data['imagesPerCategory'], 83)
        self.assertNotEqual(state_data['timeLeft'], '')
        self.assertEqual(state_data['imagesLoaded'], 192)
        self.assertEqual(state_data['failures'], 38)
        self.assertEqual(state_data['failedUrls'], ['1', 'one'])
        self.assertEqual(state_data['succeededUrls'], ['x'])

        self.assertEqual(state_data['errors'], ['Some error'])

        self.assertAlmostEqual(state_data['progress'], 192.0 / 9309)
class StateManager(QtCore.QObject):
    stateChanged = QtCore.pyqtSignal()
    exceptionRaised = QtCore.pyqtSignal(str, arguments=['message'])

    def __init__(self):
        super().__init__()
        self._app_state = AppState()
        self._log_path = config.log_path

        if self._app_state.progress_info.finished:
            self._state = 'finished'
        elif self._app_state.inprogress:
            self._state = 'paused'
        else:
            self._state = 'initial'
            self._reset_log()

        self._strategy = self.get_strategy()
        self._connect_signals()

    def _connect_signals(self):
        def handle_loaded(urls):
            self.stateChanged.emit()

        def handle_failed(urls):
            self._log_failures(self._log_path, urls)
            self.stateChanged.emit()

        def handle_paused():
            self._state = 'paused'
            self.stateChanged.emit()

        def handle_allDownloaded():
            self._state = 'finished'
            self._app_state.mark_finished()
            self._app_state.save()
            self.stateChanged.emit()

        def handle_exception(message):
            self._state = 'error'
            self.exceptionRaised.emit(message)

        self._strategy.imagesLoaded.connect(handle_loaded)
        self._strategy.downloadFailed.connect(handle_failed)

        self._strategy.downloadPaused.connect(handle_paused)
        self._strategy.allDownloaded.connect(handle_allDownloaded)

        self._strategy.exceptionRaised.connect(handle_exception)

    def _reset_log(self):
        if not os.path.exists(config.app_data_folder):
            os.mkdir(config.app_data_folder)

        if os.path.isfile(self._log_path):
            os.remove(self._log_path)

        with open(self._log_path, 'w') as f:
            f.write('')

    def _log_failures(self, log_path, urls):
        with open(log_path, 'a') as f:
            lines = '\n'.join(urls)
            f.write(lines)

    def get_strategy(self):
        return DummyStrategy()

    @QtCore.pyqtSlot()
    def start_download(self):
        if self._state == 'ready':
            self._state = 'running'
            self.stateChanged.emit()
            self._strategy.start()

    @QtCore.pyqtSlot(str, int, int)
    def configure(self, destination, number_of_images,
                  images_per_category):
        if self._state not in ['initial', 'ready']:
            return

        self._app_state.reset()

        conf = DownloadConfiguration(number_of_images=number_of_images,
                                     images_per_category=images_per_category,
                                     download_destination=destination,
                                     batch_size=config.default_batch_size)
        if conf.is_valid:
            self._state = 'ready'
            path = self._parse_url(destination)
            conf.download_destination = path
            self._app_state.set_configuration(conf)
        else:
            self._state = 'initial'
            self._generate_error_messages(conf)

        self.stateChanged.emit()

    def _generate_error_messages(self, download_conf):
        for e in download_conf.errors:
            self._app_state.add_error(e)

    def _parse_url(self, file_uri):
        p = urlparse(file_uri)
        return os.path.abspath(os.path.join(p.netloc, p.path))

    @QtCore.pyqtSlot()
    def pause(self):
        if self._state == 'running':
            self._state = 'pausing'
            self.stateChanged.emit()
            self._strategy.pause_download()

    @QtCore.pyqtSlot()
    def resume(self):
        if self._state == 'paused':
            self._state = 'running'
            self._strategy.resume_download()
            self.stateChanged.emit()

    @QtCore.pyqtSlot()
    def reset(self):
        if self._state not in ['running', 'pausing']:
            self._state = 'initial'
            self._reset_log()
            self._app_state.reset()
            self._strategy.quit()
            self._strategy = self.get_strategy()
            self._connect_signals()

            self._app_state.save()
            self.stateChanged.emit()

    @QtCore.pyqtProperty(str)
    def download_state(self):
        return self._state

    @QtCore.pyqtProperty(str)
    def state_data_json(self):
        return self._app_state.to_json()

    @QtCore.pyqtProperty(str)
    def time_remaining(self):
        return self._app_state.time_remaining