Ejemplo n.º 1
0
    def create_pool_list(self):
        pool_list = []
        for p in self.pools.all_pools:
            pool_info = {
                'id':
                p['id'],
                'name':
                smart_strip(p['name'], 30),
                'algo':
                p['algo'],
                'is_fixed':
                p['is_fixed'],
                'is_hidden':
                p['is_hidden'],
                'num_cpus':
                get_num_cpus() if p['num_cpus'] == 0 else p['num_cpus'],
                'priority_level':
                p['priority_level'] if 'priority_level' in p else 0,
            }
            pool_list.append(pool_info)

        self.on_create_note_pool_list_event.emit(json.dumps(pool_list),
                                                 CPU_COUNT,
                                                 sys.platform == "win32")
Ejemplo n.º 2
0
class Hub(QObject):
    def __init__(self, app):
        super(Hub, self).__init__()
        self.app = app
        self.pools = Pools(self.app.property("AppPath"))
        self.pools.load_all()

        self.add_pool_dialog = AddPoolDialog(self.app, self, "addpool.html",
                                             False)

    def setUI(self, ui):
        self.ui = ui

    @Slot(str)
    def open_web(self, url):
        #print url
        webbrowser.open(url)
        self.on_web_open_event.emit("web opened")

    @Slot(str, int)
    def start_stop_mining(self, pool_id, num_procs=0):
        pool_info = self.pools.find_pool(pool_id)
        if not pool_info:
            self.on_back_end_error_event.emit("Error: pool [%s] not found" %
                                              pool_id)
            return

        if pool_info['username'] == "":
            username, result = self._custom_input_dialog(self.ui, \
                        "Enter Wallet Address", "Please enter wallet address to mine:")
            if result:
                ##TODO: address validation
                pool_info['username'] = username
                self.pools.save_all()
            else:
                QMessageBox.warning(self.ui, "Wallet Address Required", \
                    "Wallet address is required to start mining!<br><br>\
                    Hint: You can get a new address from:<br><br><b>https://wallet.evonote.com</b>"                                                                                                   )
                self.on_stop_mining_event.emit(pool_info["id"])
                return
        """ Stop if mining """
        if self._stop_mining(pool_info):
            self.on_stop_mining_event.emit(pool_info["id"])
            return
        """ Else start mining """
        if num_procs == 0:
            num_procs = get_num_cpus()

        global manager
        if not manager: manager = Manager()

        if not 'work_submit_queue' in pool_info:
            work_submit_queue = Queue()
            pool_info['work_submit_queue'] = work_submit_queue
        else:
            work_submit_queue = pool_info['work_submit_queue']

        if not 'g_work' in pool_info:
            g_work = manager.dict()
            pool_info['g_work'] = g_work
        else:
            g_work = pool_info['g_work']

        if not 'hash_report' in pool_info:
            hash_report = manager.dict()
            pool_info['hash_report'] = hash_report
        else:
            hash_report = pool_info['hash_report']

        if not 'work_report' in pool_info:
            work_report = manager.dict()
            pool_info['work_report'] = work_report
        else:
            work_report = pool_info['work_report']

        cpu_priority_level = get_cpu_priority_level(
            pool_info['priority_level'])

        pool_info['thr_list'] = []
        for thr_id in range(num_procs):
            p = MinerWork(thr_id, work_submit_queue, g_work, hash_report,
                          get_cpu_priority_level('normal'))
            p.start()
            p.set_cpu_priority(cpu_priority_level)
            pool_info['thr_list'].append(p)

        # set main UI process priority to normal level to avoid UI frozen
        psutil.Process().nice(NORMAL_CPU_PRIORITY_LEVEL)

        pool_info['num_cpus'] = num_procs
        pool_info['is_mining'] = True

        rpc = MinerRPC(pool_info, work_submit_queue, g_work, work_report)
        rpc.set_thread_list(pool_info['thr_list'])
        rpc.daemon = True
        rpc.start()
        pool_info['rpc'] = rpc

        self.on_start_mining_event.emit(pool_info["id"])

    def _stop_mining(self, pool_info):
        if 'thr_list' in pool_info and pool_info['thr_list'] is not None:
            # shut down threads
            for thr in pool_info['thr_list']:
                thr.shutdown()
                thr.join()
                self.app_process_events(0.1)
            pool_info['thr_list'] = None

            # shut down RPC client
            pool_info['rpc'].shutdown()
            pool_info['rpc'].join()
            work_submit_queue = pool_info['work_submit_queue']
            # clear the submit queue
            while not work_submit_queue.empty():
                _ = work_submit_queue.get()

            if 'error' in pool_info:
                pool_info['error'] = None

            pool_info['is_mining'] = False
            return True
        return False

    def app_process_events(self, seconds=1):
        for _ in range(int(seconds * 10)):
            self.app.processEvents()
            sleep(.1)

    @Slot(str, int)
    def change_cpus(self, pool_id, num_cpus):
        #         pool_info = get_pool(pool_id)
        pool_info = self.pools.find_pool(pool_id)
        if not pool_info:
            self.on_back_end_error_event.emit("Error: pool [%s] not found" %
                                              pool_id)
            return

        pool_info['num_cpus'] = num_cpus

        if not 'thr_list' in pool_info or pool_info['thr_list'] is None:
            # it means mining stopped or never started
            return

        thr_list = pool_info['thr_list']
        if num_cpus > len(thr_list):
            # add more cores
            cpus = num_cpus - len(thr_list)
            work_submit_queue = pool_info['work_submit_queue']
            g_work = pool_info['g_work']
            hash_report = pool_info['hash_report']
            for _ in range(cpus):
                thr_id = len(thr_list)
                p = MinerWork(thr_id, work_submit_queue, g_work, hash_report,
                              get_cpu_priority_level('normal'))
                thr_list.append(p)
                g_work['num_thrs'] = len(thr_list)
                p.start()
                p.set_cpu_priority(
                    get_cpu_priority_level(pool_info['priority_level']))
        elif num_cpus < len(thr_list):
            # remove cores
            for _ in range(len(thr_list) - num_cpus):
                thr = thr_list.pop(len(thr_list) - 1)
                thr.shutdown()
                thr.join()

    @Slot(str, str)
    def change_priority(self, pool_id, priority_level):
        #print "Change process priority to", priority_level
        cpu_priority_level = get_cpu_priority_level(priority_level)

        pool_info = self.pools.find_pool(pool_id)
        if not pool_info:
            self.on_back_end_error_event.emit("Error: pool [%s] not found" %
                                              pool_id)
            return

        if 'thr_list' in pool_info and pool_info['thr_list'] is not None:
            for thr in pool_info['thr_list']:
                thr.set_cpu_priority(cpu_priority_level)

        pool_info['priority_level'] = priority_level
        sleep(1)
        psutil.Process().nice(NORMAL_CPU_PRIORITY_LEVEL)

        #print "Main process priority", psutil.Process().nice()

    @Slot(str)
    def view_log(self, pool_id):
        log_file = os.path.join(DATA_DIR, 'logs', "%s.log" % pool_id)
        log_dialog = LogViewer(parent=self.ui, log_file=log_file)
        log_dialog.load_log()

    @Slot(str)
    def hide_pool_row(self, pool_id):
        pool_info = self.pools.find_pool(pool_id)
        if pool_info['is_mining']:
            QMessageBox.warning(self.ui, 'Pool Hide Not Allowed',
                                "Pool in mining is not allowed to hide!")
            return

        pool_info['is_hidden'] = True
        self.ui.resetWindowSize()
        self.on_hide_pool_success_event.emit(pool_id)

    @Slot(str)
    def show_pools(self, pool_ids):
        if not pool_ids: return
        pool_ids = pool_ids.split(',')
        for pool_id in pool_ids:
            pool_info = self.pools.find_pool(pool_id)
            pool_info['is_hidden'] = False
        self.ui.resetWindowSize()

    @Slot(str)
    def remove_pool(self, pool_id):
        pool_info = self.pools.find_pool(pool_id)
        # possible to remove user's created pool only
        if not pool_info or pool_info['is_fixed'] == True:
            return

        reply = QMessageBox.question(self.ui, 'Remove Pool Confirmation',
                                     "Are you sure to remove this pool?",
                                     QMessageBox.Yes, QMessageBox.No)

        if reply == QMessageBox.Yes:
            # stop mining
            self._stop_mining(pool_info)
            # remove pool from list
            self.pools.remove_pool(pool_id)
            # tell UI to remove the pool row
            self.on_remove_pool_confirm_event.emit(pool_id)
            # resize window to fit
            self.ui.resetWindowSize()
            # save pool list
            self.pools.save_all()

    @Slot()
    def show_addpool_dialog(self):
        if len(self.pools.all_pools) >= POOL_SIZE_LIMIT:
            QMessageBox.warning(
                self.ui, 'Add/Edit Pool Error',
                "<b>You have reached number of pools limit!\
            <br> Adding new pool is not allowed.</b><br><br><i>Hint: Remove unused pools to add new ones</i>"
            )
            return
        self.on_reset_addpool_form_event.emit()
        #         self.add_pool_dialog.center()
        self.add_pool_dialog.exec_()
#         self.add_pool_dialog.show()

    @Slot()
    def close_addpool_dialog(self):
        self.add_pool_dialog.close()

    @Slot(str, str, str, str, str, str, bool)
    def add_edit_pool(self, pool_id, pool_display_name, pool_url,
                      pool_username, pool_password, pool_algo, pool_ssl):

        if not pool_display_name.strip():
            QMessageBox.warning(self.add_pool_dialog, 'Add/Edit Pool Error',
                                "Pool Name is required.")
            return
        if not pool_url.strip():
            QMessageBox.warning(self.add_pool_dialog, 'Add/Edit Pool Error',
                                "Pool URL/Port is required.")
            return
        if not pool_username.strip():
            QMessageBox.warning(self.add_pool_dialog, 'Add/Edit Pool Error',
                                "Wallet Address is required.")
            return
        if not pool_algo:
            QMessageBox.warning(self.add_pool_dialog, 'Add/Edit Pool Error',
                                "Hash algorithm is required.")
            return

        url_host = None
        url_port = None
        try:
            if '://' not in pool_url:
                pool_url = "stratum+tcp://" + pool_url
            url = urlparse.urlparse(pool_url.strip())
            if not url.hostname:
                QMessageBox.warning(
                    self.add_pool_dialog, 'Add/Edit Pool Error',
                    "Pool URL (e.g. pool.evonote.com) is required.")
                return

            if not url.port:
                reply = QMessageBox.question(
                    self.add_pool_dialog, 'Add Pool Confirmation',
                    "Pool port is not specified.\nDo you want to use default port [3333] instead?",
                    QMessageBox.Yes, QMessageBox.No)
                if reply == QMessageBox.No:
                    return
                url_port = 3333
            else:
                url_port = int(url.port)

            url_host = url.hostname
        except Exception, e:
            log("Invalid pool URL: " + str(e), LEVEL_ERROR)
            QMessageBox.warning(
                self.add_pool_dialog, 'Add/Edit Pool Error',
                "Invalid pool URL!<br><br>Pool URL must be in form of <b>URL:Port</b><br> like <b>pool.evonote.com:3333</b>"
            )
            return

        if pool_id == "":
            pool_id = str(uuid.uuid4())
            pool_info = {
                'id': pool_id,
                'name': pool_display_name.strip()[:50],
                'url': "stratum+tcp://%s:%d" % (url_host, url_port),
                'username': pool_username.strip()[:512],
                'password': pool_password.strip()[:512],
                'algo': pool_algo,
                'is_fixed': False,
                'is_mining': False,
                'num_cpus': get_num_cpus(),
                'priority_level': 'normal',
                'is_hidden': False,
                'ssl_enabled': pool_ssl,
            }

            self.pools.add_pool(pool_info)
            # resize window to fit
            self.ui.resetWindowSize()
            # set pool info to UI
            _pool_info = {
                'id':
                pool_info['id'],
                'name':
                smart_strip(pool_info['name'], 30),
                'algo':
                pool_info['algo'],
                'is_fixed':
                pool_info['is_fixed'],
                'is_hidden':
                pool_info['is_hidden'],
                'num_cpus':
                get_num_cpus()
                if pool_info['num_cpus'] == 0 else pool_info['num_cpus'],
                'priority_level':
                pool_info['priority_level'],
            }
            self.on_create_note_pool_list_event.emit(json.dumps([_pool_info]),
                                                     CPU_COUNT,
                                                     sys.platform == "win32")
            self.add_pool_dialog.close()
            QMessageBox.information(
                self.ui, '%s - Add Pool Success' % APP_NAME,
                "New mining pool \"%s\" \nhas been added!" %
                _pool_info['name'])
        else:  # Edit pool
            pool_info = self.pools.find_pool(pool_id)
            need_restart_mining = False
            if pool_info:
                if pool_info['algo'] != pool_algo:
                    pool_info['algo'] = pool_algo
                    need_restart_mining = True

                pool_info['name'] = pool_display_name.strip()[:50]

                url = "stratum+tcp://%s:%d" % (url_host, url_port)
                if pool_info['url'] != url:
                    pool_info['url'] = url
                    need_restart_mining = True

                username = pool_username.strip()[:512]
                if pool_info['username'] != username:
                    pool_info['username'] = username
                    need_restart_mining = True

                password = pool_password.strip()[:512]
                if pool_info['password'] != password:
                    pool_info['password'] = password
                    need_restart_mining = True

                if 'ssl_enabled' in pool_info and pool_info[
                        'ssl_enabled'] != pool_ssl:
                    need_restart_mining = True
                pool_info['ssl_enabled'] = pool_ssl

                pool_info['is_hidden'] = False

                _pool_info = {
                    'id': pool_info['id'],
                    'name': smart_strip(pool_info['name'], 30),
                }
                self.on_edit_pool_success_event.emit(json.dumps(_pool_info))
                self.add_pool_dialog.close()

                # restart mining with new settings
                if need_restart_mining and pool_info['is_mining']:
                    if self._stop_mining(pool_info):
                        self.on_stop_mining_event.emit(pool_info["id"])
                        self.app_process_events(1)
                        self.start_stop_mining(pool_info["id"],
                                               pool_info['num_cpus'])

        # save pool list
        self.pools.save_all()