示例#1
0
    def __init__(self, *, adapterConfig, taskModel, parent=None):
        super().__init__(parent)
        self._adapterConfig = adapterConfig
        self._ws = None

        self.klassMap = KlassMap(adapter=self,
                                 namespace=self.namespace,
                                 taskModel=taskModel)
        self.klassMap.addTaskMap(TaskMap(klass=Aria2TaskClass.Active))
        self.klassMap.addTaskMap(TaskMap(klass=Aria2TaskClass.Waiting))
        self.klassMap.addTaskMap(TaskMap(klass=Aria2TaskClass.Stopped))

        # Stats
        self._ulSpeed = 0
        self._dlSpeed = 0
        self._activeCount = 0
        self._waitingCount = 0
        self._stoppedCount = 0
        self._stoppedTotalCount = 0

        self._ids = {}
        self._loop = None
        self._loop_executor = None
        self._loop_thread = threading.Thread(daemon=True,
                                             target=self._startEventLoop,
                                             name=adapterConfig.name)
示例#2
0
    def setUp(self):
        adapter = mock.Mock()
        taskModel = mock.Mock()
        self.km = KlassMap(adapter=adapter,
                           namespace="foo-ins1",
                           taskModel=taskModel)
        self.tm1 = TaskMapBase(klass=0)
        self.tm1.__class__._Item = DummyTaskItem
        self.km.addTaskMap(self.tm1)
        self.tm2 = TaskMapBase(klass=1)
        self.tm2.__class__._Item = DummyTaskItem
        self.km.addTaskMap(self.tm2)

        self.am = mock.Mock()
        self.km.setAdapterMap(self.am)
示例#3
0
    def __init__(self, *, adapterConfig, taskModel, parent = None):
        super().__init__(parent)
        self._adapterConfig = adapterConfig
        self._ws = None

        self.klassMap = KlassMap(adapter = self, namespace = self.namespace, taskModel = taskModel)
        self.klassMap.addTaskMap(
            TaskMap(klass = Aria2TaskClass.Active)
        )
        self.klassMap.addTaskMap(
            TaskMap(klass = Aria2TaskClass.Waiting)
        )
        self.klassMap.addTaskMap(
            TaskMap(klass = Aria2TaskClass.Stopped)
        )

        # Stats
        self._ulSpeed = 0
        self._dlSpeed = 0
        self._activeCount = 0
        self._waitingCount = 0
        self._stoppedCount = 0
        self._stoppedTotalCount = 0

        self._ids = {}
        self._loop = None
        self._loop_executor = None
        self._loop_thread = threading.Thread(daemon = True,
                                             target = self._startEventLoop,
                                             name = adapterConfig.name)
示例#4
0
    def setUp(self):
        adapter = mock.Mock()
        taskModel = mock.Mock()
        self.km = KlassMap(adapter = adapter, namespace = "foo-ins1", taskModel = taskModel)
        self.tm1 = TaskMapBase(klass = 0)
        self.tm1.__class__._Item = DummyTaskItem
        self.km.addTaskMap(self.tm1)
        self.tm2 = TaskMapBase(klass = 1)
        self.tm2.__class__._Item = DummyTaskItem
        self.km.addTaskMap(self.tm2)

        self.am = mock.Mock()
        self.km.setAdapterMap(self.am)
示例#5
0
class KlassMapTest(TestCase):
    def setUp(self):
        adapter = mock.Mock()
        taskModel = mock.Mock()
        self.km = KlassMap(adapter=adapter,
                           namespace="foo-ins1",
                           taskModel=taskModel)
        self.tm1 = TaskMapBase(klass=0)
        self.tm1.__class__._Item = DummyTaskItem
        self.km.addTaskMap(self.tm1)
        self.tm2 = TaskMapBase(klass=1)
        self.tm2.__class__._Item = DummyTaskItem
        self.km.addTaskMap(self.tm2)

        self.am = mock.Mock()
        self.km.setAdapterMap(self.am)

    def test_create_init(self):
        self.assertEqual(self.km.namespace, "foo-ins1")
        self.assertEqual(len(self.km), 0)
        self.assertRaises(NotImplementedError, self.km.__setitem__, "what",
                          "ever")

        # namespace is set
        self.assertEqual(self.tm1.namespace, "foo-ins1")

    def test_create_same_klass(self):
        # try add it again, but with the same class
        tm1_1 = TaskMapBase(klass=0)
        self.assertRaises(RuntimeError, self.km.addTaskMap, tm1_1)

    def test_task_add_update_delete(self):
        self.tm1.updateData({
            "1": "task1",
            "2": "task2",
        })
        self.assertEqual(len(self.km), 2)
        self.assertEqual(self.km["1"].value, "task1")
        self.assertEqual(self.km["1"].klass, 0)
        self.assertEqual(self.km["2"].value, "task2")
        self.assertEqual(self.km["2"].klass, 0)
        self.assertEqual(self.km["2"].isDeletionPending, False)

        # change item
        self.tm1.updateData({
            "1": "task1!",
            "2": "task2",
        })
        self.assertEqual(len(self.km), 2)
        self.assertEqual(self.km["1"].value, "task1!")
        self.assertEqual(self.km["2"].value, "task2")
        self.assertEqual(self.km["2"].isDeletionPending, False)

        # remove one item
        self.tm1.updateData({
            "1": "task1!",
        })
        self.assertEqual(len(self.km), 2)
        self.assertEqual(self.km["2"].isDeletionPending, True)

        # try to remove it again, shouldn't remove
        self.tm1.updateData({
            "1": "task1!",
        })
        self.assertEqual(len(self.km), 2)
        self.assertEqual(self.km["2"].isDeletionPending, True)

        # updateData with taskMap2
        self.tm2.updateData({})
        self.assertEqual(len(self.km), 1)
        self.assertRaises(KeyError, self.km.__getitem__, "2")

    def test_task_move(self):
        # order: 1, 2, then move 1 to 2
        self.tm1.updateData({
            "1": "task1",
        })

        self.tm2.updateData({
            "1": "task1??",
        })
        self.tm1.updateData({})

        self.assertEqual(self.km["1"].isDeletionPending, True)

        # until tm1 deletes it, it shouldn't be in tm2
        self.assertEqual(len(self.tm1), 1)
        self.assertEqual(len(self.tm2), 0)

        # after tm2 updates again, it should be moved
        self.tm2.updateData({
            "1": "task1??",
        })
        self.assertEqual(len(self.tm1), 0)
        self.assertEqual(len(self.tm2), 1)
        self.assertEqual(self.tm2["1"].value, "task1??")
        self.assertEqual(self.tm2["1"].isDeletionPending, False)

        self.assertFalse(self.am.afterMove.called)

    def test_task_move_model_move_down(self):
        # set tm1 = [0,1,2,3] tm2=[4,5]
        self.tm1.updateData({
            "0": "task0",
        })
        self.tm1.updateData({
            "0": "task0",
            "1": "task1",
        })
        self.tm1.updateData({
            "0": "task0",
            "1": "task1",
            "2": "task2",
        })
        self.tm1.updateData({
            "0": "task0",
            "1": "task1",
            "2": "task2",
            "3": "task3",
        })
        self.tm2.updateData({
            "4": "task4",
            "5": "task5",
        })

        # move 2 from tm1 to tm2
        self.assertFalse(self.km["2"].isDeletionPending)
        self.tm1.updateData({
            "0": "task0",
            "1": "task1",
            "3": "task3",
        })
        self.assertTrue(self.km["2"].isDeletionPending)
        self.tm2.updateData({
            "2": "task2",
            "4": "task4",
            "5": "task5",
        })
        self.assertFalse(self.km["2"].isDeletionPending)

        self.am.beforeMove.assert_called_with("foo-ins1", 2, 6)

    def test_task_move_model_move_up(self):
        # tm1 is empty; tm2's 3rd item is task2
        self.tm1.updateData({})
        self.tm2.updateData({
            "0": "task0",
            "1": "task1",
        })
        self.tm2.updateData({
            "0": "task0",
            "1": "task1",
            "2": "task2",
        })
        self.tm2.updateData({
            "0": "task0",
            "1": "task1",
            "2": "task2",
            "3": "task3",
            "4": "task4",
            "5": "task5",
        })

        # move task2 to tm1 from tm2
        self.assertFalse(self.km["2"].isDeletionPending)
        self.tm2.updateData({
            "0": "task0",
            "1": "task1",
            "3": "task3",
            "4": "task4",
            "5": "task5",
        })
        self.assertTrue(self.km["2"].isDeletionPending)
        self.tm1.updateData({
            "2": "task2moved",
        })
        self.assertFalse(self.km["2"].isDeletionPending)
        self.am.beforeMove.assert_called_with("foo-ins1", 2, 0)

    def test_iter_index(self):
        self.tm1.updateData({
            "1": 10,
        })
        self.assertEqual(self.km.index("1"), 0)

        self.tm2.updateData({
            "4": 41,
        })
        self.assertEqual(self.km.index("4"), 1)

        self.tm2.updateData({
            "4": 41,
            "2": 25,
        })
        self.assertEqual(self.km.index("2"), 2)

        self.tm1.updateData({
            "1": 10,
            "3": 37,
        })
        self.assertEqual(self.km.index("3"), 1)

        # index()
        with self.assertRaises(ValueError):
            self.km.index("0")

        # __contains__
        for key in ["1", "2", "3", "4"]:
            self.assertTrue(key in self.km, key)
        for key in ["5", "6", "7"]:
            self.assertFalse(key in self.km, key)

        # __iter__
        self.assertListEqual(list(self.km.__iter__()), ['1', '3', '4', '2'])

        # items()
        self.assertListEqual(
            list(map(lambda pair: (pair[0], pair[1].value), self.km.items())),
            [("1", 10), ("3", 37), ("4", 41), ("2", 25)],
        )

        # values()
        self.assertListEqual(
            list(map(lambda i: i.value, self.km.values())),
            [10, 37, 41, 25],
        )

    def test_get_klassMap(self):
        self.assertEqual(self.km.klass(0), self.tm1)
        self.assertNotEqual(self.km.klass(1), self.tm1)

    def test_findItemKlass(self):
        self.tm1.updateData({
            "1": 1,
            "2": 2,
        })
        self.tm2.updateData({
            "3": 3,
        })
        self.assertEqual(self.km.findItemKlass("1"), 0)
        self.assertEqual(self.km.findItemKlass("2"), 0)
        self.assertEqual(self.km.findItemKlass("3"), 1)
        with self.assertRaises(KeyError):
            self.km.findItemKlass("4")
示例#6
0
    def __init__(self, *, adapterConfig, taskModel, parent = None):
        super().__init__(parent)
        # Prepare XwareClient Variables
        self._ulSpeed = 0
        self._dlSpeed = 0
        self._runningTaskCount = 0
        from .vanilla import GetSysInfo
        self._sysInfo = GetSysInfo(Return = 0, Network = 0, License = 0, Bound = 0,
                                   ActivateCode = "", Mount = 0, InternalVersion = "",
                                   Nickname = "", Unknown = "", UserId = 0, VipLevel = 0)
        self._xwareSettings = XwareSettings(self)

        # Prepare XwaredClient Variables
        self._xwaredRunning = False
        self._etmPid = 0
        self._peerId = ""
        self._startEtmWhen = 1

        self._adapterConfig = adapterConfig
        connection = parse.urlparse(self._adapterConfig["connection"], scheme = "file")
        self.xwaredSocket = None
        self.mountsFaker = None

        self.klassMap = KlassMap(adapter = self, namespace = self.namespace, taskModel = taskModel)
        self.klassMap.addTaskMap(
            TaskMap(klass = TaskClass.RUNNING)
        )
        self.klassMap.addTaskMap(
            TaskMap(klass = TaskClass.COMPLETED)
        )
        self.klassMap.addTaskMap(
            TaskMap(klass = TaskClass.RECYCLED)
        )
        self.klassMap.addTaskMap(
            TaskMap(klass = TaskClass.FAILED_ON_SUBMISSION)
        )

        self.useXwared = False
        self.isLocal = False
        _clientInitOptions = dict()
        if connection.scheme == "file":
            _clientInitOptions["host"] = "127.0.0.1"
            self.useXwared = True
            self.xwaredSocket = os.path.expanduser(connection.path)
            app.aboutToQuit.connect(lambda: self.do_daemon_quitFrontend())
            self._notifyFrontendStart = True
            from .mounts import MountsFaker
            self.mountsFaker = MountsFaker(constants.MOUNTS_FILE)
        elif connection.scheme == "http":
            # assume etm is always running
            self._etmPid = 0xDEADBEEF
            host, port = connection.netloc.split(":")
            self._peerId = connection.query
            _clientInitOptions["host"] = host
            _clientInitOptions["port"] = port
        else:
            raise NotImplementedError()

        self._loop = None
        self._loop_executor = None
        self._xwareClient = None
        self._loop_thread = threading.Thread(daemon = True,
                                             target = self._startEventLoop,
                                             args = (_clientInitOptions,),
                                             name = adapterConfig.name)
示例#7
0
class XwareAdapter(QObject):
    initialized = pyqtSignal()
    infoUpdated = pyqtSignal()  # daemon infoPolled

    Manifest = {
        "SupportedTypes": [
            TaskCreationType.Normal, TaskCreationType.Emule, TaskCreationType.Magnet,
            TaskCreationType.RemoteTorrent, TaskCreationType.LocalTorrent,
        ],
    }

    def __init__(self, *, adapterConfig, taskModel, parent = None):
        super().__init__(parent)
        # Prepare XwareClient Variables
        self._ulSpeed = 0
        self._dlSpeed = 0
        self._runningTaskCount = 0
        from .vanilla import GetSysInfo
        self._sysInfo = GetSysInfo(Return = 0, Network = 0, License = 0, Bound = 0,
                                   ActivateCode = "", Mount = 0, InternalVersion = "",
                                   Nickname = "", Unknown = "", UserId = 0, VipLevel = 0)
        self._xwareSettings = XwareSettings(self)

        # Prepare XwaredClient Variables
        self._xwaredRunning = False
        self._etmPid = 0
        self._peerId = ""
        self._startEtmWhen = 1

        self._adapterConfig = adapterConfig
        connection = parse.urlparse(self._adapterConfig["connection"], scheme = "file")
        self.xwaredSocket = None
        self.mountsFaker = None

        self.klassMap = KlassMap(adapter = self, namespace = self.namespace, taskModel = taskModel)
        self.klassMap.addTaskMap(
            TaskMap(klass = TaskClass.RUNNING)
        )
        self.klassMap.addTaskMap(
            TaskMap(klass = TaskClass.COMPLETED)
        )
        self.klassMap.addTaskMap(
            TaskMap(klass = TaskClass.RECYCLED)
        )
        self.klassMap.addTaskMap(
            TaskMap(klass = TaskClass.FAILED_ON_SUBMISSION)
        )

        self.useXwared = False
        self.isLocal = False
        _clientInitOptions = dict()
        if connection.scheme == "file":
            _clientInitOptions["host"] = "127.0.0.1"
            self.useXwared = True
            self.xwaredSocket = os.path.expanduser(connection.path)
            app.aboutToQuit.connect(lambda: self.do_daemon_quitFrontend())
            self._notifyFrontendStart = True
            from .mounts import MountsFaker
            self.mountsFaker = MountsFaker(constants.MOUNTS_FILE)
        elif connection.scheme == "http":
            # assume etm is always running
            self._etmPid = 0xDEADBEEF
            host, port = connection.netloc.split(":")
            self._peerId = connection.query
            _clientInitOptions["host"] = host
            _clientInitOptions["port"] = port
        else:
            raise NotImplementedError()

        self._loop = None
        self._loop_executor = None
        self._xwareClient = None
        self._loop_thread = threading.Thread(daemon = True,
                                             target = self._startEventLoop,
                                             args = (_clientInitOptions,),
                                             name = adapterConfig.name)

    def start(self):
        self._loop_thread.start()

    def _startEventLoop(self, clientInitOptions = None):
        self._loop = asyncio.new_event_loop()
        self._loop.set_debug(True)
        self._loop_executor = ThreadPoolExecutor(max_workers = 1)
        self._loop.set_default_executor(self._loop_executor)
        asyncio.events.set_event_loop(self._loop)
        self._xwareClient = XwareClient()
        self.setClientOptions(clientInitOptions or dict())
        asyncio.ensure_future(self.main())
        self._loop.run_forever()

    @pyqtProperty(str, notify = initialized)
    def namespace(self):
        return "xware-" + self._adapterConfig.name[len("adapter-"):]

    @pyqtProperty(str, notify = initialized)
    def name(self):
        return self._adapterConfig["name"]

    @pyqtProperty(str, notify = initialized)
    def connection(self):
        return self._adapterConfig["connection"]

    @property
    def ulSpeed(self):
        return self._ulSpeed

    @property
    def dlSpeed(self):
        return self._dlSpeed

    @property
    def runningTaskCount(self):
        return self._runningTaskCount

    @property
    def backendSettings(self):
        return self._xwareSettings

    def setClientOptions(self, clientOptions: dict):
        host = clientOptions.get("host", None)
        if host in ("127.0.0.1", "localhost"):
            self.isLocal = True

        self._xwareClient.updateOptions(clientOptions)

    # =========================== PUBLIC ===========================
    @asyncio.coroutine
    def main(self):
        # Entry point of the thread "XwareAdapterEventLoop"
        # main() handles non-stop polling
        if getattr(self, "_notifyFrontendStart", False):
            self._loop.call_soon(self.daemon_start)

        while True:
            self._loop.call_soon(self.get_getsysinfo)
            self._loop.call_soon(self.get_list, TaskClass.RUNNING)
            self._loop.call_soon(self.get_list, TaskClass.COMPLETED)
            self._loop.call_soon(self.get_list, TaskClass.RECYCLED)
            self._loop.call_soon(self.get_list, TaskClass.FAILED_ON_SUBMISSION)
            if not self._xwareSettings.initialized:
                self._loop.call_soon(self.get_settings)
            if self.useXwared:
                self._loop.call_soon(self.daemon_infoPoll)
            yield from asyncio.sleep(_POLLING_INTERVAL)

    # =========================== META-PROGRAMMING MAGICS ===========================
    def __getattr__(self, name):
        if name.startswith("get_") or name.startswith("post_"):
            def method(*args):
                clientMethod = getattr(self._xwareClient, name)
                coro = clientMethod(*args)
                assert asyncio.iscoroutine(coro)
                future =asyncio.ensure_future(coro)
                cb = getattr(self, "_donecb_" + name, None)
                if cb:
                    cb = partial(cb, *args)
                    future.add_done_callback(cb)
            setattr(self, name, method)
            return method
        elif name.startswith("daemon_"):
            def method(*args):
                assert self.useXwared
                curried = partial(callXwared, self)
                clientMethodName = name[len("daemon_"):]
                asyncio.ensure_future(curried(clientMethodName, args))
            setattr(self, name, method)
            return method
        raise AttributeError("XwareAdapter doesn't have a {name}.".format(**locals()))

    @property
    def sysInfo(self):
        return self._sysInfo

    def _donecb_get_getsysinfo(self, future):
        exception = future.exception()
        if not exception:
            result = future.result()
            self._sysInfo = result
        else:
            logging.error("get_getsysinfo failed.")

    def _donecb_get_list(self, klass, future):
        exception = future.exception()
        if not exception:
            result = future.result()

            if klass == TaskClass.RUNNING:
                self._ulSpeed = result["upSpeed"]
                self._dlSpeed = result["dlSpeed"]
                self._runningTaskCount = result["dlNum"]
            self.klassMap.klass(klass).updateData(result["tasks"])
        else:
            logging.error("get_list failed.")

    def _donecb_get_settings(self, future):
        exception = future.exception()
        if not exception:
            result = future.result()
            self._xwareSettings.update(result)
        else:
            logging.error("get/post settings failed.")

    def _donecb_post_settings(self, _: "new settings", future):
        return self._donecb_get_settings(future)

    def do_pauseTasks(self, tasks, options):
        taskIds = map(lambda t: t.realid, tasks)
        self._loop.call_soon_threadsafe(self.post_pause, taskIds)

    def do_startTasks(self, tasks, options):
        taskIds = map(lambda t: t.realid, tasks)
        self._loop.call_soon_threadsafe(self.post_start, taskIds)

    def do_createTask(self, creation: TaskCreation) -> (bool, str):
        if creation.kind not in self.__class__.Manifest["SupportedTypes"]:
            return False, "Not a supported type."

        # convert path
        path = self.mountsFaker.convertToMappedPath(creation.path)
        if not path:
            return False, "Not pre-mounted."

        if creation.kind in (TaskCreationType.Normal, TaskCreationType.Emule) or \
           creation.kind == TaskCreationType.RemoteTorrent:  # TODO xware not properly support yet
            fileInfo = creation.subtaskInfo[0]

            # Workaround: xware doesn't acquire filename if not set.
            filename = fileInfo.name

            self._loop.call_soon_threadsafe(self.post_createTask,
                                            path,
                                            creation.url,
                                            filename)
            return True, None

        elif creation.kind == TaskCreationType.Magnet:
            # Note:
            # To add a magnet task, xware requires a name field, same as normal and emule tasks.
            # But xware will ignore the name parameter and acquire the name on its own.
            self._loop.call_soon_threadsafe(self.post_createTask,
                                            path,
                                            creation.url,
                                            "解析中的磁力链接")
            return True, None
        elif creation.kind == TaskCreationType.LocalTorrent:
            self._loop.call_soon_threadsafe(self.post_createBtTask,
                                            )
        return False, "Not implemented."

    def do_delTasks(self, tasks, options):
        taskIds = map(lambda t: t.realid, tasks)
        self._loop.call_soon_threadsafe(self.post_del,
                                        taskIds,
                                        options["recycle"],
                                        options["delete"])

    def do_restoreTasks(self, tasks, options):
        taskIds = map(lambda t: t.realid, tasks)
        self._loop.call_soon_threadsafe(self.post_restore,
                                        taskIds)

    def do_openLixianChannel(self, taskItem, enable: bool):
        taskId = taskItem.realid
        self._loop.call_soon_threadsafe(self.post_openLixianChannel, taskId, enable)

    def do_openVipChannel(self, taskItem):
        taskId = taskItem.realid
        self._loop.call_soon_threadsafe(self.post_openVipChannel, taskId)

    def do_applySettings(self, settings: dict):
        dLimit = settings.get("downloadSpeedLimit", -1)
        uLimit = settings.get("uploadSpeedLimit", -1)

        if dLimit != -1:
            self._adapterConfig.setint("dlspeedlimit", dLimit)
        if uLimit != -1:
            self._adapterConfig.setint("ulspeedlimit", uLimit)

        self._loop.call_soon_threadsafe(self.post_settings, settings)

    # ==================== DAEMON ====================
    @pyqtProperty(bool, notify = infoUpdated)
    def xwaredRunning(self):
        return self._xwaredRunning

    @pyqtProperty(int, notify = infoUpdated)
    def etmPid(self):
        return self._etmPid

    @pyqtProperty(str, notify = infoUpdated)
    def peerId(self):
        return self._peerId

    @pyqtProperty(int, notify = infoUpdated)
    def startEtmWhen(self):
        return self._startEtmWhen

    @startEtmWhen.setter
    def startEtmWhen(self, value):
        self._loop.call_soon_threadsafe(self.daemon_setStartEtmWhen, value)

    def _donecb_daemon_infoPoll(self, data):
        error = data.get("error")
        if not error:
            result = data.get("result")
            self._xwaredRunning = True
            self._etmPid = result.get("etmPid")
            self._peerId = result.get("peerId")
            lcPort = result.get("lcPort")
            self._startEtmWhen = result.get("startEtmWhen")
        else:
            self._xwaredRunning = False
            self._etmPid = 0
            self._peerId = ""
            lcPort = 0
            self._startEtmWhen = 1
            print("infoPoll failed with error", error, file = sys.stderr)
        self.setClientOptions({
            "port": lcPort,
        })
        self.infoUpdated.emit()

    def do_daemon_start(self):
        self._loop.call_soon_threadsafe(self.daemon_startETM)

    def do_daemon_restart(self):
        self._loop.call_soon_threadsafe(self.daemon_restartETM)

    def do_daemon_stop(self):
        self._loop.call_soon_threadsafe(self.daemon_stopETM)

    def do_daemon_startFrontend(self):
        raise NotImplementedError()
        # handled in main()
        # self._loop.call_soon_threadsafe(self.daemon_start)

    def do_daemon_quitFrontend(self):
        self._loop.call_soon_threadsafe(self.daemon_quit)

    @property
    def daemonManagedBySystemd(self):
        return os.path.lexists(constants.SYSTEMD_SERVICE_ENABLED_USERFILE) and \
            os.path.lexists(constants.SYSTEMD_SERVICE_USERFILE)

    @daemonManagedBySystemd.setter
    def daemonManagedBySystemd(self, on):
        if on:
            tryMkdir(os.path.dirname(constants.SYSTEMD_SERVICE_ENABLED_USERFILE))

            trySymlink(constants.SYSTEMD_SERVICE_FILE,
                       constants.SYSTEMD_SERVICE_USERFILE)

            trySymlink(constants.SYSTEMD_SERVICE_USERFILE,
                       constants.SYSTEMD_SERVICE_ENABLED_USERFILE)
        else:
            tryRemove(constants.SYSTEMD_SERVICE_ENABLED_USERFILE)
            tryRemove(constants.SYSTEMD_SERVICE_USERFILE)
        if getInitType() == InitType.SYSTEMD:
            os.system("systemctl --user daemon-reload")

    @property
    def daemonManagedByUpstart(self):
        return os.path.lexists(constants.UPSTART_SERVICE_USERFILE)

    @daemonManagedByUpstart.setter
    def daemonManagedByUpstart(self, on):
        if on:
            tryMkdir(os.path.dirname(constants.UPSTART_SERVICE_USERFILE))

            trySymlink(constants.UPSTART_SERVICE_FILE,
                       constants.UPSTART_SERVICE_USERFILE)
        else:
            tryRemove(constants.UPSTART_SERVICE_USERFILE)
        if getInitType() == InitType.UPSTART:
            os.system("initctl --user reload-configuration")

    @property
    def daemonManagedByAutostart(self):
        return os.path.lexists(constants.AUTOSTART_DESKTOP_USERFILE)

    @daemonManagedByAutostart.setter
    def daemonManagedByAutostart(self, on):
        if on:
            tryMkdir(os.path.dirname(constants.AUTOSTART_DESKTOP_USERFILE))

            trySymlink(constants.AUTOSTART_DESKTOP_FILE,
                       constants.AUTOSTART_DESKTOP_USERFILE)
        else:
            tryRemove(constants.AUTOSTART_DESKTOP_USERFILE)
示例#8
0
    def __init__(self, *, adapterConfig, taskModel, parent = None):
        super().__init__(parent)
        # Prepare XwareClient Variables
        self._ulSpeed = 0
        self._dlSpeed = 0
        self._runningTaskCount = 0
        from .vanilla import GetSysInfo
        self._sysInfo = GetSysInfo(Return = 0, Network = 0, License = 0, Bound = 0,
                                   ActivateCode = "", Mount = 0, InternalVersion = "",
                                   Nickname = "", Unknown = "", UserId = 0, VipLevel = 0)
        self._xwareSettings = XwareSettings(self)

        # Prepare XwaredClient Variables
        self._xwaredRunning = False
        self._etmPid = 0
        self._peerId = ""
        self._startEtmWhen = 1

        self._adapterConfig = adapterConfig
        connection = parse.urlparse(self._adapterConfig["connection"], scheme = "file")
        self.xwaredSocket = None
        self.mountsFaker = None

        self.klassMap = KlassMap(adapter = self, namespace = self.namespace, taskModel = taskModel)
        self.klassMap.addTaskMap(
            TaskMap(klass = TaskClass.RUNNING)
        )
        self.klassMap.addTaskMap(
            TaskMap(klass = TaskClass.COMPLETED)
        )
        self.klassMap.addTaskMap(
            TaskMap(klass = TaskClass.RECYCLED)
        )
        self.klassMap.addTaskMap(
            TaskMap(klass = TaskClass.FAILED_ON_SUBMISSION)
        )

        self.useXwared = False
        self.isLocal = False
        _clientInitOptions = dict()
        if connection.scheme == "file":
            _clientInitOptions["host"] = "127.0.0.1"
            self.useXwared = True
            self.xwaredSocket = os.path.expanduser(connection.path)
            app.aboutToQuit.connect(lambda: self.do_daemon_quitFrontend())
            self._notifyFrontendStart = True
            from .mounts import MountsFaker
            self.mountsFaker = MountsFaker(constants.MOUNTS_FILE)
        elif connection.scheme == "http":
            # assume etm is always running
            self._etmPid = 0xDEADBEEF
            host, port = connection.netloc.split(":")
            self._peerId = connection.query
            _clientInitOptions["host"] = host
            _clientInitOptions["port"] = port
        else:
            raise NotImplementedError()

        self._loop = None
        self._loop_executor = None
        self._xwareClient = None
        self._loop_thread = threading.Thread(daemon = True,
                                             target = self._startEventLoop,
                                             args = (_clientInitOptions,),
                                             name = adapterConfig.name)
示例#9
0
class XwareAdapter(QObject):
    initialized = pyqtSignal()
    infoUpdated = pyqtSignal()  # daemon infoPolled

    Manifest = {
        "SupportedTypes": [
            TaskCreationType.Normal, TaskCreationType.Emule, TaskCreationType.Magnet,
            TaskCreationType.RemoteTorrent, TaskCreationType.LocalTorrent,
        ],
    }

    def __init__(self, *, adapterConfig, taskModel, parent = None):
        super().__init__(parent)
        # Prepare XwareClient Variables
        self._ulSpeed = 0
        self._dlSpeed = 0
        self._runningTaskCount = 0
        from .vanilla import GetSysInfo
        self._sysInfo = GetSysInfo(Return = 0, Network = 0, License = 0, Bound = 0,
                                   ActivateCode = "", Mount = 0, InternalVersion = "",
                                   Nickname = "", Unknown = "", UserId = 0, VipLevel = 0)
        self._xwareSettings = XwareSettings(self)

        # Prepare XwaredClient Variables
        self._xwaredRunning = False
        self._etmPid = 0
        self._peerId = ""
        self._startEtmWhen = 1

        self._adapterConfig = adapterConfig
        connection = parse.urlparse(self._adapterConfig["connection"], scheme = "file")
        self.xwaredSocket = None
        self.mountsFaker = None

        self.klassMap = KlassMap(adapter = self, namespace = self.namespace, taskModel = taskModel)
        self.klassMap.addTaskMap(
            TaskMap(klass = TaskClass.RUNNING)
        )
        self.klassMap.addTaskMap(
            TaskMap(klass = TaskClass.COMPLETED)
        )
        self.klassMap.addTaskMap(
            TaskMap(klass = TaskClass.RECYCLED)
        )
        self.klassMap.addTaskMap(
            TaskMap(klass = TaskClass.FAILED_ON_SUBMISSION)
        )

        self.useXwared = False
        self.isLocal = False
        _clientInitOptions = dict()
        if connection.scheme == "file":
            _clientInitOptions["host"] = "127.0.0.1"
            self.useXwared = True
            self.xwaredSocket = os.path.expanduser(connection.path)
            app.aboutToQuit.connect(lambda: self.do_daemon_quitFrontend())
            self._notifyFrontendStart = True
            from .mounts import MountsFaker
            self.mountsFaker = MountsFaker(constants.MOUNTS_FILE)
        elif connection.scheme == "http":
            # assume etm is always running
            self._etmPid = 0xDEADBEEF
            host, port = connection.netloc.split(":")
            self._peerId = connection.query
            _clientInitOptions["host"] = host
            _clientInitOptions["port"] = port
        else:
            raise NotImplementedError()

        self._loop = None
        self._loop_executor = None
        self._xwareClient = None
        self._loop_thread = threading.Thread(daemon = True,
                                             target = self._startEventLoop,
                                             args = (_clientInitOptions,),
                                             name = adapterConfig.name)

    def start(self):
        self._loop_thread.start()

    def _startEventLoop(self, clientInitOptions = None):
        self._loop = asyncio.new_event_loop()
        self._loop.set_debug(True)
        self._loop_executor = ThreadPoolExecutor(max_workers = 1)
        self._loop.set_default_executor(self._loop_executor)
        asyncio.events.set_event_loop(self._loop)
        self._xwareClient = XwareClient()
        self.setClientOptions(clientInitOptions or dict())
        asyncio.async(self.main())
        self._loop.run_forever()

    @pyqtProperty(str, notify = initialized)
    def namespace(self):
        return "xware-" + self._adapterConfig.name[len("adapter-"):]

    @pyqtProperty(str, notify = initialized)
    def name(self):
        return self._adapterConfig["name"]

    @pyqtProperty(str, notify = initialized)
    def connection(self):
        return self._adapterConfig["connection"]

    @property
    def ulSpeed(self):
        return self._ulSpeed

    @property
    def dlSpeed(self):
        return self._dlSpeed

    @property
    def runningTaskCount(self):
        return self._runningTaskCount

    @property
    def backendSettings(self):
        return self._xwareSettings

    def setClientOptions(self, clientOptions: dict):
        host = clientOptions.get("host", None)
        if host in ("127.0.0.1", "localhost"):
            self.isLocal = True

        self._xwareClient.updateOptions(clientOptions)

    # =========================== PUBLIC ===========================
    @asyncio.coroutine
    def main(self):
        # Entry point of the thread "XwareAdapterEventLoop"
        # main() handles non-stop polling
        if getattr(self, "_notifyFrontendStart", False):
            self._loop.call_soon(self.daemon_start)

        while True:
            self._loop.call_soon(self.get_getsysinfo)
            self._loop.call_soon(self.get_list, TaskClass.RUNNING)
            self._loop.call_soon(self.get_list, TaskClass.COMPLETED)
            self._loop.call_soon(self.get_list, TaskClass.RECYCLED)
            self._loop.call_soon(self.get_list, TaskClass.FAILED_ON_SUBMISSION)
            if not self._xwareSettings.initialized:
                self._loop.call_soon(self.get_settings)
            if self.useXwared:
                self._loop.call_soon(self.daemon_infoPoll)
            yield from asyncio.sleep(_POLLING_INTERVAL)

    # =========================== META-PROGRAMMING MAGICS ===========================
    def __getattr__(self, name):
        if name.startswith("get_") or name.startswith("post_"):
            def method(*args):
                clientMethod = getattr(self._xwareClient, name)
                coro = clientMethod(*args)
                assert asyncio.iscoroutine(coro)
                future =asyncio.async(coro)
                cb = getattr(self, "_donecb_" + name, None)
                if cb:
                    cb = partial(cb, *args)
                    future.add_done_callback(cb)
            setattr(self, name, method)
            return method
        elif name.startswith("daemon_"):
            def method(*args):
                assert self.useXwared
                curried = partial(callXwared, self)
                clientMethodName = name[len("daemon_"):]
                asyncio.async(curried(clientMethodName, args))
            setattr(self, name, method)
            return method
        raise AttributeError("XwareAdapter doesn't have a {name}.".format(**locals()))

    @property
    def sysInfo(self):
        return self._sysInfo

    def _donecb_get_getsysinfo(self, future):
        exception = future.exception()
        if not exception:
            result = future.result()
            self._sysInfo = result
        else:
            logging.error("get_getsysinfo failed.")

    def _donecb_get_list(self, klass, future):
        exception = future.exception()
        if not exception:
            result = future.result()

            if klass == TaskClass.RUNNING:
                self._ulSpeed = result["upSpeed"]
                self._dlSpeed = result["dlSpeed"]
                self._runningTaskCount = result["dlNum"]
            self.klassMap.klass(klass).updateData(result["tasks"])
        else:
            logging.error("get_list failed.")

    def _donecb_get_settings(self, future):
        exception = future.exception()
        if not exception:
            result = future.result()
            self._xwareSettings.update(result)
        else:
            logging.error("get/post settings failed.")

    def _donecb_post_settings(self, _: "new settings", future):
        return self._donecb_get_settings(future)

    def do_pauseTasks(self, tasks, options):
        taskIds = map(lambda t: t.realid, tasks)
        self._loop.call_soon_threadsafe(self.post_pause, taskIds)

    def do_startTasks(self, tasks, options):
        taskIds = map(lambda t: t.realid, tasks)
        self._loop.call_soon_threadsafe(self.post_start, taskIds)

    def do_createTask(self, creation: TaskCreation) -> (bool, str):
        if creation.kind not in self.__class__.Manifest["SupportedTypes"]:
            return False, "Not a supported type."

        # convert path
        path = self.mountsFaker.convertToMappedPath(creation.path)
        if not path:
            return False, "Not pre-mounted."

        if creation.kind in (TaskCreationType.Normal, TaskCreationType.Emule) or \
           creation.kind == TaskCreationType.RemoteTorrent:  # TODO xware not properly support yet
            fileInfo = creation.subtaskInfo[0]

            # Workaround: xware doesn't acquire filename if not set.
            filename = fileInfo.name

            self._loop.call_soon_threadsafe(self.post_createTask,
                                            path,
                                            creation.url,
                                            filename)
            return True, None

        elif creation.kind == TaskCreationType.Magnet:
            # Note:
            # To add a magnet task, xware requires a name field, same as normal and emule tasks.
            # But xware will ignore the name parameter and acquire the name on its own.
            self._loop.call_soon_threadsafe(self.post_createTask,
                                            path,
                                            creation.url,
                                            "解析中的磁力链接")
            return True, None
        elif creation.kind == TaskCreationType.LocalTorrent:
            self._loop.call_soon_threadsafe(self.post_createBtTask,
                                            )
        return False, "Not implemented."

    def do_delTasks(self, tasks, options):
        taskIds = map(lambda t: t.realid, tasks)
        self._loop.call_soon_threadsafe(self.post_del,
                                        taskIds,
                                        options["recycle"],
                                        options["delete"])

    def do_restoreTasks(self, tasks, options):
        taskIds = map(lambda t: t.realid, tasks)
        self._loop.call_soon_threadsafe(self.post_restore,
                                        taskIds)

    def do_openLixianChannel(self, taskItem, enable: bool):
        taskId = taskItem.realid
        self._loop.call_soon_threadsafe(self.post_openLixianChannel, taskId, enable)

    def do_openVipChannel(self, taskItem):
        taskId = taskItem.realid
        self._loop.call_soon_threadsafe(self.post_openVipChannel, taskId)

    def do_applySettings(self, settings: dict):
        dLimit = settings.get("downloadSpeedLimit", -1)
        uLimit = settings.get("uploadSpeedLimit", -1)

        if dLimit != -1:
            self._adapterConfig.setint("dlspeedlimit", dLimit)
        if uLimit != -1:
            self._adapterConfig.setint("ulspeedlimit", uLimit)

        self._loop.call_soon_threadsafe(self.post_settings, settings)

    # ==================== DAEMON ====================
    @pyqtProperty(bool, notify = infoUpdated)
    def xwaredRunning(self):
        return self._xwaredRunning

    @pyqtProperty(int, notify = infoUpdated)
    def etmPid(self):
        return self._etmPid

    @pyqtProperty(str, notify = infoUpdated)
    def peerId(self):
        return self._peerId

    @pyqtProperty(int, notify = infoUpdated)
    def startEtmWhen(self):
        return self._startEtmWhen

    @startEtmWhen.setter
    def startEtmWhen(self, value):
        self._loop.call_soon_threadsafe(self.daemon_setStartEtmWhen, value)

    def _donecb_daemon_infoPoll(self, data):
        error = data.get("error")
        if not error:
            result = data.get("result")
            self._xwaredRunning = True
            self._etmPid = result.get("etmPid")
            self._peerId = result.get("peerId")
            lcPort = result.get("lcPort")
            self._startEtmWhen = result.get("startEtmWhen")
        else:
            self._xwaredRunning = False
            self._etmPid = 0
            self._peerId = ""
            lcPort = 0
            self._startEtmWhen = 1
            print("infoPoll failed with error", error, file = sys.stderr)
        self.setClientOptions({
            "port": lcPort,
        })
        self.infoUpdated.emit()

    def do_daemon_start(self):
        self._loop.call_soon_threadsafe(self.daemon_startETM)

    def do_daemon_restart(self):
        self._loop.call_soon_threadsafe(self.daemon_restartETM)

    def do_daemon_stop(self):
        self._loop.call_soon_threadsafe(self.daemon_stopETM)

    def do_daemon_startFrontend(self):
        raise NotImplementedError()
        # handled in main()
        # self._loop.call_soon_threadsafe(self.daemon_start)

    def do_daemon_quitFrontend(self):
        self._loop.call_soon_threadsafe(self.daemon_quit)

    @property
    def daemonManagedBySystemd(self):
        return os.path.lexists(constants.SYSTEMD_SERVICE_ENABLED_USERFILE) and \
            os.path.lexists(constants.SYSTEMD_SERVICE_USERFILE)

    @daemonManagedBySystemd.setter
    def daemonManagedBySystemd(self, on):
        if on:
            tryMkdir(os.path.dirname(constants.SYSTEMD_SERVICE_ENABLED_USERFILE))

            trySymlink(constants.SYSTEMD_SERVICE_FILE,
                       constants.SYSTEMD_SERVICE_USERFILE)

            trySymlink(constants.SYSTEMD_SERVICE_USERFILE,
                       constants.SYSTEMD_SERVICE_ENABLED_USERFILE)
        else:
            tryRemove(constants.SYSTEMD_SERVICE_ENABLED_USERFILE)
            tryRemove(constants.SYSTEMD_SERVICE_USERFILE)
        if getInitType() == InitType.SYSTEMD:
            os.system("systemctl --user daemon-reload")

    @property
    def daemonManagedByUpstart(self):
        return os.path.lexists(constants.UPSTART_SERVICE_USERFILE)

    @daemonManagedByUpstart.setter
    def daemonManagedByUpstart(self, on):
        if on:
            tryMkdir(os.path.dirname(constants.UPSTART_SERVICE_USERFILE))

            trySymlink(constants.UPSTART_SERVICE_FILE,
                       constants.UPSTART_SERVICE_USERFILE)
        else:
            tryRemove(constants.UPSTART_SERVICE_USERFILE)
        if getInitType() == InitType.UPSTART:
            os.system("initctl --user reload-configuration")

    @property
    def daemonManagedByAutostart(self):
        return os.path.lexists(constants.AUTOSTART_DESKTOP_USERFILE)

    @daemonManagedByAutostart.setter
    def daemonManagedByAutostart(self, on):
        if on:
            tryMkdir(os.path.dirname(constants.AUTOSTART_DESKTOP_USERFILE))

            trySymlink(constants.AUTOSTART_DESKTOP_FILE,
                       constants.AUTOSTART_DESKTOP_USERFILE)
        else:
            tryRemove(constants.AUTOSTART_DESKTOP_USERFILE)
示例#10
0
class Aria2Adapter(QObject):
    initialized = pyqtSignal()
    statPolled = pyqtSignal()

    Manifest = {
        "SupportedTypes": [
            TaskCreationType.Normal,
            TaskCreationType.Magnet,
            TaskCreationType.RemoteTorrent,
            TaskCreationType.LocalTorrent,
        ],
    }

    def __init__(self, *, adapterConfig, taskModel, parent=None):
        super().__init__(parent)
        self._adapterConfig = adapterConfig
        self._ws = None

        self.klassMap = KlassMap(adapter=self,
                                 namespace=self.namespace,
                                 taskModel=taskModel)
        self.klassMap.addTaskMap(TaskMap(klass=Aria2TaskClass.Active))
        self.klassMap.addTaskMap(TaskMap(klass=Aria2TaskClass.Waiting))
        self.klassMap.addTaskMap(TaskMap(klass=Aria2TaskClass.Stopped))

        # Stats
        self._ulSpeed = 0
        self._dlSpeed = 0
        self._activeCount = 0
        self._waitingCount = 0
        self._stoppedCount = 0
        self._stoppedTotalCount = 0

        self._ids = {}
        self._loop = None
        self._loop_executor = None
        self._loop_thread = threading.Thread(daemon=True,
                                             target=self._startEventLoop,
                                             name=adapterConfig.name)

    def start(self):
        self._loop_thread.start()

    def _startEventLoop(self):
        self._loop = asyncio.new_event_loop()
        self._loop.set_debug(True)
        self._loop_executor = ThreadPoolExecutor(max_workers=1)
        self._loop.set_default_executor(self._loop_executor)
        asyncio.events.set_event_loop(self._loop)
        asyncio.ensure_future(self.main())
        self._loop.run_forever()

    def updateOptions(self, options):
        self._options.update(options)

    def _ready(self):
        if not self._ws:
            return False
        return self._ws.open

    @pyqtProperty(str, notify=initialized)
    def name(self):
        return self._adapterConfig["name"]

    @pyqtProperty(str, notify=initialized)
    def connection(self):
        return self._adapterConfig["connection"]

    @pyqtProperty(str, notify=initialized)
    def namespace(self):
        return "aria2-" + self._adapterConfig.name[len("adapter-"):]

    @asyncio.coroutine
    def main(self):
        asyncio.ensure_future(self._getMessage())  # It can handle reconnect
        while True:
            try:
                self._ws = yield from websockets.client.connect(
                    self._adapterConfig["connection"])
            except ConnectionRefusedError:
                yield from asyncio.sleep(1)
                continue

            while True:
                if not self._ready():
                    break

                asyncio.ensure_future(
                    self._call(_Callable(Aria2Method.GetGlobalStat)))
                asyncio.ensure_future(
                    self._call(_Callable(Aria2Method.TellActive)))
                asyncio.ensure_future(
                    self._call(_Callable(Aria2Method.TellWaiting, 0, 100000)))
                asyncio.ensure_future(
                    self._call(_Callable(Aria2Method.TellStopped, 0, 100000)))
                yield from asyncio.sleep(1)

    @asyncio.coroutine
    def _getMessage(self):
        while True:
            if not self._ready():
                yield from asyncio.sleep(1)  # try again in 1 sec
                continue

            msg = yield from self._ws.recv()
            if not msg:
                continue  # disconnected
            datas = json.loads(msg)

            # make a single call's response look like being part of a batch call
            if not isinstance(datas, list):
                datas = (datas, )

            for data in datas:
                assert data["jsonrpc"] == "2.0"
                if "error" in data:
                    print("Error Handling", data)  # TODO
                else:
                    id_ = data.get("id", None)
                    if id_:
                        # response
                        shortName = _getShortName(id_)
                        result = data["result"]
                    else:
                        # notifications
                        shortName = _getShortName(data["method"])
                        result = data["params"]

                    cb = getattr(self, "_cb_" + shortName, None)
                    if cb:
                        assert asyncio.iscoroutinefunction(cb)
                        yield from cb(result)

    @asyncio.coroutine
    def _call(self, callable_: _Callable):
        assert isinstance(callable_, _Callable)
        additionals = dict()
        if "rpc-secret" in self._adapterConfig:
            additionals["token"] = self._adapterConfig["rpc-secret"]
        payload = callable_.toString(**additionals)
        yield from self._ws.send(payload)

    def _callFromExternal(self, callable_: _Callable):
        self._loop.call_soon_threadsafe(asyncio.ensure_future,
                                        self._call(callable_))

    def do_createTask(self, creation: TaskCreation) -> (bool, str):
        url = [creation.url]
        options = dict(dir=creation.path)

        if creation.kind in (TaskCreationType.Normal, ):
            # see if need to override filename
            fileInfo = creation.subtaskInfo[0]
            if fileInfo.name_userset:
                options["out"] = fileInfo.name

            self._callFromExternal(_Callable(Aria2Method.AddUri, url, options))
            return True, None
        elif creation.kind == TaskCreationType.Magnet:
            self._callFromExternal(_Callable(Aria2Method.AddUri, url, dict()))
            return True, None

        return False, "Not implemented."

    def do_getFiles(self, gid):
        self._callFromExternal(_Callable(Aria2Method.GetFiles, gid))

    def do_delTasks(self, tasks, options):
        taskIds = map(lambda t: t.realid, tasks)
        calls = map(
            lambda gid: _Callable(Aria2Method.RemoveDownloadResult, gid),
            taskIds)
        delMultiple = _Callable(Aria2Method.MultiCall, *calls)
        # TODO: aria2 don't remove files from filesystem.
        self._callFromExternal(delMultiple)

    @asyncio.coroutine
    def _cb_tellActive(self, result):
        self.klassMap.klass(Aria2TaskClass.Active).updateData(result)

    @asyncio.coroutine
    def _cb_tellWaiting(self, result):
        self.klassMap.klass(Aria2TaskClass.Waiting).updateData(result)

    @asyncio.coroutine
    def _cb_tellStopped(self, result):
        self.klassMap.klass(Aria2TaskClass.Stopped).updateData(result)

    @asyncio.coroutine
    def _cb_getGlobalOption(self, result):
        pass

    @asyncio.coroutine
    def _cb_getFiles(self, result):
        print("_cb_getFiles", result)

    @asyncio.coroutine
    def _cb_onDownloadStart(self, event):
        print("_cb_onDownloadComplete", event)

    @asyncio.coroutine
    def _cb_onDownloadPause(self, event):
        print("_cb_onDownloadPause", event)

    @asyncio.coroutine
    def _cb_onDownloadStop(self, event):
        print("_cb_onDownloadStop", event)

    @asyncio.coroutine
    def _cb_onDownloadComplete(self, event):
        print("_cb_onDownloadComplete", event)

    @asyncio.coroutine
    def _cb_onDownloadError(self, event):
        print("_cb_onDownloadError", event)

    @asyncio.coroutine
    def _cb_onBtDownloadComplete(self, event):
        print("_cb_onBtDownloadComplete", event)

    # Stats
    @pyqtProperty(int, notify=statPolled)
    def dlSpeed(self):
        return self._dlSpeed

    @pyqtProperty(int, notify=statPolled)
    def ulSpeed(self):
        return self._ulSpeed

    @pyqtProperty(int, notify=statPolled)
    def runningTaskCount(self):
        return 0  # TODO

    @asyncio.coroutine
    def _cb_getGlobalStat(self, result):
        self._dlSpeed = int(result["downloadSpeed"])
        self._ulSpeed = int(result["uploadSpeed"])
        # TODO: waiting, stopped, stoppedtotal, active
        self.statPolled.emit()
示例#11
0
class Aria2Adapter(QObject):
    initialized = pyqtSignal()
    statPolled = pyqtSignal()

    Manifest = {
        "SupportedTypes": [
            TaskCreationType.Normal, TaskCreationType.Magnet,
            TaskCreationType.RemoteTorrent, TaskCreationType.LocalTorrent,
        ],
    }

    def __init__(self, *, adapterConfig, taskModel, parent = None):
        super().__init__(parent)
        self._adapterConfig = adapterConfig
        self._ws = None

        self.klassMap = KlassMap(adapter = self, namespace = self.namespace, taskModel = taskModel)
        self.klassMap.addTaskMap(
            TaskMap(klass = Aria2TaskClass.Active)
        )
        self.klassMap.addTaskMap(
            TaskMap(klass = Aria2TaskClass.Waiting)
        )
        self.klassMap.addTaskMap(
            TaskMap(klass = Aria2TaskClass.Stopped)
        )

        # Stats
        self._ulSpeed = 0
        self._dlSpeed = 0
        self._activeCount = 0
        self._waitingCount = 0
        self._stoppedCount = 0
        self._stoppedTotalCount = 0

        self._ids = {}
        self._loop = None
        self._loop_executor = None
        self._loop_thread = threading.Thread(daemon = True,
                                             target = self._startEventLoop,
                                             name = adapterConfig.name)

    def start(self):
        self._loop_thread.start()

    def _startEventLoop(self):
        self._loop = asyncio.new_event_loop()
        self._loop.set_debug(True)
        self._loop_executor = ThreadPoolExecutor(max_workers = 1)
        self._loop.set_default_executor(self._loop_executor)
        asyncio.events.set_event_loop(self._loop)
        asyncio.async(self.main())
        self._loop.run_forever()

    def updateOptions(self, options):
        self._options.update(options)

    def _ready(self):
        if not self._ws:
            return False
        return self._ws.open

    @pyqtProperty(str, notify = initialized)
    def name(self):
        return self._adapterConfig["name"]

    @pyqtProperty(str, notify = initialized)
    def connection(self):
        return self._adapterConfig["connection"]

    @pyqtProperty(str, notify = initialized)
    def namespace(self):
        return "aria2-" + self._adapterConfig.name[len("adapter-"):]

    @asyncio.coroutine
    def main(self):
        asyncio.async(self._getMessage())  # It can handle reconnect
        while True:
            try:
                self._ws = yield from websockets.client.connect(
                    self._adapterConfig["connection"]
                )
            except ConnectionRefusedError:
                yield from asyncio.sleep(1)
                continue

            while True:
                if not self._ready():
                    break

                asyncio.async(self._call(_Callable(Aria2Method.GetGlobalStat)))
                asyncio.async(self._call(_Callable(Aria2Method.TellActive)))
                asyncio.async(self._call(_Callable(Aria2Method.TellWaiting, 0, 100000)))
                asyncio.async(self._call(_Callable(Aria2Method.TellStopped, 0, 100000)))
                yield from asyncio.sleep(1)

    @asyncio.coroutine
    def _getMessage(self):
        while True:
            if not self._ready():
                yield from asyncio.sleep(1)  # try again in 1 sec
                continue

            msg = yield from self._ws.recv()
            if not msg:
                continue  # disconnected
            datas = json.loads(msg)

            # make a single call's response look like being part of a batch call
            if not isinstance(datas, list):
                datas = (datas,)

            for data in datas:
                assert data["jsonrpc"] == "2.0"
                if "error" in data:
                    print("Error Handling", data)  # TODO
                else:
                    id_ = data.get("id", None)
                    if id_:
                        # response
                        shortName = _getShortName(id_)
                        result = data["result"]
                    else:
                        # notifications
                        shortName = _getShortName(data["method"])
                        result = data["params"]

                    cb = getattr(self, "_cb_" + shortName, None)
                    if cb:
                        assert asyncio.iscoroutinefunction(cb)
                        yield from cb(result)

    @asyncio.coroutine
    def _call(self, callable_: _Callable):
        assert isinstance(callable_, _Callable)
        additionals = dict()
        if "rpc-secret" in self._adapterConfig:
            additionals["token"] = self._adapterConfig["rpc-secret"]
        payload = callable_.toString(**additionals)
        yield from self._ws.send(payload)

    def _callFromExternal(self, callable_: _Callable):
        self._loop.call_soon_threadsafe(asyncio.async, self._call(callable_))

    def do_createTask(self, creation: TaskCreation) -> (bool, str):
        url = [creation.url]
        options = dict(dir = creation.path)

        if creation.kind in (TaskCreationType.Normal,):
            # see if need to override filename
            fileInfo = creation.subtaskInfo[0]
            if fileInfo.name_userset:
                options["out"] = fileInfo.name

            self._callFromExternal(
                _Callable(Aria2Method.AddUri, url, options)
            )
            return True, None
        elif creation.kind == TaskCreationType.Magnet:
            self._callFromExternal(
                _Callable(Aria2Method.AddUri, url, dict())
            )
            return True, None

        return False, "Not implemented."

    def do_getFiles(self, gid):
        self._callFromExternal(_Callable(Aria2Method.GetFiles, gid))

    def do_delTasks(self, tasks, options):
        taskIds = map(lambda t: t.realid, tasks)
        calls = map(lambda gid: _Callable(Aria2Method.RemoveDownloadResult, gid), taskIds)
        delMultiple = _Callable(Aria2Method.MultiCall, *calls)
        # TODO: aria2 don't remove files from filesystem.
        self._callFromExternal(delMultiple)

    @asyncio.coroutine
    def _cb_tellActive(self, result):
        self.klassMap.klass(Aria2TaskClass.Active).updateData(result)

    @asyncio.coroutine
    def _cb_tellWaiting(self, result):
        self.klassMap.klass(Aria2TaskClass.Waiting).updateData(result)

    @asyncio.coroutine
    def _cb_tellStopped(self, result):
        self.klassMap.klass(Aria2TaskClass.Stopped).updateData(result)

    @asyncio.coroutine
    def _cb_getGlobalOption(self, result):
        pass

    @asyncio.coroutine
    def _cb_getFiles(self, result):
        print("_cb_getFiles", result)

    @asyncio.coroutine
    def _cb_onDownloadStart(self, event):
        print("_cb_onDownloadComplete", event)

    @asyncio.coroutine
    def _cb_onDownloadPause(self, event):
        print("_cb_onDownloadPause", event)

    @asyncio.coroutine
    def _cb_onDownloadStop(self, event):
        print("_cb_onDownloadStop", event)

    @asyncio.coroutine
    def _cb_onDownloadComplete(self, event):
        print("_cb_onDownloadComplete", event)

    @asyncio.coroutine
    def _cb_onDownloadError(self, event):
        print("_cb_onDownloadError", event)

    @asyncio.coroutine
    def _cb_onBtDownloadComplete(self, event):
        print("_cb_onBtDownloadComplete", event)

    # Stats
    @pyqtProperty(int, notify = statPolled)
    def dlSpeed(self):
        return self._dlSpeed

    @pyqtProperty(int, notify = statPolled)
    def ulSpeed(self):
        return self._ulSpeed

    @pyqtProperty(int, notify = statPolled)
    def runningTaskCount(self):
        return 0  # TODO

    @asyncio.coroutine
    def _cb_getGlobalStat(self, result):
        self._dlSpeed = int(result["downloadSpeed"])
        self._ulSpeed = int(result["uploadSpeed"])
        # TODO: waiting, stopped, stoppedtotal, active
        self.statPolled.emit()
示例#12
0
class KlassMapTest(TestCase):
    def setUp(self):
        adapter = mock.Mock()
        taskModel = mock.Mock()
        self.km = KlassMap(adapter = adapter, namespace = "foo-ins1", taskModel = taskModel)
        self.tm1 = TaskMapBase(klass = 0)
        self.tm1.__class__._Item = DummyTaskItem
        self.km.addTaskMap(self.tm1)
        self.tm2 = TaskMapBase(klass = 1)
        self.tm2.__class__._Item = DummyTaskItem
        self.km.addTaskMap(self.tm2)

        self.am = mock.Mock()
        self.km.setAdapterMap(self.am)

    def test_create_init(self):
        self.assertEqual(self.km.namespace, "foo-ins1")
        self.assertEqual(len(self.km), 0)
        self.assertRaises(NotImplementedError, self.km.__setitem__, "what", "ever")

        # namespace is set
        self.assertEqual(self.tm1.namespace, "foo-ins1")

    def test_create_same_klass(self):
        # try add it again, but with the same class
        tm1_1 = TaskMapBase(klass = 0)
        self.assertRaises(RuntimeError, self.km.addTaskMap, tm1_1)

    def test_task_add_update_delete(self):
        self.tm1.updateData({
            "1": "task1",
            "2": "task2",
        })
        self.assertEqual(len(self.km), 2)
        self.assertEqual(self.km["1"].value, "task1")
        self.assertEqual(self.km["1"].klass, 0)
        self.assertEqual(self.km["2"].value, "task2")
        self.assertEqual(self.km["2"].klass, 0)
        self.assertEqual(self.km["2"].isDeletionPending, False)

        # change item
        self.tm1.updateData({
            "1": "task1!",
            "2": "task2",
        })
        self.assertEqual(len(self.km), 2)
        self.assertEqual(self.km["1"].value, "task1!")
        self.assertEqual(self.km["2"].value, "task2")
        self.assertEqual(self.km["2"].isDeletionPending, False)

        # remove one item
        self.tm1.updateData({
            "1": "task1!",
        })
        self.assertEqual(len(self.km), 2)
        self.assertEqual(self.km["2"].isDeletionPending, True)

        # try to remove it again, shouldn't remove
        self.tm1.updateData({
            "1": "task1!",
        })
        self.assertEqual(len(self.km), 2)
        self.assertEqual(self.km["2"].isDeletionPending, True)

        # updateData with taskMap2
        self.tm2.updateData({})
        self.assertEqual(len(self.km), 1)
        self.assertRaises(KeyError, self.km.__getitem__, "2")

    def test_task_move(self):
        # order: 1, 2, then move 1 to 2
        self.tm1.updateData({
            "1": "task1",
        })

        self.tm2.updateData({
            "1": "task1??",
        })
        self.tm1.updateData({})

        self.assertEqual(self.km["1"].isDeletionPending, True)

        # until tm1 deletes it, it shouldn't be in tm2
        self.assertEqual(len(self.tm1), 1)
        self.assertEqual(len(self.tm2), 0)

        # after tm2 updates again, it should be moved
        self.tm2.updateData({
            "1": "task1??",
        })
        self.assertEqual(len(self.tm1), 0)
        self.assertEqual(len(self.tm2), 1)
        self.assertEqual(self.tm2["1"].value, "task1??")
        self.assertEqual(self.tm2["1"].isDeletionPending, False)

        self.assertFalse(self.am.afterMove.called)

    def test_task_move_model_move_down(self):
        # set tm1 = [0,1,2,3] tm2=[4,5]
        self.tm1.updateData({
            "0": "task0",
        })
        self.tm1.updateData({
            "0": "task0",
            "1": "task1",
        })
        self.tm1.updateData({
            "0": "task0",
            "1": "task1",
            "2": "task2",
        })
        self.tm1.updateData({
            "0": "task0",
            "1": "task1",
            "2": "task2",
            "3": "task3",
        })
        self.tm2.updateData({
            "4": "task4",
            "5": "task5",
        })

        # move 2 from tm1 to tm2
        self.assertFalse(self.km["2"].isDeletionPending)
        self.tm1.updateData({
            "0": "task0",
            "1": "task1",
            "3": "task3",
        })
        self.assertTrue(self.km["2"].isDeletionPending)
        self.tm2.updateData({
            "2": "task2",
            "4": "task4",
            "5": "task5",
        })
        self.assertFalse(self.km["2"].isDeletionPending)

        self.am.beforeMove.assert_called_with("foo-ins1", 2, 6)

    def test_task_move_model_move_up(self):
        # tm1 is empty; tm2's 3rd item is task2
        self.tm1.updateData({})
        self.tm2.updateData({
            "0": "task0",
            "1": "task1",
        })
        self.tm2.updateData({
            "0": "task0",
            "1": "task1",
            "2": "task2",
        })
        self.tm2.updateData({
            "0": "task0",
            "1": "task1",
            "2": "task2",
            "3": "task3",
            "4": "task4",
            "5": "task5",
        })

        # move task2 to tm1 from tm2
        self.assertFalse(self.km["2"].isDeletionPending)
        self.tm2.updateData({
            "0": "task0",
            "1": "task1",
            "3": "task3",
            "4": "task4",
            "5": "task5",
        })
        self.assertTrue(self.km["2"].isDeletionPending)
        self.tm1.updateData({
            "2": "task2moved",
        })
        self.assertFalse(self.km["2"].isDeletionPending)
        self.am.beforeMove.assert_called_with("foo-ins1", 2, 0)

    def test_iter_index(self):
        self.tm1.updateData({
            "1": 10,
        })
        self.assertEqual(self.km.index("1"), 0)

        self.tm2.updateData({
            "4": 41,
        })
        self.assertEqual(self.km.index("4"), 1)

        self.tm2.updateData({
            "4": 41,
            "2": 25,
        })
        self.assertEqual(self.km.index("2"), 2)

        self.tm1.updateData({
            "1": 10,
            "3": 37,
        })
        self.assertEqual(self.km.index("3"), 1)

        # index()
        with self.assertRaises(ValueError):
            self.km.index("0")

        # __contains__
        for key in ["1", "2", "3", "4"]:
            self.assertTrue(key in self.km, key)
        for key in ["5", "6", "7"]:
            self.assertFalse(key in self.km, key)

        # __iter__
        self.assertListEqual(list(self.km.__iter__()), ['1', '3', '4', '2'])

        # items()
        self.assertListEqual(
            list(map(lambda pair: (pair[0], pair[1].value), self.km.items())),
            [("1", 10), ("3", 37), ("4", 41), ("2", 25)],
        )

        # values()
        self.assertListEqual(
            list(map(lambda i: i.value, self.km.values())),
            [10, 37, 41, 25],
        )

    def test_get_klassMap(self):
        self.assertEqual(self.km.klass(0), self.tm1)
        self.assertNotEqual(self.km.klass(1), self.tm1)

    def test_findItemKlass(self):
        self.tm1.updateData({
            "1": 1,
            "2": 2,
        })
        self.tm2.updateData({
            "3": 3,
        })
        self.assertEqual(self.km.findItemKlass("1"), 0)
        self.assertEqual(self.km.findItemKlass("2"), 0)
        self.assertEqual(self.km.findItemKlass("3"), 1)
        with self.assertRaises(KeyError):
            self.km.findItemKlass("4")